From 5fc8cf20f3d005f12a6cf0570d01f239c550121d Mon Sep 17 00:00:00 2001 From: Dimitur Kirov Date: Tue, 26 Sep 2006 15:57:47 +0000 Subject: [PATCH] merge from trunk --- Changelog | 40 +- README | 16 +- data/glade/gajim_themes_window.glade | 46 -- data/glade/message_window.glade | 4 +- data/glade/preferences_window.glade | 288 +++++--- data/glade/privacy_list_window.glade | 779 ++++++++++++++++++++++ data/glade/privacy_lists_window.glade | 255 +++++++ data/glade/profile_window.glade | 340 ++++++---- data/glade/roster_window.glade | 737 +++++++++++--------- data/glade/systray_context_menu.glade | 12 +- data/glade/vcard_information_window.glade | 84 ++- po/pl.po | 2 +- src/chat_control.py | 9 +- src/common/config.py | 9 +- src/common/connection_handlers.py | 8 +- src/common/helpers.py | 15 + src/common/socks5.py | 10 +- src/common/xmpp/protocol.py | 15 +- src/config.py | 84 ++- src/conversation_textview.py | 15 +- src/dbus_support.py | 16 +- src/dialogs.py | 104 +-- src/disco.py | 194 +++--- src/filetransfers_window.py | 15 +- src/gajim-remote.py | 22 +- src/gajim.py | 57 +- src/gajim_themes_window.py | 9 +- src/groupchat_control.py | 38 +- src/gtkgui_helpers.py | 26 +- src/message_window.py | 16 +- src/music_track_listener.py | 118 ++++ src/notify.py | 18 +- src/profile_window.py | 137 +++- src/remote_control.py | 207 ++---- src/roster_window.py | 118 +++- src/systray.py | 14 +- src/vcard.py | 43 +- 37 files changed, 2791 insertions(+), 1129 deletions(-) create mode 100644 data/glade/privacy_list_window.glade create mode 100644 data/glade/privacy_lists_window.glade create mode 100644 src/music_track_listener.py diff --git a/Changelog b/Changelog index 7856b930b..f21490300 100644 --- a/Changelog +++ b/Changelog @@ -1,14 +1,38 @@ +Gajim 0.11 (XX October 2006) + + * Put your stuff here [each dev put their own please; next time we do this everytime we commit sth major and not in the end] + + * We can now operate on more than one contact in one in time in roster (#1514) + * Connection lost is now a non-intrusive popup + * Try to get contact desired nick when we add him to roster aka User Nickname (JEP-0172) + * Better design of User Profile window, with a progress bar + * New Add User dialog, with possibility to register to transport directly from it + * Completion for "Start Chat" input dialog + * We can now have a different spellchecking language in each chat window. (#2383 and #746) + * Support for Privacy Lists + * Forbid to run multiple instances (but you can use differents profiles) + * We can save avatar with right click on avatar in chat banner + * Use differents colors for nickname colors of occupants in groupchats. + * Ability to show only Join/Leave in groupchats instead of all status changes + * New possibilities to insert nickname of an occupant in groupchats conversations : Tab in an empty line now cycle through nicks, maj+right click->insert nickname, maj+click on name in gc-roster, /names command to show all users presents + + * Fixed bugs when removing or renaming an account with tabs open (#2369 and #2370) + + * FIXME : Ad-Hoc for 0.11 ? + * FIXME : does that work ? not tested : Metacontacts across accounts (#1596) + * Gajim now requires Python 2.4 to run + Gajim 0.10.1 (06 June 2006) - * freeze and lost contacts in roster (#1953) - * popup menus are correctly placed - * high CPU usage on FreeBSD (#1963) - * nickname can contain '|' (#1913) - * update pl, cs, fr translations - * don't play sound, when no event is shown (#1970) - * set gajim icon for history manager + * Freeze and lost contacts in roster (#1953) + * Popup menus are correctly placed + * High CPU usage on FreeBSD (#1963) + * Nickname can contain '|' (#1913) + * Update pl, cs, fr translations + * Don't play sound, when no event is shown (#1970) + * Set gajim icon for history manager * gajim.desktop is generated with translation (#834) - * preventing several TBs and annoyances (r6273, r6275, r6279, r6301, + * Preventing several TBs and annoyances (r6273, r6275, r6279, r6301, r6308, r6311, r6323, r6326, r6327, r6335, r6342, r6346, r6348) Gajim 0.10 (01 May 2006) diff --git a/README b/README index 9f11c8552..5e2ad7468 100644 --- a/README +++ b/README @@ -1,10 +1,10 @@ Welcome and thanks for trying out Gajim. =RUNTIME REQUIREMENTS= -python2.4 (python2.3 should work too) +python2.4 or higher pygtk2.6 or higher python-libglade -pysqlite2 (aka. python-pysqlite2) +pysqlite2 (if you have python 2.5, you already have this) some distros also split too much python standard library. I know SUSE does. In such distros you also need python-xml @@ -29,7 +29,8 @@ notification-daemon (and D-Bus) to get cooler popups D-Bus to have gajim-remote working NOTE TO PACKAGERS: -Gajim is a GTK+ app and not a gnome one. Just do 'make' so you don't require gnomepythonextras +Gajim is a GTK+ app and not a gnome one. +Just do 'make' so you don't require gnomepythonextras which is gnome dep =INSTALLATION PROCEDURE= @@ -65,9 +66,6 @@ you're advised to enable verbose via advanced configuration window. If you don't want to make this permanent, execute gajim with --verbose everytime you want to have verbose output. -Cannot join room with password: -please read the FAQ for the reply on this issue - =FAQ/Wiki= FAQ can be found at http://trac.gajim.org/wiki/GajimFaq Wiki can be found at http://trac.gajim.org/wiki @@ -75,13 +73,13 @@ Wiki can be found at http://trac.gajim.org/wiki That is all, enjoy! -(C) 2003-2005 +(C) 2003-2006 The Gajim Team http://gajim.org PS. -we use original art and parts of sounds and other art from Psi, Gossip, +We use original art and parts of sounds and other art from Psi, Gossip, Gnomebaker, Gaim and some icons from various gnome-icons (mostly Dropline Etiquette) we found at art.gnome.org -If you think we're violating a license please inform us +If you think we're violating a license please inform us. Thank you diff --git a/data/glade/gajim_themes_window.glade b/data/glade/gajim_themes_window.glade index 44d7e381c..9f0dd0092 100644 --- a/data/glade/gajim_themes_window.glade +++ b/data/glade/gajim_themes_window.glade @@ -554,52 +554,6 @@ Banner - - - True - Active - False - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - 1 - 1 - 2 - fill - - - - - - - True - True - False - True - - - - 1 - 2 - 1 - 2 - fill - - - - True diff --git a/data/glade/message_window.glade b/data/glade/message_window.glade index ecd51204d..d4678bfd3 100644 --- a/data/glade/message_window.glade +++ b/data/glade/message_window.glade @@ -374,7 +374,7 @@ Status message True - gtk-preferences + gtk-execute 4 0.5 0.5 @@ -916,7 +916,7 @@ topic True - gtk-preferences + gtk-execute 4 0.5 0.5 diff --git a/data/glade/preferences_window.glade b/data/glade/preferences_window.glade index f5aceb5ff..573c182a8 100644 --- a/data/glade/preferences_window.glade +++ b/data/glade/preferences_window.glade @@ -819,7 +819,7 @@ Per type 0.5 0 0 - before_time_entry + scrolledwindow25 PANGO_ELLIPSIZE_NONE -1 False @@ -848,7 +848,7 @@ Per type 0.5 0 0 - after_nickname_entry + scrolledwindow28 PANGO_ELLIPSIZE_NONE -1 False @@ -998,7 +998,7 @@ Per type 0.5 0 0 - after_time_entry + scrolledwindow26 PANGO_ELLIPSIZE_NONE -1 False @@ -1027,7 +1027,7 @@ Per type 0.5 0 0 - before_nickname_entry + scrolledwindow27 PANGO_ELLIPSIZE_NONE -1 False @@ -1043,54 +1043,6 @@ Per type - - - 39 - True - True - True - True - 0 - - True - * - False - - - - 3 - 4 - 0 - 1 - - - - - - - - 40 - True - True - True - True - 0 - - True - * - False - - - - 3 - 4 - 1 - 2 - - - - - True @@ -1132,54 +1084,6 @@ Per type - - - 40 - True - True - True - True - 0 - - True - * - False - - - - 1 - 2 - 1 - 2 - - - - - - - - 40 - True - True - True - True - 0 - - True - * - False - - - - 1 - 2 - 0 - 1 - - - - - True @@ -1298,6 +1202,170 @@ Per type fill + + + + 30 + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_CHAR + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + 3 + 4 + 0 + 1 + fill + + + + + + + 30 + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_CHAR + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + 3 + 4 + 1 + 2 + fill + + + + + + + 30 + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_CHAR + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + 1 + 2 + 1 + 2 + fill + + + + + + + 30 + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_CHAR + True + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + + 1 + 2 + 0 + 1 + fill + + + 0 @@ -2543,6 +2611,26 @@ Disabled + + + True + True + Set status message to reflect currently playing _music track + True + GTK_RELIEF_NORMAL + True + False + False + True + + + + 0 + False + False + + + True diff --git a/data/glade/privacy_list_window.glade b/data/glade/privacy_list_window.glade new file mode 100644 index 000000000..1f14d6b71 --- /dev/null +++ b/data/glade/privacy_list_window.glade @@ -0,0 +1,779 @@ + + + + + + + 6 + True + Privacy List + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + False + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + 600 + True + False + 0 + + + + True + True + 0 + + + + True + <i>Privacy List</i> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + True + Active for this session + True + GTK_RELIEF_NORMAL + True + False + False + True + + + + 0 + False + False + + + + + + True + True + Active on each startup + True + GTK_RELIEF_NORMAL + True + False + False + True + + + + 0 + False + False + + + + + 0 + False + True + + + + + + True + + + 5 + False + False + + + + + + True + <b>List of rules</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 5 + False + False + + + + + + True + + False + True + + + + 5 + False + True + + + + + + True + True + 0 + + + + 5 + True + True + gtk-add + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + 5 + True + True + gtk-remove + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + 6 + True + True + gtk-edit + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + 0 + False + True + + + + + + 5 + False + 0 + + + + True + + + 5 + True + True + + + + + + True + <b>Add / Edit a rule</b> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 5 + False + False + + + + + + True + False + 0 + + + + True + True + 0 + + + + True + True + Allow + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + True + True + Deny + True + GTK_RELIEF_NORMAL + True + False + False + True + edit_allow_radiobutton + + + 0 + False + False + + + + + 0 + True + True + + + + + + True + True + 0 + + + + 5 + True + False + 0 + + + + True + True + JabberID + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 5 + False + False + + + + + + True + True + True + True + 0 + + True + + False + + + 5 + True + True + + + + + 0 + True + True + + + + + + 5 + True + False + 0 + + + + True + True + all in the group + True + GTK_RELIEF_NORMAL + True + False + False + True + edit_type_jabberid_radiobutton + + + 5 + False + False + + + + + + True + + False + True + + + 5 + True + True + + + + + 0 + True + True + + + + + + 5 + True + False + 0 + + + + True + True + all by subscription + True + GTK_RELIEF_NORMAL + True + False + False + True + edit_type_jabberid_radiobutton + + + 5 + False + False + + + + + + True + none +both +from +to + False + True + + + 5 + True + True + + + + + 0 + True + True + + + + + + 10 + True + False + 0 + + + + True + True + All + True + GTK_RELIEF_NORMAL + True + False + False + True + edit_type_jabberid_radiobutton + + + 0 + False + False + + + + + 0 + False + False + + + + + 0 + True + True + + + + + + True + True + 0 + + + + True + True + to send me messages + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + True + True + to send me queries + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + True + True + to view my status + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + + True + True + to send me status + True + GTK_RELIEF_NORMAL + True + False + False + True + + + 0 + False + False + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + True + True + 0 + + + + True + False + 0 + + + + True + Order: + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 5 + False + False + + + + + + True + True + 1 + 0 + False + GTK_UPDATE_ALWAYS + False + False + 1 0 100 1 10 10 + + + 0 + False + True + + + + + 0 + True + True + + + + + + 5 + True + True + gtk-save + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + True + + + 0 + False + True + + + + + + True + True + 0 + + + + 5 + True + True + gtk-refresh + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + 5 + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + 0 + False + True + + + + + + + diff --git a/data/glade/privacy_lists_window.glade b/data/glade/privacy_lists_window.glade new file mode 100644 index 000000000..7a7470123 --- /dev/null +++ b/data/glade/privacy_lists_window.glade @@ -0,0 +1,255 @@ + + + + + + + 12 + True + window1 + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + True + False + 0 + + + + True + Server-based Privacy Lists + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 5 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + 4 + True + + False + True + + + 0 + True + True + + + + + + True + True + 0 + + + + 5 + True + True + gtk-delete + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + 5 + True + True + gtk-open + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + 0 + True + True + + + + + + True + + + 5 + True + True + + + + + + True + Create your own Privacy Lists + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 5 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + True + True + True + 0 + + True + + False + + + 4 + False + False + + + + + + 5 + True + True + gtk-new + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + True + + + 5 + True + True + + + + + + True + True + 0 + + + + 5 + True + True + gtk-refresh + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + 5 + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + 0 + True + True + + + + + + + diff --git a/data/glade/profile_window.glade b/data/glade/profile_window.glade index aeaaa8518..723df7548 100644 --- a/data/glade/profile_window.glade +++ b/data/glade/profile_window.glade @@ -4,7 +4,6 @@ - 12 Personal Information GTK_WINDOW_TOPLEVEL GTK_WIN_POS_NONE @@ -29,6 +28,7 @@ + 6 True True True @@ -1016,7 +1016,7 @@ - 0 + 1 4 6 7 @@ -1024,6 +1024,34 @@ expand + + + + True + Avatar: + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 6 + 7 + fill + + + False @@ -1800,159 +1828,201 @@ - + + 6 True - GTK_BUTTONBOX_END - 12 + False + 0 - + True - True - True - GTK_RELIEF_NORMAL - True - - - - - True - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - - - - True - False - 2 - - - - True - gtk-go-up - 4 - 0 - 0 - 0 - 0 - - - 0 - False - False - - - - - - True - _Publish - True - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - + GTK_PROGRESS_LEFT_TO_RIGHT + 0 + 0.10000000149 + PANGO_ELLIPSIZE_NONE + + 0 + False + False + - + True - True - True - GTK_RELIEF_NORMAL - True - + GTK_BUTTONBOX_END + 12 - + True - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 + True + True + GTK_RELIEF_NORMAL + True + - + True - False - 2 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 - + True - gtk-go-down - 4 - 0 - 0 - 0 - 0 - - - 0 - False - False - - + False + 2 - - - True - _Retrieve - True - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 + + + True + gtk-go-up + 4 + 0 + 0 + 0 + 0 + + + 0 + False + False + + + + + + True + _Publish + True + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + - - 0 - False - False - + + + + True + True + True + GTK_RELIEF_NORMAL + True + + + + + True + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-go-down + 4 + 0 + 0 + 0 + 0 + + + 0 + False + False + + + + + + True + _Retrieve + True + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + + + + + + + True + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + + + + + 0 + True + True + @@ -1961,6 +2031,18 @@ True + + + + True + False + + + 0 + False + False + + diff --git a/data/glade/roster_window.glade b/data/glade/roster_window.glade index 94c8b896a..d1c9cf194 100644 --- a/data/glade/roster_window.glade +++ b/data/glade/roster_window.glade @@ -1,328 +1,413 @@ - - - + + + - - 85 - 200 - Gajim - roster - 150 - 400 - - - - - - - True - - - True - - - True - _Actions - True - - - - - - True - _Start Chat - True - - - True - 0,000000 - 0,000000 - gtk-jump-to - 1 - - - - - - - True - _Group Chat - True - - - True - 0,000000 - 0,000000 - gtk-connect - 1 - - - - - - - True - - - - - True - Add _Contact - True - - - True - 0,000000 - 0,000000 - gtk-add - 1 - - - - - - - True - _Discover Services - True - - - True - 0,000000 - 0,000000 - gtk-find - 1 - - - - - - - True - _Advanced - True - - - - - True - Show _Offline Contacts - True - - - - - - - True - - - - - True - _Quit - True - - - - - True - 0,000000 - 0,000000 - gtk-quit - 1 - - - - - - - - - - - True - _Edit - True - - - - - - True - A_ccounts - True - - - - - True - 0,000000 - 0,000000 - gtk-network - 1 - - - - - - - True - File _Transfers - True - - - - - True - 0,000000 - 0,000000 - 1 - - - - - - - True - Profile, Avatar - True - - - True - 0,000000 - 0,000000 - gtk-properties - 1 - - - - - - - True - - - - - True - _Preferences - True - - - - - True - 0,000000 - 0,000000 - gtk-preferences - 1 - - - - - - - - - - - True - _Help - True - - - - - True - Help online - _Contents - True - - - - True - 0,000000 - 0,000000 - gtk-help - 1 - - - - - - - True - Frequently Asked Questions (online) - _FAQ - True - - - - - - True - gtk-about - True - True - - - - - - - - - - False - False - - - - - True - True - 2 - GTK_POLICY_NEVER - GTK_POLICY_AUTOMATIC - - - True - True - False - True - - - - - - - - - - - - - - 1 - - - - - True - - - - False - 2 - - - - - + + + 85 + 200 + Gajim + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 150 + 400 + True + False + roster + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + False + + + + + + + + True + False + 0 + + + + True + GTK_PACK_DIRECTION_LTR + GTK_PACK_DIRECTION_LTR + + + + True + _Actions + True + + + + + + + + True + _Start Chat + True + + + + True + gtk-jump-to + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + _Group Chat + True + + + + True + gtk-connect + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + + + True + Add _Contact + True + + + + True + gtk-add + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + _Discover Services + True + + + + True + gtk-find + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + _Advanced + True + + + + + + True + Show _Offline Contacts + True + False + + + + + + + + True + + + + + + True + _Quit + True + + + + + + True + gtk-quit + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + + + + + True + _Edit + True + + + + + + + + True + A_ccounts + True + + + + + + True + gtk-network + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + File _Transfers + True + + + + + + True + gtk-file + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Profile, Avatar + True + + + + True + gtk-properties + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + + + True + _Preferences + True + + + + + + True + gtk-preferences + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + + + + + True + _Help + True + + + + + + + True + Help online + _Contents + True + + + + + True + gtk-help + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + Frequently Asked Questions (online) + _FAQ + True + + + + + + + True + gtk-about + True + + + + + + + + + + 0 + False + False + + + + + + 2 + True + True + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_NONE + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + True + False + False + False + + + + + + + + + + + + + + 0 + True + True + + + + + + True + False + True + + + + 0 + False + True + + + + + + diff --git a/data/glade/systray_context_menu.glade b/data/glade/systray_context_menu.glade index 0f3aaec88..c82aec544 100644 --- a/data/glade/systray_context_menu.glade +++ b/data/glade/systray_context_menu.glade @@ -2,6 +2,7 @@ + @@ -11,7 +12,7 @@ True - + True gtk-network 1 @@ -31,7 +32,7 @@ True - + True gtk-jump-to 1 @@ -51,7 +52,7 @@ True - + True gtk-connect 1 @@ -71,7 +72,7 @@ True - + True gtk-new 1 @@ -107,7 +108,7 @@ - + True gtk-home 1 @@ -144,4 +145,5 @@ + diff --git a/data/glade/vcard_information_window.glade b/data/glade/vcard_information_window.glade index 7b9eed488..3b88416c0 100644 --- a/data/glade/vcard_information_window.glade +++ b/data/glade/vcard_information_window.glade @@ -652,7 +652,7 @@ - + 12 True 6 @@ -1640,34 +1640,6 @@ - - - True - True - - False - False - GTK_JUSTIFY_LEFT - False - True - 0 - 0 - 5 - 5 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 1 - 4 - 3 - 4 - - - - True @@ -2581,6 +2553,60 @@ True + + + + True + False + 0 + + + + True + GTK_PROGRESS_LEFT_TO_RIGHT + 0 + 0.10000000149 + PANGO_ELLIPSIZE_NONE + + + 0 + False + False + + + + + + True + GTK_BUTTONBOX_END + 0 + + + + True + True + True + gtk-close + True + GTK_RELIEF_NORMAL + True + + + + + + 0 + True + True + + + + + 0 + True + True + + diff --git a/po/pl.po b/po/pl.po index 54bdb4a3b..6303d7024 100644 --- a/po/pl.po +++ b/po/pl.po @@ -1334,7 +1334,7 @@ msgstr "W każdej _wiadomości" #: ../data/glade/preferences_window.glade.h:58 msgid "One message _window:" -msgstr "Wyślij wiadomość i _zamknij okno" +msgstr "Grupuj okna:" #: ../data/glade/preferences_window.glade.h:59 msgid "Play _sounds" diff --git a/src/chat_control.py b/src/chat_control.py index b9cf730a7..7b19fc563 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -196,7 +196,6 @@ class ChatControlBase(MessageControl): self.msg_textview.lang = lang spell.set_language(lang) except (gobject.GError, RuntimeError), msg: - #FIXME: add a ui for this use spell.set_language() dialogs.ErrorDialog(unicode(msg), _('If that is not your language ' 'for which you want to highlight misspelled words, then please ' 'set your $LANG as appropriate. Eg. for French do export ' @@ -1018,7 +1017,7 @@ class ChatControl(ChatControlBase): acct_info = '' self.account_displayed = False for ctrl in self.parent_win.controls(): - if ctrl == self: + if ctrl == self or ctrl.type_id == 'gc': continue if self.contact.get_shown_name() == ctrl.contact.get_shown_name()\ and not avoid_showing_account_too: @@ -1276,9 +1275,6 @@ class ChatControl(ChatControlBase): elif chatstate == 'paused': color = gajim.config.get_per('themes', theme, 'state_paused_color') - else: - color = gajim.config.get_per('themes', theme, - 'state_active_color') if color: # We set the color for when it's the current tab or not color = gtk.gdk.colormap_get_system().alloc_color(color) @@ -1287,6 +1283,9 @@ class ChatControl(ChatControlBase): if chatstate in ('inactive', 'gone') and\ self.parent_win.get_active_control() != self: color = self.lighten_color(color) + elif chatstate == 'active' : # active, get color from gtk + color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] + name = self.contact.get_shown_name() if self.resource: diff --git a/src/common/config.py b/src/common/config.py index 1a0f18865..08b198644 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -37,6 +37,8 @@ opt_bool = [ 'boolean', 0 ] opt_color = [ 'color', '^(#[0-9a-fA-F]{6})|()$' ] opt_one_window_types = ['never', 'always', 'peracct', 'pertype'] +DEFAULT_ICONSET = 'dcraven' + class Config: __options = { @@ -67,7 +69,7 @@ class Config: 'last_status_msg_invisible': [ opt_str, '' ], 'last_status_msg_offline': [ opt_str, '' ], 'trayicon': [ opt_bool, True, '', True ], - 'iconset': [ opt_str, 'dcraven', '', True ], + 'iconset': [ opt_str, DEFAULT_ICONSET, '', True ], 'use_transports_iconsets': [ opt_bool, True, '', True ], 'inmsgcolor': [ opt_color, '#a34526', '', True ], 'outmsgcolor': [ opt_color, '#164e6f', '', True ], @@ -126,6 +128,7 @@ class Config: 'before_nickname': [ opt_str, '' ], 'after_nickname': [ opt_str, ':' ], 'send_os_info': [ opt_bool, True ], + 'set_status_msg_from_current_music_track': [ opt_bool, False ], 'notify_on_new_gmail_email': [ opt_bool, True ], 'notify_on_new_gmail_email_extra': [ opt_bool, False ], 'usegpg': [ opt_bool, False, '', True ], @@ -188,7 +191,7 @@ class Config: 'restored_messages_color': [opt_str, 'grey'], 'restored_messages_small': [opt_bool, True, _('If True, restored messages will use a smaller font than the default one.')], 'hide_avatar_of_transport': [opt_bool, False, _('Don\'t show avatar for the transport itself.')], - 'roster_window_skip_taskbar': [opt_bool, False], + 'roster_window_skip_taskbar': [opt_bool, False, _('Don\'t show roster in the system taskbar.')], 'use_urgency_hint': [opt_bool, True, _('If True and installed GTK+ and PyGTK versions are at least 2.8, make the window flash (the default behaviour in most Window Managers) when holding pending events.')], 'notification_timeout': [opt_int, 5], 'send_sha_in_gc_presence': [opt_bool, True, _('Jabberd1.4 does not like sha info when one join a password protected room. Turn this option to False to stop sending sha info in group chat presences.')], @@ -290,8 +293,6 @@ class Config: 'bannerfontattrs': [ opt_str, 'B', '', True ], # http://www.pitt.edu/~nisg/cis/web/cgi/rgb.html - # FIXME: not black but the default color from gtk+ theme - 'state_active_color': [ opt_color, 'black' ], 'state_inactive_color': [ opt_color, 'grey62' ], 'state_composing_color': [ opt_color, 'green4' ], 'state_paused_color': [ opt_color, 'mediumblue' ], diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index dc15c9b8e..c29cf539a 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -175,7 +175,7 @@ class ConnectionBytestream: except socket.gaierror: self.dispatch('ERROR', (_('Wrong host'), _('The host you configured as the ft_override_host_to_send advanced option is not valid, so ignored.'))) ft_override_host_to_send = self.peerhost[0] - listener = gajim.socks5queue.start_listener(self.peerhost[0], port, + listener = gajim.socks5queue.start_listener(port, sha_str, self._result_socks5_sid, file_props['sid']) if listener == None: file_props['error'] = -5 @@ -1134,6 +1134,12 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco) raise common.xmpp.NodeProcessed def _ErrorCB(self, con, iq_obj): + gajim.log.debug('ErrorCB') + if iq_obj.getQueryNS() == common.xmpp.NS_VERSION: + who = helpers.get_full_jid_from_iq(iq_obj) + jid_stripped, resource = gajim.get_room_and_nick_from_fjid(who) + self.dispatch('OS_INFO', (jid_stripped, resource, '', '')) + return errmsg = iq_obj.getErrorMsg() errcode = iq_obj.getErrorCode() jid_from = helpers.get_full_jid_from_iq(iq_obj) diff --git a/src/common/helpers.py b/src/common/helpers.py index 1226f2cd5..1f3a7206b 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -290,6 +290,21 @@ def get_uf_role(role, plural = False): else: role_name = _('Visitor') return role_name + +def get_uf_affiliation(affiliation): + '''Get a nice and translated affilition for muc''' + if affiliation == 'none': + affiliation_name = Q_('?Group Chat Contact Affiliation:None') + elif affiliation == 'owner': + affiliation_name = _('Owner') + elif affiliation == 'admin': + affiliation_name = _('Administrator') + elif affiliation == 'member': + affiliation_name = _('Member') + else: # Argl ! An unknown affiliation ! + affiliation_name = affiliation.capitalize() + return affiliation_name + def get_sorted_keys(adict): keys = adict.keys() diff --git a/src/common/socks5.py b/src/common/socks5.py index 6e472e1b8..26d8e5672 100644 --- a/src/common/socks5.py +++ b/src/common/socks5.py @@ -74,13 +74,13 @@ class SocksQueue: self.on_success = None self.on_failure = None - def start_listener(self, host, port, sha_str, sha_handler, sid): + def start_listener(self, port, sha_str, sha_handler, sid): ''' start waiting for incomming connections on (host, port) and do a socks5 authentication using sid for generated sha ''' self.sha_handlers[sha_str] = (sha_handler, sid) if self.listener == None: - self.listener = Socks5Listener(self.idlequeue, host, port) + self.listener = Socks5Listener(self.idlequeue, port) self.listener.queue = self self.listener.bind() if self.listener.started is False: @@ -790,12 +790,12 @@ class Socks5Sender(Socks5, IdleObject): self.queue.remove_sender(self.queue_idx, False) class Socks5Listener(IdleObject): - def __init__(self, idlequeue, host, port): - ''' handle all incomming connections on (host, port) + def __init__(self, idlequeue, port): + ''' handle all incomming connections on (0.0.0.0, port) This class implements IdleObject, but we will expect only pollin events though ''' - self.host, self.port = host, port + self.port = port self.queue_idx = -1 self.idlequeue = idlequeue self.queue = None diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py index 2ac25069d..b9d2ad9ff 100644 --- a/src/common/xmpp/protocol.py +++ b/src/common/xmpp/protocol.py @@ -19,7 +19,7 @@ Protocol module contains tools that is needed for processing of xmpp-related data structures. """ -from simplexml import Node,ustr +from simplexml import Node,NodeBuilder,ustr import time NS_ACTIVITY ='http://jabber.org/protocol/activity' # JEP-0108 NS_ADDRESS ='http://jabber.org/protocol/address' # JEP-0033 @@ -94,6 +94,7 @@ NS_VCARD_UPDATE =NS_VCARD+':x:update' NS_VERSION ='jabber:iq:version' NS_WAITINGLIST ='http://jabber.org/protocol/waitinglist' # JEP-0130 NS_XHTML_IM ='http://jabber.org/protocol/xhtml-im' # JEP-0071 +NS_XHTML = 'http://www.w3.org/1999/xhtml' # " NS_DATA_LAYOUT ='http://jabber.org/protocol/xdata-layout' # JEP-0141 NS_DATA_VALIDATE='http://jabber.org/protocol/xdata-validate' # JEP-0122 NS_XMPP_STREAMS ='urn:ietf:params:xml:ns:xmpp-streams' @@ -385,16 +386,21 @@ class Protocol(Node): class Message(Protocol): """ XMPP Message stanza - "push" mechanism.""" - def __init__(self, to=None, body=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None): + def __init__(self, to=None, body=None, xhtml=None, typ=None, subject=None, attrs={}, frm=None, payload=[], timestamp=None, xmlns=NS_CLIENT, node=None): """ Create message object. You can specify recipient, text of message, type of message any additional attributes, sender of the message, any additional payload (f.e. jabber:x:delay element) and namespace in one go. Alternatively you can pass in the other XML object as the 'node' parameted to replicate it as message. """ Protocol.__init__(self, 'message', to=to, typ=typ, attrs=attrs, frm=frm, payload=payload, timestamp=timestamp, xmlns=xmlns, node=node) if body: self.setBody(body) + if xhtml: self.setXHTML(xhtml) if subject: self.setSubject(subject) def getBody(self): """ Returns text of the message. """ return self.getTagData('body') + def getXHTML(self): + """ Returns serialized xhtml-im body text of the message. """ + xhtml = self.getTag('html') + return str(xhtml.getTag('body')) def getSubject(self): """ Returns subject of the message. """ return self.getTagData('subject') @@ -404,6 +410,11 @@ class Message(Protocol): def setBody(self,val): """ Sets the text of the message. """ self.setTagData('body',val) + def setXHTML(self,val): + """ Sets the xhtml text of the message (JEP-0071). + The parameter is the "inner html" to the body.""" + dom = NodeBuilder(val) + self.setTag('html',namespace=NS_XHTML_IM).setTag('body',namespace=NS_XHTML).addChild(node=dom.getDom()) def setSubject(self,val): """ Sets the subject of the message. """ self.setTagData('subject',val) diff --git a/src/config.py b/src/config.py index 30748c007..312b13408 100644 --- a/src/config.py +++ b/src/config.py @@ -1,7 +1,7 @@ ## config.py ## ## Copyright (C) 2003-2006 Yann Le Boulanger -## Copyright (C) 2005-2006 Nikos Kouremenos +## Copyright (C) 2005-2006 Nikos Kouremenos ## Copyright (C) 2005 Dimitur Kirov ## Copyright (C) 2003-2005 Vincent Hanquez ## @@ -182,7 +182,7 @@ class PreferencesWindow: theme = config_theme.replace('_', ' ') model.append([theme]) if gajim.config.get('roster_theme') == config_theme: - theme_combobox.set_active(i) + theme_combobox.set_active(i) i += 1 self.on_theme_combobox_changed(theme_combobox) @@ -212,19 +212,23 @@ class PreferencesWindow: #before time st = gajim.config.get('before_time') - self.xml.get_widget('before_time_entry').set_text(st) + st = helpers.from_one_line(st) + self.xml.get_widget('before_time_textview').get_buffer().set_text(st) #after time st = gajim.config.get('after_time') - self.xml.get_widget('after_time_entry').set_text(st) + st = helpers.from_one_line(st) + self.xml.get_widget('after_time_textview').get_buffer().set_text(st) #before nickname st = gajim.config.get('before_nickname') - self.xml.get_widget('before_nickname_entry').set_text(st) + st = helpers.from_one_line(st) + self.xml.get_widget('before_nickname_textview').get_buffer().set_text(st) #after nickanme st = gajim.config.get('after_nickname') - self.xml.get_widget('after_nickname_entry').set_text(st) + st = helpers.from_one_line(st) + self.xml.get_widget('after_nickname_textview').get_buffer().set_text(st) #Color for incomming messages colSt = gajim.config.get('inmsgcolor') @@ -455,12 +459,18 @@ class PreferencesWindow: # send os info st = gajim.config.get('send_os_info') self.xml.get_widget('send_os_info_checkbutton').set_active(st) + + # set status msg from currently playing music track + st = gajim.config.get('set_status_msg_from_current_music_track') + self.xml.get_widget( + 'set_status_msg_from_current_music_track_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') notify_gmail_checkbutton = self.xml.get_widget('notify_gmail_checkbutton') - notify_gmail_extra_checkbutton = self.xml.get_widget('notify_gmail_extra_checkbutton') + notify_gmail_extra_checkbutton = self.xml.get_widget( + 'notify_gmail_extra_checkbutton') frame_gmail.set_no_show_all(True) for account in gajim.config.get_per('accounts'): @@ -512,6 +522,8 @@ class PreferencesWindow: gajim.interface.systray.change_status(show) else: gajim.config.set('trayicon', False) + if not gajim.interface.roster.window.get_property('visible'): + gajim.interface.roster.window.present() gajim.interface.hide_systray() gajim.config.set('show_roster_on_startup', True) # no tray, show roster! gajim.interface.roster.draw_roster() @@ -523,7 +535,7 @@ class PreferencesWindow: 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_status_msgs_in_roster_checkbutton_toggled(self, widget): self.on_checkbutton_toggled(widget, 'show_status_msgs_in_roster') gajim.interface.roster.draw_roster() @@ -643,9 +655,9 @@ class PreferencesWindow: def _set_sensitivity_for_before_after_time_widgets(self, sensitive): self.xml.get_widget('before_time_label').set_sensitive(sensitive) - self.xml.get_widget('before_time_entry').set_sensitive(sensitive) + self.xml.get_widget('before_time_textview').set_sensitive(sensitive) self.xml.get_widget('after_time_label').set_sensitive(sensitive) - self.xml.get_widget('after_time_entry').set_sensitive(sensitive) + self.xml.get_widget('after_time_textview').set_sensitive(sensitive) def on_time_never_radiobutton_toggled(self, widget): if widget.get_active(): @@ -665,20 +677,33 @@ class PreferencesWindow: self._set_sensitivity_for_before_after_time_widgets(True) gajim.interface.save_config() - def on_before_time_entry_focus_out_event(self, widget, event): - gajim.config.set('before_time', widget.get_text().decode('utf-8')) + def _get_textview_text(self, tv): + buffer = tv.get_buffer() + begin, end = buffer.get_bounds() + return buffer.get_text(begin, end).decode('utf-8') + + def on_before_time_textview_focus_out_event(self, widget, event): + text = self._get_textview_text(widget) + text = helpers.to_one_line(text) + gajim.config.set('before_time', text) gajim.interface.save_config() - def on_after_time_entry_focus_out_event(self, widget, event): - gajim.config.set('after_time', widget.get_text().decode('utf-8')) + def on_after_time_textview_focus_out_event(self, widget, event): + text = self._get_textview_text(widget) + text = helpers.to_one_line(text) + gajim.config.set('after_time', text) gajim.interface.save_config() - def on_before_nickname_entry_focus_out_event(self, widget, event): - gajim.config.set('before_nickname', widget.get_text().decode('utf-8')) + def on_before_nickname_textview_focus_out_event(self, widget, event): + text = self._get_textview_text(widget) + text = helpers.to_one_line(text) + gajim.config.set('before_nickname', text) gajim.interface.save_config() - def on_after_nickname_entry_focus_out_event(self, widget, event): - gajim.config.set('after_nickname', widget.get_text().decode('utf-8')) + def on_after_nickname_textview_focus_out_event(self, widget, event): + text = self._get_textview_text(widget) + text = helpers.to_one_line(text) + gajim.config.set('after_nickname', text) gajim.interface.save_config() def update_text_tags(self): @@ -1064,6 +1089,13 @@ class PreferencesWindow: gajim.interface.instances['advanced_config'] = \ dialogs.AdvancedConfigurationWindow() + def set_status_msg_from_current_music_track_checkbutton_toggled(self, + widget): + self.on_checkbutton_toggled(widget, + 'set_status_msg_from_current_music_track') + gajim.interface.roster.enable_syncing_status_msg_from_current_music_track( + widget.get_active()) + #---------- AccountModificationWindow class -------------# class AccountModificationWindow: '''Class for account informations''' @@ -1097,11 +1129,12 @@ class AccountModificationWindow: '''set or unset sensitivity of widgets when widget is toggled''' for w in widgets: w.set_sensitive(widget.get_active()) - + def init_account_gpg(self): keyid = gajim.config.get_per('accounts', self.account, 'keyid') keyname = gajim.config.get_per('accounts', self.account, 'keyname') - savegpgpass = gajim.config.get_per('accounts', self.account,'savegpgpass') + savegpgpass = gajim.config.get_per('accounts', self.account, + 'savegpgpass') if not keyid or not gajim.config.get('usegpg'): return @@ -1358,8 +1391,7 @@ class AccountModificationWindow: gajim.events.change_account_name(self.account, name) # change account variable for chat / gc controls - for ctrl in gajim.interface.msg_win_mgr.get_controls(): - ctrl.account = name + gajim.interface.msg_win_mgr.change_account_name(self.account, name) # upgrade account variable in opened windows for kind in ('infos', 'disco', 'gc_config'): for j in gajim.interface.instances[name][kind]: @@ -1857,6 +1889,7 @@ class AccountsWindow: else: gajim.interface.roster.regroup = False gajim.interface.roster.draw_roster() + def on_enable_zeroconf_checkbutton_toggled(self, widget): if gajim.config.get('enable_zeroconf'): @@ -2987,9 +3020,6 @@ _('You can set advanced account options by pressing Advanced button, or later by con = connection.Connection(self.account) con.password = password - if not savepass: - password = "" - config = {} config['name'] = login config['hostname'] = server @@ -3018,6 +3048,10 @@ _('You can set advanced account options by pressing Advanced button, or later by def create_vars(self, config): gajim.config.add_per('accounts', self.account) + + if not config['savepass']: + config['password'] = '' + for opt in config: gajim.config.set_per('accounts', self.account, opt, config[opt]) diff --git a/src/conversation_textview.py b/src/conversation_textview.py index 888c9d2b3..daf1fc7c3 100644 --- a/src/conversation_textview.py +++ b/src/conversation_textview.py @@ -138,6 +138,9 @@ class ConversationTextview: self.focus_out_end_iter_offset = None self.line_tooltip = tooltips.BaseTooltip() + + path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png') + self.focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) def del_handlers(self): for i in self.handlers.keys(): @@ -230,19 +233,14 @@ class ConversationTextview: end_iter_for_previous_line) # add the new focus out line - # FIXME: Why is this loaded from disk everytime - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png') - focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) end_iter = buffer.get_end_iter() buffer.insert(end_iter, '\n') - buffer.insert_pixbuf(end_iter, focus_out_line_pixbuf) + buffer.insert_pixbuf(end_iter, self.focus_out_line_pixbuf) end_iter = buffer.get_end_iter() before_img_iter = end_iter.copy() before_img_iter.backward_char() # one char back (an image also takes one char) buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter) - #FIXME: remove this workaround when bug is fixed - # c http://bugzilla.gnome.org/show_bug.cgi?id=318569 self.allow_focus_out_line = False @@ -562,6 +560,7 @@ class ConversationTextview: img.show() #add with possible animation self.tv.add_child_at_anchor(img, anchor) + #FIXME: one day, somehow sync with regexp in gajim.py elif special_text.startswith('http://') or \ special_text.startswith('www.') or \ special_text.startswith('ftp://') or \ @@ -664,7 +663,9 @@ class ConversationTextview: current_print_time = gajim.config.get('print_time') if current_print_time == 'always' and kind != 'info': before_str = gajim.config.get('before_time') + before_str = helpers.from_one_line(before_str) after_str = gajim.config.get('after_time') + after_str = helpers.from_one_line(after_str) # get difference in days since epoch (86400 = 24*3600) # number of days since epoch for current time (in GMT) - # number of days since epoch for message (in GMT) @@ -748,7 +749,9 @@ class ConversationTextview: name_tags = other_tags_for_name[:] # create a new list name_tags.append(kind) before_str = gajim.config.get('before_nickname') + before_str = helpers.from_one_line(before_str) after_str = gajim.config.get('after_nickname') + after_str = helpers.from_one_line(after_str) format = before_str + name + after_str + ' ' buffer.insert_with_tags_by_name(end_iter, format, *name_tags) diff --git a/src/dbus_support.py b/src/dbus_support.py index 59e751c41..d4f7542a3 100644 --- a/src/dbus_support.py +++ b/src/dbus_support.py @@ -23,25 +23,17 @@ from common import exceptions try: import dbus - version = getattr(dbus, 'version', (0, 20, 0)) - supported = True + import dbus.service + import dbus.glib + supported = True # does use have D-Bus bindings? except ImportError: - version = (0, 0, 0) supported = False if not os.name == 'nt': # only say that to non Windows users print _('D-Bus python bindings are missing in this computer') print _('D-Bus capabilities of Gajim cannot be used') - -# dbus 0.23 leads to segfault with threads_init() -if sys.version[:4] >= '2.4' and version[1] < 30: - supported = False - -if version >= (0, 41, 0): - import dbus.service - import dbus.glib # cause dbus 0.35+ doesn't return signal replies without it class SessionBus: - '''A Singleton for the DBus SessionBus''' + '''A Singleton for the D-Bus SessionBus''' def __init__(self): self.session_bus = None diff --git a/src/dialogs.py b/src/dialogs.py index 6a8ec5de3..d6183ea8a 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -837,27 +837,31 @@ class FileChooserDialog(gtk.FileChooserDialog): self.set_current_folder(current_folder) else: self.set_current_folder(helpers.get_documents_path()) - - buttons = self.action_area.get_children() - possible_responses = {gtk.STOCK_OPEN: on_response_ok, - gtk.STOCK_SAVE: on_response_ok, - gtk.STOCK_CANCEL: on_response_cancel} - for b in buttons: - for response in possible_responses: - if b.get_label() == response: - if not possible_responses[response]: - b.connect('clicked', self.just_destroy) - elif isinstance(possible_responses[response], tuple): - if len(possible_responses[response]) == 1: - b.connect('clicked', possible_responses[response][0]) - else: - b.connect('clicked', *possible_responses[response]) - else: - b.connect('clicked', possible_responses[response]) - break - + self.response_ok, self.response_cancel = \ + on_response_ok, on_response_cancel + # in gtk+-2.10 clicked signal on some of the buttons in a dialog + # is emitted twice, so we cannot rely on 'clicked' signal + self.connect('response', self.on_dialog_response) self.show_all() + def on_dialog_response(self, dialog, response): + if response in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_CLOSE): + if self.response_cancel: + if isinstance(self.response_cancel, tuple): + self.response_cancel[0](dialog, *self.response_cancel[1:]) + else: + self.response_cancel(dialog) + else: + self.just_destroy(dialog) + elif response == gtk.RESPONSE_OK: + if self.response_ok: + if isinstance(self.response_ok, tuple): + self.response_ok[0](dialog, *self.response_ok[1:]) + else: + self.response_ok(dialog) + else: + self.just_destroy(dialog) + def just_destroy(self, widget): self.destroy() @@ -1192,7 +1196,7 @@ class NewChatDialog(InputDialog): title = _('Start Chat with account %s') % account else: title = _('Start Chat') - prompt_text = _('Fill in the jid, or nick of the contact you would like\nto send a chat message to:') + prompt_text = _('Fill in the nickname or the Jabber ID of the contact you would like\nto send a chat message to:') InputDialog.__init__(self, title, prompt_text, is_modal = False) self.completion_dict = {} @@ -1725,10 +1729,13 @@ class XMLConsoleWindow: self.input_textview.grab_focus() class PrivacyListWindow: - def __init__(self, account, privacy_list, list_type): - '''list_type can be 0 if list is created or 1 if it id edited''' + '''Window that is used for creating NEW or EDITING already there privacy + lists''' + def __init__(self, account, privacy_list_name, action): + '''action is 'edit' or 'new' depending on if we create a new priv list + or edit an already existing one''' self.account = account - self.privacy_list = privacy_list + self.privacy_list_name = privacy_list_name # Dicts and Default Values self.active_rule = '' @@ -1740,7 +1747,7 @@ class PrivacyListWindow: self.allow_deny = 'allow' # Connect to glade - self.xml = gtkgui_helpers.get_glade('privacy_list_edit_window.glade') + self.xml = gtkgui_helpers.get_glade('privacy_list_window.glade') self.window = self.xml.get_widget('privacy_list_edit_window') # Add Widgets @@ -1762,10 +1769,10 @@ class PrivacyListWindow: 'privacy_list_default_checkbutton']: self.__dict__[widget_to_add] = self.xml.get_widget(widget_to_add) - # Send translations + self.privacy_lists_title_label.set_label( _('Privacy List %s') % \ - gtkgui_helpers.escape_for_pango_markup(self.privacy_list)) + gtkgui_helpers.escape_for_pango_markup(self.privacy_list_name)) if len(gajim.connections) > 1: title = _('Privacy List for %s') % self.account @@ -1777,8 +1784,7 @@ class PrivacyListWindow: self.privacy_list_active_checkbutton.set_sensitive(False) self.privacy_list_default_checkbutton.set_sensitive(False) - # Check if list is created (0) or edited (1) - if list_type == 1: + if action == 'edit': self.refresh_rules() count = 0 @@ -1799,16 +1805,16 @@ class PrivacyListWindow: def on_privacy_list_edit_window_destroy(self, widget): '''close window''' if gajim.interface.instances[self.account].has_key('privacy_list_%s' % \ - self.privacy_list): + self.privacy_list_name): del gajim.interface.instances[self.account]['privacy_list_%s' % \ - self.privacy_list] + self.privacy_list_name] def check_active_default(self, a_d_dict): - if a_d_dict['active'] == self.privacy_list: + if a_d_dict['active'] == self.privacy_list_name: self.privacy_list_active_checkbutton.set_active(True) else: self.privacy_list_active_checkbutton.set_active(False) - if a_d_dict['default'] == self.privacy_list: + if a_d_dict['default'] == self.privacy_list_name: self.privacy_list_default_checkbutton.set_active(True) else: self.privacy_list_default_checkbutton.set_active(False) @@ -1845,7 +1851,7 @@ class PrivacyListWindow: gajim.connections[self.account].get_active_default_lists() def refresh_rules(self): - gajim.connections[self.account].get_privacy_list(self.privacy_list) + gajim.connections[self.account].get_privacy_list(self.privacy_list_name) def on_delete_rule_button_clicked(self, widget): tags = [] @@ -1854,7 +1860,7 @@ class PrivacyListWindow: self.list_of_rules_combobox.get_active_text().decode('utf-8'): tags.append(self.global_rules[rule]) gajim.connections[self.account].set_privacy_list( - self.privacy_list, tags) + self.privacy_list_name, tags) self.privacy_list_received(tags) self.add_edit_vbox.hide() @@ -1918,13 +1924,13 @@ class PrivacyListWindow: def on_privacy_list_active_checkbutton_toggled(self, widget): if widget.get_active(): - gajim.connections[self.account].set_active_list(self.privacy_list) + gajim.connections[self.account].set_active_list(self.privacy_list_name) else: gajim.connections[self.account].set_active_list(None) def on_privacy_list_default_checkbutton_toggled(self, widget): if widget.get_active(): - gajim.connections[self.account].set_default_list(self.privacy_list) + gajim.connections[self.account].set_default_list(self.privacy_list_name) else: gajim.connections[self.account].set_default_list(None) @@ -1994,7 +2000,7 @@ class PrivacyListWindow: else: tags.append(current_tags) - gajim.connections[self.account].set_privacy_list(self.privacy_list, tags) + gajim.connections[self.account].set_privacy_list(self.privacy_list_name, tags) self.privacy_list_received(tags) self.add_edit_vbox.hide() @@ -2019,7 +2025,9 @@ class PrivacyListWindow: self.add_edit_vbox.hide() class PrivacyListsWindow: -# To do: UTF-8 ??????? + '''Window that is the main window for Privacy Lists; + we can list there the privacy lists and ask to create a new one + or edit an already there one''' def __init__(self, account): self.account = account @@ -2027,7 +2035,7 @@ class PrivacyListsWindow: self.privacy_lists_save = [] - self.xml = gtkgui_helpers.get_glade('privacy_lists_first_window.glade') + self.xml = gtkgui_helpers.get_glade('privacy_lists_window.glade') self.window = self.xml.get_widget('privacy_lists_first_window') for widget_to_add in ['list_of_privacy_lists_combobox', @@ -2087,7 +2095,7 @@ class PrivacyListsWindow: self.list_of_privacy_lists_combobox.get_active()] gajim.connections[self.account].del_privacy_list(active_list) self.privacy_lists_save.remove(active_list) - self.privacy_lists_received({'lists':self.privacy_lists_save}) + self.privacy_lists_received({'lists': self.privacy_lists_save}) def privacy_lists_received(self, lists): if not lists: @@ -2107,7 +2115,7 @@ class PrivacyListsWindow: window.present() else: gajim.interface.instances[self.account]['privacy_list_%s' % name] = \ - PrivacyListWindow(self.account, name, 0) + PrivacyListWindow(self.account, name, 'new') self.new_privacy_list_entry.set_text('') def on_privacy_lists_refresh_button_clicked(self, widget): @@ -2122,7 +2130,7 @@ class PrivacyListsWindow: window.present() else: gajim.interface.instances[self.account]['privacy_list_%s' % name] = \ - PrivacyListWindow(self.account, name, 1) + PrivacyListWindow(self.account, name, 'edit') class InvitationReceivedDialog: def __init__(self, account, room_jid, contact_jid, password = None, comment = None): @@ -2291,6 +2299,18 @@ class ImageChooserDialog(FileChooserDialog): return widget.get_preview_widget().set_from_pixbuf(pixbuf) +class AvatarChooserDialog(ImageChooserDialog): + def __init__(self, path_to_file = '', on_response_ok = None, + on_response_cancel = None, on_response_clear = None): + ImageChooserDialog.__init__(self, path_to_file, on_response_ok, + on_response_cancel) + button = gtk.Button(None, gtk.STOCK_CLEAR) + if on_response_clear: + button.connect('clicked', on_response_clear) + button.show_all() + self.action_area.pack_start(button) + self.action_area.reorder_child(button, 0) + class AddSpecialNotificationDialog: def __init__(self, jid): '''jid is the jid for which we want to add special notification diff --git a/src/disco.py b/src/disco.py index f56f70a1c..e0f928f91 100644 --- a/src/disco.py +++ b/src/disco.py @@ -36,10 +36,10 @@ # - def update_actions(self) # - def default_action(self) # - def _find_item(self, jid, node) -# - def _add_item(self, model, jid, node, item, force) -# - def _update_item(self, model, iter, jid, node, item) -# - def _update_info(self, model, iter, jid, node, identities, features, data) -# - def _update_error(self, model, iter, jid, node) +# - def _add_item(self, jid, node, item, force) +# - def _update_item(self, iter, jid, node, item) +# - def _update_info(self, iter, jid, node, identities, features, data) +# - def _update_error(self, iter, jid, node) # # * Should call the super class for this method. # All others do not have to call back to the super class. (but can if they want @@ -215,8 +215,8 @@ class ServicesCache: ServiceCache instance.''' def __init__(self, account): self.account = account - self._items = CacheDictionary(15, getrefresh = False) - self._info = CacheDictionary(15, getrefresh = False) + self._items = CacheDictionary(1, getrefresh = True) + self._info = CacheDictionary(1, getrefresh = True) self._cbs = {} def _clean_closure(self, cb, type, addr): @@ -422,6 +422,7 @@ _('Without a connection, you can not browse available services')) self.xml = gtkgui_helpers.get_glade('service_discovery_window.glade') self.window = self.xml.get_widget('service_discovery_window') self.services_treeview = self.xml.get_widget('services_treeview') + self.model = None # This is more reliable than the cursor-changed signal. selection = self.services_treeview.get_selection() selection.connect_after('changed', @@ -452,7 +453,6 @@ _('Without a connection, you can not browse available services')) liststore = gtk.ListStore(str) self.address_comboboxentry.set_model(liststore) - self.address_comboboxentry.set_text_column(0) self.latest_addresses = gajim.config.get( 'latest_disco_addresses').split() if jid in self.latest_addresses: @@ -720,9 +720,9 @@ class AgentBrowser: note that the first two columns should ALWAYS be of type string and contain the JID and node of the item respectively.''' # JID, node, name, address - model = gtk.ListStore(str, str, str, str) - model.set_sort_column_id(3, gtk.SORT_ASCENDING) - self.window.services_treeview.set_model(model) + self.model = gtk.ListStore(str, str, str, str) + self.model.set_sort_column_id(3, gtk.SORT_ASCENDING) + self.window.services_treeview.set_model(self.model) # Name column col = gtk.TreeViewColumn(_('Name')) renderer = gtk.CellRendererText() @@ -740,7 +740,7 @@ class AgentBrowser: self.window.services_treeview.set_headers_visible(True) def _clean_treemodel(self): - self.window.services_treeview.get_model().clear() + self.model.clear() for col in self.window.services_treeview.get_columns(): self.window.services_treeview.remove_column(col) self.window.services_treeview.set_headers_visible(False) @@ -872,8 +872,7 @@ class AgentBrowser: def browse(self, force = False): '''Fill the treeview with agents, fetching the info if necessary.''' - model = self.window.services_treeview.get_model() - model.clear() + self.model.clear() self._total_items = self._progress = 0 self.window.progressbar.show() self._pulse_timeout = gobject.timeout_add(250, self._pulse_timeout_cb) @@ -890,21 +889,21 @@ class AgentBrowser: def _find_item(self, jid, node): '''Check if an item is already in the treeview. Return an iter to it if so, None otherwise.''' - model = self.window.services_treeview.get_model() - iter = model.get_iter_root() + iter = self.model.get_iter_root() while iter: - cjid = model.get_value(iter, 0).decode('utf-8') - cnode = model.get_value(iter, 1).decode('utf-8') + cjid = self.model.get_value(iter, 0).decode('utf-8') + cnode = self.model.get_value(iter, 1).decode('utf-8') if jid == cjid and node == cnode: break - iter = model.iter_next(iter) + iter = self.model.iter_next(iter) if iter: return iter return None def _agent_items(self, jid, node, items, force): '''Callback for when we receive a list of agent items.''' - model = self.window.services_treeview.get_model() + self.model.clear() + self._total_items = 0 gobject.source_remove(self._pulse_timeout) self.window.progressbar.hide() # The server returned an error @@ -916,53 +915,48 @@ class AgentBrowser: _('This service does not contain any items to browse.')) return # We got a list of items + self.window.services_treeview.set_model(None) for item in items: jid = item['jid'] node = item.get('node', '') - iter = self._find_item(jid, node) - if iter: - # Already in the treeview - self._update_item(model, iter, jid, node, item) - else: - # Not in the treeview - self._total_items += 1 - self._add_item(model, jid, node, item, force) + self._total_items += 1 + self._add_item(jid, node, item, force) + self.window.services_treeview.set_model(self.model) def _agent_info(self, jid, node, identities, features, data): '''Callback for when we receive info about an agent's item.''' addr = get_agent_address(jid, node) - model = self.window.services_treeview.get_model() iter = self._find_item(jid, node) if not iter: # Not in the treeview, stop return if identities == 0: # The server returned an error - self._update_error(model, iter, jid, node) + self._update_error(iter, jid, node) else: # We got our info - self._update_info(model, iter, jid, node, + self._update_info(iter, jid, node, identities, features, data) self.update_actions() - def _add_item(self, model, jid, node, item, force): + def _add_item(self, jid, node, item, force): '''Called when an item should be added to the model. The result of a disco#items query.''' - model.append((jid, node, item.get('name', ''), + self.model.append((jid, node, item.get('name', ''), get_agent_address(jid, node))) - def _update_item(self, model, iter, jid, node, item): + def _update_item(self, iter, jid, node, item): '''Called when an item should be updated in the model. The result of a disco#items query. (seldom)''' if item.has_key('name'): - model[iter][2] = item['name'] + self.model[iter][2] = item['name'] - def _update_info(self, model, iter, jid, node, identities, features, data): + def _update_info(self, iter, jid, node, identities, features, data): '''Called when an item should be updated in the model with further info. The result of a disco#info query.''' - model[iter][2] = identities[0].get('name', '') + self.model[iter][2] = identities[0].get('name', '') - def _update_error(self, model, iter, jid, node): + def _update_error(self, iter, jid, node): '''Called when a disco#info query failed for an item.''' pass @@ -1046,14 +1040,12 @@ class ToplevelAgentBrowser(AgentBrowser): # These are all callbacks to make tooltips work def on_treeview_leave_notify_event(self, widget, event): - model = widget.get_model() props = widget.get_path_at_pos(int(event.x), int(event.y)) if self.tooltip.timeout > 0: if not props or self.tooltip.id == props[0]: self.tooltip.hide_tooltip() def on_treeview_motion_notify_event(self, widget, event): - model = widget.get_model() props = widget.get_path_at_pos(int(event.x), int(event.y)) if self.tooltip.timeout > 0: if not props or self.tooltip.id != props[0]: @@ -1062,12 +1054,12 @@ class ToplevelAgentBrowser(AgentBrowser): [row, col, x, y] = props iter = None try: - iter = model.get_iter(row) + iter = self.model.get_iter(row) except: self.tooltip.hide_tooltip() return - jid = model[iter][0] - state = model[iter][4] + jid = self.model[iter][0] + state = self.model[iter][4] # Not a category, and we have something to say about state if jid and state > 0 and \ (self.tooltip.timeout == 0 or self.tooltip.id != props[0]): @@ -1084,10 +1076,10 @@ class ToplevelAgentBrowser(AgentBrowser): # JID, node, icon, description, state # State means 2 when error, 1 when fetching, 0 when succes. view = self.window.services_treeview - model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int) - model.set_sort_func(4, self._treemodel_sort_func) - model.set_sort_column_id(4, gtk.SORT_ASCENDING) - view.set_model(model) + self.model = gtk.TreeStore(str, str, gtk.gdk.Pixbuf, str, int) + self.model.set_sort_func(4, self._treemodel_sort_func) + self.model.set_sort_column_id(4, gtk.SORT_ASCENDING) + view.set_model(self.model) col = gtk.TreeViewColumn() # Icon Renderer @@ -1329,41 +1321,38 @@ class ToplevelAgentBrowser(AgentBrowser): def _create_category(self, cat, type=None): '''Creates a category row.''' - model = self.window.services_treeview.get_model() cat, prio = self._friendly_category(cat, type) - return model.append(None, ('', '', None, cat, prio)) + return self.model.append(None, ('', '', None, cat, prio)) def _find_category(self, cat, type=None): '''Looks up a category row and returns the iterator to it, or None.''' - model = self.window.services_treeview.get_model() cat, prio = self._friendly_category(cat, type) - iter = model.get_iter_root() + iter = self.model.get_iter_root() while iter: - if model.get_value(iter, 3).decode('utf-8') == cat: + if self.model.get_value(iter, 3).decode('utf-8') == cat: break - iter = model.iter_next(iter) + iter = self.model.iter_next(iter) if iter: return iter return None def _find_item(self, jid, node): - model = self.window.services_treeview.get_model() iter = None - cat_iter = model.get_iter_root() + cat_iter = self.model.get_iter_root() while cat_iter and not iter: - iter = model.iter_children(cat_iter) + iter = self.model.iter_children(cat_iter) while iter: - cjid = model.get_value(iter, 0).decode('utf-8') - cnode = model.get_value(iter, 1).decode('utf-8') + cjid = self.model.get_value(iter, 0).decode('utf-8') + cnode = self.model.get_value(iter, 1).decode('utf-8') if jid == cjid and node == cnode: break - iter = model.iter_next(iter) - cat_iter = model.iter_next(cat_iter) + iter = self.model.iter_next(iter) + cat_iter = self.model.iter_next(cat_iter) if iter: return iter return None - def _add_item(self, model, jid, node, item, force): + def _add_item(self, jid, node, item, force): # Row text addr = get_agent_address(jid, node) if item.has_key('name'): @@ -1387,21 +1376,21 @@ class ToplevelAgentBrowser(AgentBrowser): cat = self._find_category(*cat_args) if not cat: cat = self._create_category(*cat_args) - model.append(cat, (item['jid'], item.get('node', ''), pix, descr, 1)) + self.model.append(cat, (item['jid'], item.get('node', ''), pix, descr, 1)) self._expand_all() # Grab info on the service self.cache.get_info(jid, node, self._agent_info, force = force) self._update_progressbar() - def _update_item(self, model, iter, jid, node, item): + def _update_item(self, iter, jid, node, item): addr = get_agent_address(jid, node) if item.has_key('name'): descr = "%s\n%s" % (item['name'], addr) else: descr = "%s" % addr - model[iter][3] = descr + self.model[iter][3] = descr - def _update_info(self, model, iter, jid, node, identities, features, data): + def _update_info(self, iter, jid, node, identities, features, data): addr = get_agent_address(jid, node) name = identities[0].get('name', '') if name: @@ -1423,32 +1412,32 @@ class ToplevelAgentBrowser(AgentBrowser): break # Check if we have to move categories - old_cat_iter = model.iter_parent(iter) - old_cat = model.get_value(old_cat_iter, 3).decode('utf-8') - if model.get_value(old_cat_iter, 3) == cat: + old_cat_iter = self.model.iter_parent(iter) + old_cat = self.model.get_value(old_cat_iter, 3).decode('utf-8') + if self.model.get_value(old_cat_iter, 3) == cat: # Already in the right category, just update - model[iter][2] = pix - model[iter][3] = descr - model[iter][4] = 0 + self.model[iter][2] = pix + self.model[iter][3] = descr + self.model[iter][4] = 0 return # Not in the right category, move it. - model.remove(iter) + self.model.remove(iter) # Check if the old category is empty - if not model.iter_is_valid(old_cat_iter): + if not self.model.iter_is_valid(old_cat_iter): old_cat_iter = self._find_category(old_cat) - if not model.iter_children(old_cat_iter): - model.remove(old_cat_iter) + if not self.model.iter_children(old_cat_iter): + self.model.remove(old_cat_iter) cat_iter = self._find_category(cat, type) if not cat_iter: cat_iter = self._create_category(cat, type) - model.append(cat_iter, (jid, node, pix, descr, 0)) + self.model.append(cat_iter, (jid, node, pix, descr, 0)) self._expand_all() - def _update_error(self, model, iter, jid, node): + def _update_error(self, iter, jid, node): addr = get_agent_address(jid, node) - model[iter][4] = 2 + self.model[iter][4] = 2 self._progress += 1 self._update_progressbar() @@ -1462,11 +1451,13 @@ class MucBrowser(AgentBrowser): # JID, node, name, users, description, fetched # This is rather long, I'd rather not use a data_func here though. # Users is a string, because want to be able to leave it empty. - model = gtk.ListStore(str, str, str, str, str, bool) - model.set_sort_column_id(2, gtk.SORT_ASCENDING) - self.window.services_treeview.set_model(model) + self.model = gtk.ListStore(str, str, str, str, str, bool) + self.model.set_sort_column_id(2, gtk.SORT_ASCENDING) + self.window.services_treeview.set_model(self.model) # Name column col = gtk.TreeViewColumn(_('Name')) + col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) + col.set_fixed_width(100) renderer = gtk.CellRendererText() col.pack_start(renderer) col.set_attributes(renderer, text = 2) @@ -1486,6 +1477,13 @@ class MucBrowser(AgentBrowser): col.set_attributes(renderer, text = 4) self.window.services_treeview.insert_column(col, -1) col.set_resizable(True) + # Id column + col = gtk.TreeViewColumn(_('Id')) + renderer = gtk.CellRendererText() + col.pack_start(renderer) + col.set_attributes(renderer, text = 0) + self.window.services_treeview.insert_column(col, -1) + col.set_resizable(True) self.window.services_treeview.set_headers_visible(True) # Source id for idle callback used to start disco#info queries. self._fetch_source = None @@ -1568,7 +1566,6 @@ class MucBrowser(AgentBrowser): # Prevent a silly warning, try again in a bit. self._fetch_source = gobject.timeout_add(100, self._start_info_query) return - model = view.get_model() # We have to do this in a pygtk <2.8 compatible way :/ #start, end = self.window.services_treeview.get_visible_range() rect = view.get_visible_rect() @@ -1577,7 +1574,7 @@ class MucBrowser(AgentBrowser): try: sx, sy = view.tree_to_widget_coords(rect.x, rect.y) spath = view.get_path_at_pos(sx, sy)[0] - iter = model.get_iter(spath) + iter = self.model.get_iter(spath) except TypeError: self._fetch_source = None return @@ -1591,14 +1588,14 @@ class MucBrowser(AgentBrowser): except TypeError: # We're at the end of the model, we can leave end=None though. pass - while iter and model.get_path(iter) != end: - if not model.get_value(iter, 5): - jid = model.get_value(iter, 0).decode('utf-8') - node = model.get_value(iter, 1).decode('utf-8') + while iter and self.model.get_path(iter) != end: + if not self.model.get_value(iter, 5): + jid = self.model.get_value(iter, 0).decode('utf-8') + node = self.model.get_value(iter, 1).decode('utf-8') self.cache.get_info(jid, node, self._agent_info) self._fetch_source = True return - iter = model.iter_next(iter) + iter = self.model.iter_next(iter) self._fetch_source = None def _channel_altinfo(self, jid, node, items, name = None): @@ -1619,22 +1616,21 @@ class MucBrowser(AgentBrowser): self._fetch_source = None return else: - model = self.window.services_treeview.get_model() iter = self._find_item(jid, node) if iter: if name: - model[iter][2] = name - model[iter][3] = len(items) # The number of users - model[iter][5] = True + self.model[iter][2] = name + self.model[iter][3] = len(items) # The number of users + self.model[iter][5] = True self._fetch_source = None self._query_visible() - def _add_item(self, model, jid, node, item, force): - model.append((jid, node, item.get('name', ''), '', '', False)) + def _add_item(self, jid, node, item, force): + self.model.append((jid, node, item.get('name', ''), '', '', False)) if not self._fetch_source: self._fetch_source = gobject.idle_add(self._start_info_query) - def _update_info(self, model, iter, jid, node, identities, features, data): + def _update_info(self, iter, jid, node, identities, features, data): name = identities[0].get('name', '') for form in data: typefield = form.getField('FORM_TYPE') @@ -1644,14 +1640,14 @@ class MucBrowser(AgentBrowser): users = form.getField('muc#roominfo_occupants') descr = form.getField('muc#roominfo_description') if users: - model[iter][3] = users.getValue() + self.model[iter][3] = users.getValue() if descr: - model[iter][4] = descr.getValue() + self.model[iter][4] = descr.getValue() # Only set these when we find a form with additional info # Some servers don't support forms and put extra info in # the name attribute, so we preserve it in that case. - model[iter][2] = name - model[iter][5] = True + self.model[iter][2] = name + self.model[iter][5] = True break else: # We didn't find a form, switch to alternate query mode @@ -1661,7 +1657,7 @@ class MucBrowser(AgentBrowser): self._fetch_source = None self._query_visible() - def _update_error(self, model, iter, jid, node): + def _update_error(self, iter, jid, node): # switch to alternate query mode self.cache.get_items(jid, node, self._channel_altinfo) diff --git a/src/filetransfers_window.py b/src/filetransfers_window.py index 0e31e8a4e..3560d784c 100644 --- a/src/filetransfers_window.py +++ b/src/filetransfers_window.py @@ -246,11 +246,16 @@ _('Connection with peer cannot be established.')) gtk.RESPONSE_OK, True, # select multiple true as we can select many files to send gajim.config.get('last_send_dir'), + on_response_ok = on_ok, + on_response_cancel = lambda e:dialog.destroy() ) - btn = dialog.add_button(_('_Send'), gtk.RESPONSE_OK) - btn.set_use_stock(True) # FIXME: add send icon to this button (JUMP_TO) - btn.connect('clicked', on_ok) + btn = gtk.Button(_('_Send')) + btn.set_property('can-default', True) + # FIXME: add send icon to this button (JUMP_TO) + dialog.add_action_widget(btn, gtk.RESPONSE_OK) + dialog.set_default_response(gtk.RESPONSE_OK) + btn.show() def send_file(self, account, contact, file_path): ''' start the real transfer(upload) of the file ''' @@ -450,8 +455,10 @@ _('Connection with peer cannot be established.')) for ev_type in ('file-error', 'file-completed', 'file-request-error', 'file-send-error', 'file-stopped'): for event in gajim.events.get_events(account, jid, [ev_type]): - if event.parameters[1]['sid'] == file_props['sid']: + if event.parameters['sid'] == file_props['sid']: gajim.events.remove_events(account, jid, event) + gajim.interface.roster.draw_contact(jid, account) + gajim.interface.roster.show_title() del(self.files_props[sid[0]][sid[1:]]) del(file_props) diff --git a/src/gajim-remote.py b/src/gajim-remote.py index 563cc121a..ffe9db7fc 100755 --- a/src/gajim-remote.py +++ b/src/gajim-remote.py @@ -51,13 +51,10 @@ def send_error(error_message): try: import dbus -except: - raise exceptions.DbusNotSupported - -_version = getattr(dbus, 'version', (0, 20, 0)) -if _version[1] >= 41: import dbus.service import dbus.glib +except: + raise exceptions.DbusNotSupported OBJ_PATH = '/org/gajim/dbus/RemoteObject' INTERFACE = 'org.gajim.dbus.RemoteInterface' @@ -320,14 +317,8 @@ class GajimRemote: except: raise exceptions.SessionBusNotPresent - if _version[1] >= 30: - obj = self.sbus.get_object(SERVICE, OBJ_PATH) - interface = dbus.Interface(obj, INTERFACE) - elif _version[1] < 30: - self.service = self.sbus.get_service(SERVICE) - interface = self.service.get_object(OBJ_PATH, INTERFACE) - else: - send_error(_('Unknown D-Bus version: %s') % _version[1]) + obj = self.sbus.get_object(SERVICE, OBJ_PATH) + interface = dbus.Interface(obj, INTERFACE) # get the function asked self.method = interface.__getattr__(self.command) @@ -447,10 +438,7 @@ class GajimRemote: ''' calls self.method with arguments from sys.argv[2:] ''' args = sys.argv[2:] args = [i.decode(PREFERRED_ENCODING) for i in sys.argv[2:]] - if _version[1] >= 41: - args = [dbus.String(i) for i in args] - else: - args = [i.encode('UTF-8') for i in sys.argv[2:]] + args = [dbus.String(i) for i in args] try: res = self.method(*args) return res diff --git a/src/gajim.py b/src/gajim.py index b8e96dd63..70d8bfe54 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -552,7 +552,7 @@ class Interface: chat_control = self.msg_win_mgr.get_control(jid, account) # Handle chat states - contact = gajim.contacts.get_contact(account, jid, resource) + contact = gajim.contacts.get_contact(account, jid) if contact and isinstance(contact, list): contact = contact[0] if contact: @@ -582,7 +582,10 @@ class Interface: if gajim.config.get('ignore_unknown_contacts') and \ not gajim.contacts.get_contact(account, jid) and not pm: return - + if not contact: + # contact is not in the roster, create a fake one to display + # notification + contact = common.contacts.Contact(jid = jid, resource = resource) advanced_notif_num = notify.get_advanced_notification('message_received', account, contact) @@ -606,7 +609,7 @@ class Interface: msg = message if subject: msg = _('Subject: %s') % subject + '\n' + msg - notify.notify('new_message', jid, account, [msg_type, first, nickname, + notify.notify('new_message', full_jid_with_resource, account, [msg_type, first, nickname, msg], advanced_notif_num) if self.remote_ctrl: @@ -851,6 +854,7 @@ class Interface: self.remote_ctrl.raise_signal('LastStatusTime', (account, array)) def handle_event_os_info(self, account, array): + #'OS_INFO' (account, (jid, resource, client_info, os_info)) win = None if self.instances[account]['infos'].has_key(array[0]): win = self.instances[account]['infos'][array[0]] @@ -1010,7 +1014,7 @@ class Interface: return # Add it to roster contact = gajim.contacts.create_contact(jid = jid, name = name, - groups = groups, show = 'offline', sub = sub, ask = ask) + groups = groups, show = 'offline', sub = sub, ask = ask) gajim.contacts.add_contact(account, contact) self.roster.add_contact_to_roster(jid, account) else: @@ -1075,7 +1079,7 @@ class Interface: gmail_messages_list = array[2] if gajim.config.get('notify_on_new_gmail_email'): img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', - 'single_msg_recv.png') #FIXME: find a better image + 'new_email_recv.png') title = _('New E-mail on %(gmail_mail_address)s') % \ {'gmail_mail_address': jid} text = i18n.ngettext('You have %d new E-mail message', 'You have %d new E-mail messages', gmail_new_messages, gmail_new_messages, gmail_new_messages) @@ -1328,7 +1332,9 @@ class Interface: self.instances[account]['xml_console'].print_stanza(stanza, 'outgoing') def handle_event_vcard_published(self, account, array): - dialogs.InformationDialog(_('vCard publication succeeded'), _('Your personal information has been published successfully.')) + if self.instances[account].has_key('profile'): + win = self.instances[account]['profile'] + win.vcard_published() for gc_control in self.msg_win_mgr.get_controls(message_control.TYPE_GC): if gc_control.account == account: show = gajim.SHOW_LIST[gajim.connections[account].connected] @@ -1337,7 +1343,9 @@ class Interface: gc_control.room_jid, show, status) def handle_event_vcard_not_published(self, account, array): - dialogs.InformationDialog(_('vCard publication failed'), _('There was an error while publishing your personal information, try again later.')) + if self.instances[account].has_key('profile'): + win = self.instances[account]['profile'] + win.vcard_not_published() def handle_event_signed_in(self, account, empty): '''SIGNED_IN event is emitted when we sign in, so handle it''' @@ -1406,10 +1414,10 @@ class Interface: if response == gtk.RESPONSE_OK: new_name = dlg.input_entry.get_text() print 'account, data', account, data, new_name - gajim.config.set_per('accounts', gajim.LOCAL_ACC, 'name', new_name) + gajim.config.set_per('accounts', account, 'name', new_name) status = gajim.connections[account].status - print 'status', status - gajim.connections[account].reconnect() + gajim.connections[account].username = new_name + gajim.connections[account].change_status(status, '') def read_sleepy(self): @@ -1744,14 +1752,17 @@ class Interface: jid = gajim.get_jid_without_resource(jid) if type_ in ('printed_gc_msg', 'gc_msg'): w = self.msg_win_mgr.get_window(jid, account) - elif type_ in ('printed_chat', 'chat'): + elif type_ in ('printed_chat', 'chat', ''): + # '' is for log in/out notifications if self.msg_win_mgr.has_window(fjid, account): w = self.msg_win_mgr.get_window(fjid, account) + elif self.msg_win_mgr.has_window(jid, account): + w = self.msg_win_mgr.get_window(jid, account) else: contact = gajim.contacts.get_contact(account, jid, resource) - if isinstance(contact, list): + if not contact or isinstance(contact, list): contact = gajim.contacts.get_first_contact_from_jid(account, jid) - self.roster.new_chat(contact, account, resource = resource) + self.roster.new_chat(contact, account) w = self.msg_win_mgr.get_window(fjid, account) gajim.last_message_time[account][jid] = 0 # long time ago elif type_ in ('printed_pm', 'pm'): @@ -1774,9 +1785,15 @@ class Interface: elif type_ in ('normal', 'file-request', 'file-request-error', 'file-send-error', 'file-error', 'file-stopped', 'file-completed'): # Get the first single message event - event = gajim.events.get_first_event(account, jid, type_) - # Open the window - self.roster.open_event(account, jid, event) + event = gajim.events.get_first_event(account, fjid, type_) + if not event: + # default to jid without resource + event = gajim.events.get_first_event(account, jid, type_) + # Open the window + self.roster.open_event(account, jid, event) + else: + # Open the window + self.roster.open_event(account, fjid, event) elif type_ == 'gmail': if gajim.config.get_per('accounts', account, 'savepass'): url = ('http://www.google.com/accounts/ServiceLoginAuth?service=mail&Email=%s&Passwd=%s&continue=https://mail.google.com/mail') %\ @@ -1873,9 +1890,12 @@ class Interface: for account in gajim.config.get_per('accounts'): if account != gajim.ZEROCONF_ACC_NAME: gajim.connections[account] = common.connection.Connection(account) - + + # gtk hooks gtk.about_dialog_set_email_hook(self.on_launch_browser_mailer, 'mail') gtk.about_dialog_set_url_hook(self.on_launch_browser_mailer, 'url') + if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0): + gtk.link_button_set_uri_hook(self.on_launch_browser_mailer, 'url') self.instances = {'logs': {}} @@ -1919,6 +1939,8 @@ class Interface: self.systray_capabilities = False if os.name == 'nt': + pass + ''' try: import systraywin32 except: # user doesn't have trayicon capabilities @@ -1926,6 +1948,7 @@ class Interface: else: self.systray_capabilities = True self.systray = systraywin32.SystrayWin32() + ''' else: self.systray_capabilities = systray.HAS_SYSTRAY_CAPABILITIES if self.systray_capabilities: diff --git a/src/gajim_themes_window.py b/src/gajim_themes_window.py index cc9b1742e..ef40d4b22 100644 --- a/src/gajim_themes_window.py +++ b/src/gajim_themes_window.py @@ -51,7 +51,7 @@ class GajimThemesWindow: self.themes_tree = self.xml.get_widget('themes_treeview') self.theme_options_vbox = self.xml.get_widget('theme_options_vbox') self.colorbuttons = {} - for chatstate in ('active', 'inactive', 'composing', 'paused', 'gone', + for chatstate in ('inactive', 'composing', 'paused', 'gone', 'muc_msg', 'muc_directed_msg'): self.colorbuttons[chatstate] = self.xml.get_widget(chatstate + \ '_colorbutton') @@ -198,7 +198,7 @@ class GajimThemesWindow: self.no_update = False gajim.interface.roster.change_roster_style(None) - for chatstate in ('active', 'inactive', 'composing', 'paused', 'gone', + for chatstate in ('inactive', 'composing', 'paused', 'gone', 'muc_msg', 'muc_directed_msg'): color = gajim.config.get_per('themes', theme, 'state_' + chatstate + \ '_color') @@ -333,11 +333,6 @@ class GajimThemesWindow: font_props[1] = True return font_props - def on_active_colorbutton_color_set(self, widget): - self.no_update = True - self._set_color(True, widget, 'state_active_color') - self.no_update = False - def on_inactive_colorbutton_color_set(self, widget): self.no_update = True self._set_color(True, widget, 'state_inactive_color') diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 38e20e5bf..607053d48 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -226,9 +226,6 @@ class GroupchatControl(ChatControlBase): self.gc_popup_menu = xm.get_widget('gc_control_popup_menu') self.name_label = self.xml.get_widget('banner_name_label') - id = self.parent_win.window.connect('focus-in-event', - self._on_window_focus_in_event) - self.handlers[id] = self.parent_win.window # set the position of the current hpaned self.hpaned_position = gajim.config.get('gc-hpaned-position') @@ -320,11 +317,6 @@ class GroupchatControl(ChatControlBase): return gajim.config.get('notify_on_all_muc_messages') or \ self.attention_flag - def _on_window_focus_in_event(self, widget, event): - '''When window gets focus''' - if self.parent_win.get_active_jid() == self.room_jid: - self.conv_textview.allow_focus_out_line = True - def on_treeview_size_allocate(self, widget, allocation): '''The MUC treeview has resized. Move the hpaned in all tabs to match''' self.hpaned_position = self.hpaned.get_position() @@ -371,23 +363,24 @@ class GroupchatControl(ChatControlBase): has_focus = self.parent_win.window.get_property('has-toplevel-focus') current_tab = self.parent_win.get_active_control() == self + color_name = None color = None theme = gajim.config.get('roster_theme') if chatstate == 'attention' and (not has_focus or not current_tab): self.attention_flag = True - color = gajim.config.get_per('themes', theme, + color_name = gajim.config.get_per('themes', theme, 'state_muc_directed_msg_color') elif chatstate: if chatstate == 'active' or (current_tab and has_focus): self.attention_flag = False - color = gajim.config.get_per('themes', theme, - 'state_active_color') + # get active color from gtk + color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE] elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\ not self.attention_flag: - color = gajim.config.get_per('themes', theme, 'state_muc_msg_color') - if color: - color = gtk.gdk.colormap_get_system().alloc_color(color) - + color_name = gajim.config.get_per('themes', theme, 'state_muc_msg_color') + if color_name: + color = gtk.gdk.colormap_get_system().alloc_color(color_name) + label_str = self.name return (label_str, color) @@ -865,7 +858,9 @@ class GroupchatControl(ChatControlBase): print_status = gajim.config.get('print_status_in_muc') nick_jid = nick if jid: - nick_jid += ' (%s)' % jid + # delete ressource + simple_jid = gajim.get_jid_without_resource(jid) + nick_jid += ' (%s)' % simple_jid if show == 'offline' and print_status in ('all', 'in_and_out'): st = _('%s has left') % nick_jid if reason: @@ -1268,6 +1263,10 @@ class GroupchatControl(ChatControlBase): del self.handlers[i] def allow_shutdown(self): + model, iter = self.list_treeview.get_selection().get_selected() + if iter: + self.list_treeview.get_selection().unselect_all() + return False retval = True includes = gajim.config.get('confirm_close_muc_rooms').split(' ') excludes = gajim.config.get('noconfirm_close_muc_rooms').split(' ') @@ -1293,6 +1292,7 @@ class GroupchatControl(ChatControlBase): return retval def set_control_active(self, state): + self.conv_textview.allow_focus_out_line = True self.attention_flag = False ChatControlBase.set_control_active(self, state) if not state: @@ -1453,7 +1453,11 @@ class GroupchatControl(ChatControlBase): def on_list_treeview_key_press_event(self, widget, event): if event.keyval == gtk.keysyms.Escape: - widget.get_selection().unselect_all() + selection = widget.get_selection() + model, iter = selection.get_selected() + if iter: + widget.get_selection().unselect_all() + return True def on_list_treeview_row_expanded(self, widget, iter, path): '''When a row is expanded: change the icon of the arrow''' diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py index fb6e3eaa8..7b99c3ae9 100644 --- a/src/gtkgui_helpers.py +++ b/src/gtkgui_helpers.py @@ -390,7 +390,7 @@ def possibly_move_window_in_current_desktop(window): current virtual desktop window is GTK window''' if os.name == 'nt': - return + return False root_window = gtk.gdk.screen_get_default().get_root_window() # current user's vd @@ -406,6 +406,8 @@ def possibly_move_window_in_current_desktop(window): # we are in another VD that the window was # so show it in current VD window.present() + return True + return False def file_is_locked(path_to_file): '''returns True if file is locked (WINDOWS ONLY)''' @@ -680,6 +682,14 @@ default_name = ''): file_path = dialog.get_filename() file_path = decode_filechooser_file_paths((file_path,))[0] if os.path.exists(file_path): + # check if we have write permissions + if not os.access(file_path, os.W_OK): + file_name = os.path.basename(file_path) + dialogs.ErrorDialog(_('Cannot overwrite existing file "%s"' % + file_name), + _('A file with this name already exists and you do not have ' + 'permission to overwrite it.')) + return dialog2 = dialogs.FTOverwriteConfirmationDialog( _('This file already exists'), _('What do you want to do?'), False) @@ -688,6 +698,13 @@ default_name = ''): response = dialog2.get_response() if response < 0: return + else: + dirname = os.path.dirname(file_path) + if not os.access(dirname, os.W_OK): + dialogs.ErrorDialog(_('Directory "%s" is not writable') % \ + dirname, _('You do not have permission to create files in this' + ' directory.')) + return # Get pixbuf pixbuf = None @@ -710,8 +727,8 @@ default_name = ''): try: pixbuf.save(file_path, type_) except: - #XXX Check for permissions - os.remove(file_path) + if os.path.exists(file_path): + os.remove(file_path) new_file_path = '.'.join(file_path.split('.')[:-1]) + '.jpeg' dialog2 = dialogs.ConfirmationDialog(_('Extension not supported'), _('Image cannot be saved in %(type)s format. Save as %(new_filename)s?') % {'type': type_, 'new_filename': new_file_path}, @@ -735,3 +752,6 @@ default_name = ''): dialog.set_current_name(default_name) dialog.connect('delete-event', lambda widget, event: on_cancel(widget)) + +def on_bm_header_changed_state(widget, event): + widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state diff --git a/src/message_window.py b/src/message_window.py index f190956c8..42be57e1c 100644 --- a/src/message_window.py +++ b/src/message_window.py @@ -104,6 +104,16 @@ class MessageWindow: self.notebook.drag_dest_set(gtk.DEST_DEFAULT_ALL, self.DND_TARGETS, gtk.gdk.ACTION_MOVE) + def change_account_name(self, old_name, new_name): + if self._controls.has_key(old_name): + self._controls[new_name] = self._controls[old_name] + del self._controls[old_name] + for ctrl in self.controls(): + if ctrl.account == old_name: + ctrl.account = new_name + if self.account == old_name: + self.account = new_name + def get_num_controls(self): n = 0 for dict in self._controls.values(): @@ -618,7 +628,11 @@ class MessageWindowMgr: # Map the mode to a int constant for frequent compares mode = gajim.config.get('one_message_window') self.mode = common.config.opt_one_window_types.index(mode) - + + def change_account_name(self, old_name, new_name): + for win in self.windows(): + win.change_account_name(old_name, new_name) + def _new_window(self, acct, type): win = MessageWindow(acct, type) # we track the lifetime of this window diff --git a/src/music_track_listener.py b/src/music_track_listener.py new file mode 100644 index 000000000..b5bccbdc4 --- /dev/null +++ b/src/music_track_listener.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +## musictracklistener.py +## +## Copyright (C) 2006 Gustavo Carneiro +## Copyright (C) 2006 Nikos Kouremenos +## +## 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; version 2 only. +## +## 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. +## +import gobject +import dbus_support +if dbus_support.supported: + import dbus + import dbus.glib + +class MusicTrackInfo(object): + __slots__ = ['title', 'album', 'artist', 'duration', 'track_number'] + + +class MusicTrackListener(gobject.GObject): + __gsignals__ = { 'music-track-changed': (gobject.SIGNAL_RUN_LAST, None, + (object,)) } + + _instance = None + @classmethod + def get(cls): + if cls._instance is None: + cls._instance = cls() + return cls._instance + + def __init__(self): + super(MusicTrackListener, self).__init__() + bus = dbus.SessionBus() + bus.add_signal_receiver(self._muine_music_track_change_cb, 'SongChanged', + 'org.gnome.Muine.Player') + bus.add_signal_receiver(self._rhythmbox_music_track_change_cb, + 'playingUriChanged', 'org.gnome.Rhythmbox.Player') + + def _muine_properties_extract(self, song_string): + d = dict((x.strip() for x in s1.split(':', 1)) for s1 in song_string.split('\n')) + info = MusicTrackInfo() + info.title = d['title'] + info.album = d['album'] + info.artist = d['artist'] + info.duration = int(d['duration']) + info.track_number = int(d['track_number']) + return info + + def _muine_music_track_change_cb(self, arg): + info = self._muine_properties_extract(arg) + self.emit('music-track-changed', info) + + def _rhythmbox_properties_extract(self, props): + info = MusicTrackInfo() + info.title = props['title'] + info.album = props['album'] + info.artist = props['artist'] + info.duration = int(props['duration']) + info.track_number = int(props['track-number']) + return info + + def _rhythmbox_music_track_change_cb(self, uri): + bus = dbus.SessionBus() + rbshellobj = bus.get_object('org.gnome.Rhythmbox', '/org/gnome/Rhythmbox/Shell') + rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell') + props = rbshell.getSongProperties(uri) + info = self._rhythmbox_properties_extract(props) + self.emit('music-track-changed', info) + + def get_playing_track(self): + '''Return a MusicTrackInfo for the currently playing + song, or None if no song is playing''' + + bus = dbus.SessionBus() + + ## Check Muine playing track + if dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), + 'org.gnome.Muine'): + obj = bus.get_object('org.gnome.Muine', '/org/gnome/Muine/Player') + player = dbus.Interface(obj, 'org.gnome.Muine.Player') + if player.GetPlaying(): + song_string = player.GetCurrentSong() + song = self._muine_properties_extract(song_string) + return song + + ## Check Rhythmbox playing song + if dbus.dbus_bindings.bus_name_has_owner(bus.get_connection(), + 'org.gnome.Rhythmbox'): + rbshellobj = bus.get_object('org.gnome.Rhythmbox', '/org/gnome/Rhythmbox/Shell') + player = dbus.Interface( + bus.get_object('org.gnome.Rhythmbox', '/org/gnome/Rhythmbox/Player'), + 'org.gnome.Rhythmbox.Player') + rbshell = dbus.Interface(rbshellobj, 'org.gnome.Rhythmbox.Shell') + uri = player.getPlayingUri() + props = rbshell.getSongProperties(uri) + info = self._rhythmbox_properties_extract(props) + return info + + return None + +# here we test :) +if __name__ == '__main__': + def music_track_change_cb(listener, music_track_info): + print music_track_info.title + listener = MusicTrackListener.get() + listener.connect('music-track-changed', music_track_change_cb) + track = listener.get_playing_track() + if track is None: + print 'Now not playing anything' + else: + print 'Now playing: "%s" by %s' % (track.title, track.artist) + gobject.MainLoop().run() diff --git a/src/notify.py b/src/notify.py index 908ff25d9..79fd847a9 100644 --- a/src/notify.py +++ b/src/notify.py @@ -28,9 +28,8 @@ from common import helpers import dbus_support if dbus_support.supported: import dbus - if dbus_support.version >= (0, 41, 0): - import dbus.glib - import dbus.service + import dbus.glib + import dbus.service def get_show_in_roster(event, account, contact): '''Return True if this event must be shown in roster, else False''' @@ -42,11 +41,9 @@ def get_show_in_roster(event, account, contact): return False if event == 'message_received': chat_control = helpers.get_chat_control(account, contact) - if not chat_control: - return True - elif event == 'ft_request': - return True - return False + if chat_control: + return False + return True def get_show_in_systray(event, account, contact): '''Return True if this event must be shown in roster, else False''' @@ -56,10 +53,7 @@ def get_show_in_systray(event, account, contact): return True if gajim.config.get_per('notifications', str(num), 'systray') == 'no': return False - if event in ('message_received', 'ft_request', 'gc_msg_highlight', - 'ft_request'): - return True - return False + return True def get_advanced_notification(event, account, contact): '''Returns the number of the first advanced notification or None''' diff --git a/src/profile_window.py b/src/profile_window.py index 41bbd8de3..badcbb310 100644 --- a/src/profile_window.py +++ b/src/profile_window.py @@ -60,17 +60,40 @@ class ProfileWindow: def __init__(self, account): self.xml = gtkgui_helpers.get_glade('profile_window.glade') self.window = self.xml.get_widget('profile_window') + self.progressbar = self.xml.get_widget('progressbar') + self.statusbar = self.xml.get_widget('statusbar') + self.context_id = self.statusbar.get_context_id('profile') self.account = account self.jid = gajim.get_jid_from_account(account) self.avatar_mime_type = None self.avatar_encoded = None + self.message_id = self.statusbar.push(self.context_id, + _('Retrieving profile...')) + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) + self.remove_statusbar_timeout_id = None + # Create Image for avatar button + image = gtk.Image() + self.xml.get_widget('PHOTO_button').set_image(image) self.xml.signal_autoconnect(self) self.window.show_all() + def update_progressbar(self): + self.progressbar.pulse() + return True # loop forever + + def remove_statusbar(self, message_id): + self.statusbar.remove(self.context_id, message_id) + self.remove_statusbar_timeout_id = None + def on_profile_window_destroy(self, widget): + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + if self.remove_statusbar_timeout_id is not None: + gobject.source_remove(self.remove_statusbar_timeout_id) del gajim.interface.instances[self.account]['profile'] def on_profile_window_key_press_event(self, widget, event): @@ -79,8 +102,10 @@ class ProfileWindow: def on_clear_button_clicked(self, widget): # empty the image - self.xml.get_widget('PHOTO_image').set_from_icon_name('stock_person', - gtk.ICON_SIZE_DIALOG) + button = self.xml.get_widget('PHOTO_button') + image = button.get_image() + image.set_from_pixbuf(None) + button.set_label(_('Click to set your avatar')) self.avatar_encoded = None self.avatar_mime_type = None @@ -124,24 +149,39 @@ class ProfileWindow: pixbuf = gtkgui_helpers.get_pixbuf_from_data(data) # rescale it pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') - image = self.xml.get_widget('PHOTO_image') + button = self.xml.get_widget('PHOTO_button') + image = button.get_image() image.set_from_pixbuf(pixbuf) + button.set_label('') self.avatar_encoded = base64.encodestring(data) # returns None if unknown type self.avatar_mime_type = mimetypes.guess_type(path_to_file)[0] - self.dialog = dialogs.ImageChooserDialog(on_response_ok = on_ok) + def on_clear(widget): + self.dialog.destroy() + self.on_clear_button_clicked(widget) + + self.dialog = dialogs.AvatarChooserDialog(on_response_ok = on_ok, + on_response_clear = on_clear) def on_PHOTO_button_press_event(self, widget, event): '''If right-clicked, show popup''' if event.button == 3 and self.avatar_encoded: # right click menu = gtk.Menu() - nick = gajim.config.get_per('accounts', self.account, 'name') - menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) - menuitem.connect('activate', - gtkgui_helpers.on_avatar_save_as_menuitem_activate, - self.jid, None, nick + '.jpeg') - menu.append(menuitem) + + # Try to get pixbuf + is_fake = False + if account and gajim.contacts.is_pm_from_jid(account, jid): + is_fake = True + pixbuf = get_avatar_pixbuf_from_cache(jid, is_fake) + + if pixbuf: + nick = gajim.config.get_per('accounts', self.account, 'name') + menuitem = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS) + menuitem.connect('activate', + gtkgui_helpers.on_avatar_save_as_menuitem_activate, + self.jid, None, nick + '.jpeg') + menu.append(menuitem) # show clear menuitem = gtk.ImageMenuItem(gtk.STOCK_CLEAR) menuitem.connect('activate', self.on_clear_button_clicked) @@ -162,18 +202,23 @@ class ProfileWindow: def set_values(self, vcard): if not 'PHOTO' in vcard: # set default image - image = self.xml.get_widget('PHOTO_image') - image.set_from_icon_name('stock_person', gtk.ICON_SIZE_DIALOG) + button = self.xml.get_widget('PHOTO_button') + image = button.get_image() + image.set_from_pixbuf(None) + button.set_label(_('Click to set your avatar')) for i in vcard.keys(): if i == 'PHOTO': pixbuf, self.avatar_encoded, self.avatar_mime_type = \ get_avatar_pixbuf_encoded_mime(vcard[i]) - image = self.xml.get_widget('PHOTO_image') + button = self.xml.get_widget('PHOTO_button') + image = button.get_image() if not pixbuf: - image.set_from_icon_name('stock_person', gtk.ICON_SIZE_DIALOG) + image.set_from_pixbuf(None) + button.set_label(_('Click to set your avatar')) continue pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'vcard') image.set_from_pixbuf(pixbuf) + button.set_label('') continue if i == 'ADR' or i == 'TEL' or i == 'EMAIL': for entry in vcard[i]: @@ -191,6 +236,18 @@ class ProfileWindow: vcard[i], 0) else: self.set_value(i + '_entry', vcard[i]) + if self.update_progressbar_timeout_id is not None: + if self.message_id: + self.statusbar.remove(self.context_id, self.message_id) + self.message_id = self.statusbar.push(self.context_id, + _('Information received')) + self.remove_statusbar_timeout_id = gobject.timeout_add(3000, + self.remove_statusbar, self.message_id) + gobject.source_remove(self.update_progressbar_timeout_id) + # redraw progressbar after avatar is set so that windows is already + # resized. Else progressbar is not correctly redrawn + gobject.idle_add(self.progressbar.set_fraction, 0) + self.update_progressbar_timeout_id = None def add_to_vcard(self, vcard, entry, txt): '''Add an information to the vCard dictionary''' @@ -248,6 +305,9 @@ class ProfileWindow: return vcard def on_publish_button_clicked(self, widget): + if self.update_progressbar_timeout_id: + # Operation in progress + return if gajim.connections[self.account].connected < 2: dialogs.ErrorDialog(_('You are not connected to the server'), _('Without a connection you can not publish your contact ' @@ -261,8 +321,42 @@ class ProfileWindow: nick = gajim.config.get_per('accounts', self.account, 'name') gajim.nicks[self.account] = nick gajim.connections[self.account].send_vcard(vcard) + self.message_id = self.statusbar.push(self.context_id, + _('Sending profile...')) + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) + + def vcard_published(self): + if self.message_id: + self.statusbar.remove(self.context_id, self.message_id) + self.message_id = self.statusbar.push(self.context_id, + _('Information published')) + self.remove_statusbar_timeout_id = gobject.timeout_add(3000, + self.remove_statusbar, self.message_id) + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + self.progressbar.set_fraction(0) + self.update_progressbar_timeout_id = None + + def vcard_not_published(self): + if self.message_id: + self.statusbar.remove(self.context_id, self.message_id) + self.message_id = self.statusbar.push(self.context_id, + _('Information NOT published')) + self.remove_statusbar_timeout_id = gobject.timeout_add(3000, + self.remove_statusbar, self.message_id) + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) + self.progressbar.set_fraction(0) + self.update_progressbar_timeout_id = None + dialogs.InformationDialog(_('vCard publication failed'), + _('There was an error while publishing your personal information, ' + 'try again later.')) def on_retrieve_button_clicked(self, widget): + if self.update_progressbar_timeout_id: + # Operation in progress + return entries = ['FN', 'NICKNAME', 'BDAY', 'EMAIL_HOME_USERID', 'URL', 'TEL_HOME_NUMBER', 'N_FAMILY', 'N_GIVEN', 'N_MIDDLE', 'N_PREFIX', 'N_SUFFIX', 'ADR_HOME_STREET', 'ADR_HOME_EXTADR', 'ADR_HOME_LOCALITY', @@ -275,9 +369,18 @@ class ProfileWindow: for e in entries: self.xml.get_widget(e + '_entry').set_text('') self.xml.get_widget('DESC_textview').get_buffer().set_text('') - self.xml.get_widget('PHOTO_image').set_from_icon_name('stock_person', - gtk.ICON_SIZE_DIALOG) + button = self.xml.get_widget('PHOTO_button') + image = button.get_image() + image.set_from_pixbuf(None) + button.set_label(_('Click to set your avatar')) gajim.connections[self.account].request_vcard(self.jid) else: dialogs.ErrorDialog(_('You are not connected to the server'), - _('Without a connection, you can not get your contact information.')) + _('Without a connection, you can not get your contact information.')) + self.message_id = self.statusbar.push(self.context_id, + _('Retrieving profile...')) + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) + + def on_close_button_clicked(self, widget): + self.window.destroy() diff --git a/src/remote_control.py b/src/remote_control.py index 05156cf84..0ce901e24 100644 --- a/src/remote_control.py +++ b/src/remote_control.py @@ -1,19 +1,9 @@ ## remote_control.py ## -## Contributors for this file: -## - Yann Le Boulanger -## - Nikos Kouremenos -## - Dimitur Kirov -## - Andrew Sayman -## -## Copyright (C) 2003-2004 Yann Le Boulanger -## Vincent Hanquez -## Copyright (C) 2005 Yann Le Boulanger -## Vincent Hanquez -## Nikos Kouremenos -## Dimitur Kirov -## Travis Shirk -## Norman Rasmussen +## Copyright (C) 2005-2006 Yann Le Boulanger +## Copyright (C) 2005-2006 Nikos Kouremenos +## Copyright (C) 2005-2006 Dimitur Kirov +## Copyright (C) 2005-2006 Andrew Sayman ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published @@ -36,57 +26,30 @@ from dialogs import AddNewContactWindow, NewChatDialog import dbus_support if dbus_support.supported: import dbus - if dbus_support.version >= (0, 41, 0): + if dbus_support: import dbus.service - import dbus.glib # cause dbus 0.35+ doesn't return signal replies without it - DbusPrototype = dbus.service.Object - elif dbus_support.version >= (0, 20, 0): - DbusPrototype = dbus.Object - else: #dbus is not defined - DbusPrototype = str + import dbus.glib INTERFACE = 'org.gajim.dbus.RemoteInterface' OBJ_PATH = '/org/gajim/dbus/RemoteObject' SERVICE = 'org.gajim.dbus' -# type mapping, it is different in each version -ident = lambda e: e -if dbus_support.version[1] >= 43: - # in most cases it is a utf-8 string - DBUS_STRING = dbus.String +# type mapping - # general type (for use in dicts, - # where all values should have the same type) - DBUS_VARIANT = dbus.Variant - DBUS_BOOLEAN = dbus.Boolean - DBUS_DOUBLE = dbus.Double - DBUS_INT32 = dbus.Int32 - # dictionary with string key and binary value - DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv") - # dictionary with string key and value - DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss") - # empty type - DBUS_NONE = lambda : dbus.Variant(0) +# in most cases it is a utf-8 string +DBUS_STRING = dbus.String -else: # 33, 35, 36 - DBUS_DICT_SV = lambda : {} - DBUS_DICT_SS = lambda : {} - DBUS_STRING = lambda e: unicode(e).encode('utf-8') - # this is the only way to return lists and dicts of mixed types - DBUS_VARIANT = lambda e: (isinstance(e, (str, unicode)) and \ - DBUS_STRING(e)) or repr(e) - DBUS_NONE = lambda : '' - if dbus_support.version[1] >= 41: # 35, 36 - DBUS_BOOLEAN = dbus.Boolean - DBUS_DOUBLE = dbus.Double - DBUS_INT32 = dbus.Int32 - else: # 33 - DBUS_BOOLEAN = ident - DBUS_INT32 = ident - DBUS_DOUBLE = ident - -STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', - 'invisible'] +# general type (for use in dicts, where all values should have the same type) +DBUS_VARIANT = dbus.Variant +DBUS_BOOLEAN = dbus.Boolean +DBUS_DOUBLE = dbus.Double +DBUS_INT32 = dbus.Int32 +# dictionary with string key and binary value +DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv") +# dictionary with string key and value +DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss") +# empty type +DBUS_NONE = lambda : dbus.Variant(0) def get_dbus_struct(obj): ''' recursively go through all the items and replace @@ -123,65 +86,35 @@ class Remote: self.signal_object = None session_bus = dbus_support.session_bus.SessionBus() - if dbus_support.version[1] >= 41: - service = dbus.service.BusName(SERVICE, bus=session_bus) - self.signal_object = SignalObject(service) - elif dbus_support.version[1] <= 40 and dbus_support.version[1] >= 20: - service=dbus.Service(SERVICE, session_bus) - self.signal_object = SignalObject(service) + service = dbus.service.BusName(SERVICE, bus=session_bus) + self.signal_object = SignalObject(service) def raise_signal(self, signal, arg): if self.signal_object: self.signal_object.raise_signal(signal, - get_dbus_struct(arg)) + get_dbus_struct(arg)) -class SignalObject(DbusPrototype): - ''' Local object definition for /org/gajim/dbus/RemoteObject. This doc must - not be visible, because the clients can access only the remote object. ''' +class SignalObject(dbus.service.Object): + ''' Local object definition for /org/gajim/dbus/RemoteObject. + (This docstring is not be visible, because the clients can access only the remote object.)''' def __init__(self, service): self.first_show = True self.vcard_account = None # register our dbus API - if dbus_support.version[1] >= 41: - DbusPrototype.__init__(self, service, OBJ_PATH) - elif dbus_support.version[1] >= 30: - DbusPrototype.__init__(self, OBJ_PATH, service) - else: - DbusPrototype.__init__(self, OBJ_PATH, service, - [ self.toggle_roster_appearance, - self.show_next_unread, - self.list_contacts, - self.list_accounts, - self.account_info, - self.change_status, - self.open_chat, - self.send_message, - self.send_single_message, - self.contact_info, - self.send_file, - self.prefs_list, - self.prefs_store, - self.prefs_del, - self.prefs_put, - self.add_contact, - self.remove_contact, - self.get_status, - self.get_status_message, - self.start_chat, - self.send_xml, - ]) + dbus.service.Object.__init__(self, service, OBJ_PATH) def raise_signal(self, signal, arg): - ''' raise a signal, with a single string message ''' + '''raise a signal, with a single string message''' from dbus import dbus_bindings message = dbus_bindings.Signal(OBJ_PATH, INTERFACE, signal) i = message.get_iter(True) i.append(arg) self._connection.send(message) + @dbus.service.method(INTERFACE) def get_status(self, *args): '''get_status(account = None) returns status (show to be exact) which is the global one @@ -193,8 +126,9 @@ class SignalObject(DbusPrototype): return helpers.get_global_show() # return show for the given account index = gajim.connections[account].connected - return DBUS_STRING(STATUS_LIST[index]) + return DBUS_STRING(gajim.SHOW_LIST[index]) + @dbus.service.method(INTERFACE) def get_status_message(self, *args): '''get_status(account = None) returns status which is the global one @@ -208,7 +142,7 @@ class SignalObject(DbusPrototype): status = gajim.connections[account].status return DBUS_STRING(status) - + @dbus.service.method(INTERFACE) def get_account_and_contact(self, account, jid): ''' get the account (if not given) and contact instance from jid''' connected_account = None @@ -236,6 +170,7 @@ class SignalObject(DbusPrototype): return connected_account, contact + @dbus.service.method(INTERFACE) def send_file(self, *args): '''send_file(file_path, jid, account=None) send file, located at 'file_path' to 'jid', using account @@ -254,7 +189,7 @@ class SignalObject(DbusPrototype): return False def _send_message(self, jid, message, keyID, account, type = 'chat', subject = None): - ''' can be called from send_chat_message (default when send_message) + '''can be called from send_chat_message (default when send_message) or send_single_message''' if not jid or not message: return None # or raise error @@ -269,22 +204,25 @@ class SignalObject(DbusPrototype): return True return False + @dbus.service.method(INTERFACE) def send_chat_message(self, *args): - ''' send_message(jid, message, keyID=None, account=None) + '''send_message(jid, message, keyID=None, account=None) send chat 'message' to 'jid', using account (optional) 'account'. if keyID is specified, encrypt the message with the pgp key ''' jid, message, keyID, account = self._get_real_arguments(args, 4) jid = self._get_real_jid(jid, account) return self._send_message(jid, message, keyID, account) + @dbus.service.method(INTERFACE) def send_single_message(self, *args): - ''' send_single_message(jid, subject, message, keyID=None, account=None) + '''send_single_message(jid, subject, message, keyID=None, account=None) send single 'message' to 'jid', using account (optional) 'account'. if keyID is specified, encrypt the message with the pgp key ''' jid, subject, message, keyID, account = self._get_real_arguments(args, 5) jid = self._get_real_jid(jid, account) return self._send_message(jid, message, keyID, account, type, subject) + @dbus.service.method(INTERFACE) def open_chat(self, *args): ''' start_chat(jid, account=None) -> shows the tabbed window for new message to 'jid', using account(optional) 'account' ''' @@ -332,6 +270,7 @@ class SignalObject(DbusPrototype): return True return False + @dbus.service.method(INTERFACE) def change_status(self, *args, **keywords): ''' change_status(status, message, account). account is optional - if not specified status is changed for all accounts. ''' @@ -352,13 +291,15 @@ class SignalObject(DbusPrototype): status, message) return None + @dbus.service.method(INTERFACE) def show_next_unread(self, *args): - ''' Show the window(s) with next waiting messages in tabbed/group chats. ''' + '''Show the window(s) with next waiting messages in tabbed/group chats. ''' if gajim.events.get_nb_events(): gajim.interface.systray.handle_first_event() + @dbus.service.method(INTERFACE) def contact_info(self, *args): - ''' get vcard info for a contact. Return cached value of the vcard. + '''get vcard info for a contact. Return cached value of the vcard. ''' [jid] = self._get_real_arguments(args, 1) if not isinstance(jid, unicode): @@ -375,8 +316,9 @@ class SignalObject(DbusPrototype): # return empty dict return DBUS_DICT_SV() + @dbus.service.method(INTERFACE) def list_accounts(self, *args): - ''' list register accounts ''' + '''list register accounts''' result = gajim.contacts.get_accounts() if result and len(result) > 0: result_array = [] @@ -385,8 +327,9 @@ class SignalObject(DbusPrototype): return result_array return None + @dbus.service.method(INTERFACE) def account_info(self, *args): - ''' show info on account: resource, jid, nick, prio, message ''' + '''show info on account: resource, jid, nick, prio, message''' [for_account] = self._get_real_arguments(args, 1) if not gajim.connections.has_key(for_account): # account is invalid @@ -394,19 +337,20 @@ class SignalObject(DbusPrototype): account = gajim.connections[for_account] result = DBUS_DICT_SS() index = account.connected - result['status'] = DBUS_STRING(STATUS_LIST[index]) + result['status'] = DBUS_STRING(gajim.SHOW_LIST[index]) result['name'] = DBUS_STRING(account.name) result['jid'] = DBUS_STRING(gajim.get_jid_from_account(account.name)) result['message'] = DBUS_STRING(account.status) result['priority'] = DBUS_STRING(unicode(gajim.config.get_per('accounts', - account.name, 'priority'))) + account.name, 'priority'))) result['resource'] = DBUS_STRING(unicode(gajim.config.get_per('accounts', - account.name, 'resource'))) + account.name, 'resource'))) return result + @dbus.service.method(INTERFACE) def list_contacts(self, *args): - ''' list all contacts in the roster. If the first argument is specified, - then return the contacts for the specified account ''' + '''list all contacts in the roster. If the first argument is specified, + then return the contacts for the specified account''' [for_account] = self._get_real_arguments(args, 1) result = [] accounts = gajim.contacts.get_accounts() @@ -428,6 +372,7 @@ class SignalObject(DbusPrototype): return None return result + @dbus.service.method(INTERFACE) def toggle_roster_appearance(self, *args): ''' shows/hides the roster window ''' win = gajim.interface.roster.window @@ -441,6 +386,7 @@ class SignalObject(DbusPrototype): else: win.window.focus(long(time())) + @dbus.service.method(INTERFACE) def prefs_list(self, *args): prefs_dict = DBUS_DICT_SS() def get_prefs(data, name, path, value): @@ -455,6 +401,7 @@ class SignalObject(DbusPrototype): gajim.config.foreach(get_prefs) return prefs_dict + @dbus.service.method(INTERFACE) def prefs_store(self, *args): try: gajim.interface.save_config() @@ -462,6 +409,7 @@ class SignalObject(DbusPrototype): return False return True + @dbus.service.method(INTERFACE) def prefs_del(self, *args): [key] = self._get_real_arguments(args, 1) if not key: @@ -475,6 +423,7 @@ class SignalObject(DbusPrototype): gajim.config.del_per(key_path[0], key_path[1], key_path[2]) return True + @dbus.service.method(INTERFACE) def prefs_put(self, *args): [key] = self._get_real_arguments(args, 1) if not key: @@ -488,6 +437,7 @@ class SignalObject(DbusPrototype): gajim.config.set_per(key_path[0], key_path[1], subname, value) return True + @dbus.service.method(INTERFACE) def add_contact(self, *args): [jid, account] = self._get_real_arguments(args, 2) if account: @@ -503,6 +453,7 @@ class SignalObject(DbusPrototype): AddNewContactWindow(account = None, jid = jid) return True + @dbus.service.method(INTERFACE) def remove_contact(self, *args): [jid, account] = self._get_real_arguments(args, 2) jid = self._get_real_jid(jid, account) @@ -597,9 +548,11 @@ class SignalObject(DbusPrototype): contact_dict['resources'] = DBUS_VARIANT(contact_dict['resources']) return contact_dict + @dbus.service.method(INTERFACE) def get_unread_msgs_number(self, *args): return str(gajim.events.get_nb_events) + @dbus.service.method(INTERFACE) def start_chat(self, *args): [account] = self._get_real_arguments(args, 1) if not account: @@ -608,6 +561,7 @@ class SignalObject(DbusPrototype): NewChatDialog(account) return True + @dbus.service.method(INTERFACE) def send_xml(self, *args): xml, account = self._get_real_arguments(args, 2) if account: @@ -615,36 +569,3 @@ class SignalObject(DbusPrototype): else: for acc in gajim.contacts.get_accounts(): gajim.connections[acc].send_stanza(xml) - - if dbus_support.version[1] >= 30 and dbus_support.version[1] <= 40: - method = dbus.method - signal = dbus.signal - elif dbus_support.version[1] >= 41: - method = dbus.service.method - signal = dbus.service.signal - - # prevent using decorators, because they are not supported - # on python < 2.4 - # FIXME: use decorators when python2.3 (and dbus 0.23) is OOOOOOLD - toggle_roster_appearance = method(INTERFACE)(toggle_roster_appearance) - list_contacts = method(INTERFACE)(list_contacts) - list_accounts = method(INTERFACE)(list_accounts) - show_next_unread = method(INTERFACE)(show_next_unread) - change_status = method(INTERFACE)(change_status) - open_chat = method(INTERFACE)(open_chat) - contact_info = method(INTERFACE)(contact_info) - send_message = method(INTERFACE)(send_chat_message) - send_single_message = method(INTERFACE)(send_single_message) - send_file = method(INTERFACE)(send_file) - prefs_list = method(INTERFACE)(prefs_list) - prefs_put = method(INTERFACE)(prefs_put) - prefs_del = method(INTERFACE)(prefs_del) - prefs_store = method(INTERFACE)(prefs_store) - remove_contact = method(INTERFACE)(remove_contact) - add_contact = method(INTERFACE)(add_contact) - get_status = method(INTERFACE)(get_status) - get_status_message = method(INTERFACE)(get_status_message) - account_info = method(INTERFACE)(account_info) - get_unread_msgs_number = method(INTERFACE)(get_unread_msgs_number) - start_chat = method(INTERFACE)(start_chat) - send_xml = method(INTERFACE)(send_xml) diff --git a/src/roster_window.py b/src/roster_window.py index 0ce9cd2dd..4100e54b3 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- ## roster_window.py ## ## Copyright (C) 2003-2006 Yann Le Boulanger @@ -39,6 +40,10 @@ from chat_control import ChatControl from groupchat_control import GroupchatControl from groupchat_control import PrivateChatControl +import dbus_support +if dbus_support.supported: + from music_track_listener import MusicTrackListener + #(icon, name, type, jid, account, editable, second pixbuf) ( C_IMG, # image to show state (online, new message etc) @@ -50,9 +55,6 @@ C_EDITABLE, # cellrenderer text that holds name editable or not? C_SECPIXBUF, # secondary_pixbuf (holds avatar or padlock) ) = range(7) - -DEFAULT_ICONSET = 'dcraven' - class RosterWindow: '''Class for main window of gtkgui interface''' @@ -624,9 +626,6 @@ class RosterWindow: self.join_gc_room(account, bookmark['jid'], bookmark['nick'], bookmark['password']) - def on_bm_header_changed_state(self, widget, event): - widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state - def on_send_server_message_menuitem_activate(self, widget, account): server = gajim.config.get_per('accounts', account, 'hostname') server += '/announce/online' @@ -717,6 +716,11 @@ class RosterWindow: return new_chat_menuitem = self.xml.get_widget('new_chat_menuitem') join_gc_menuitem = self.xml.get_widget('join_gc_menuitem') + iconset = gajim.config.get('iconset') + path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') + state_images = self.load_iconset(path) + if state_images.has_key('muc_active'): + join_gc_menuitem.set_image(state_images['muc_active']) add_new_contact_menuitem = self.xml.get_widget('add_new_contact_menuitem') service_disco_menuitem = self.xml.get_widget('service_disco_menuitem') advanced_menuitem = self.xml.get_widget('advanced_menuitem') @@ -787,7 +791,7 @@ class RosterWindow: label.set_use_underline(False) gc_item = gtk.MenuItem() gc_item.add(label) - gc_item.connect('state-changed', self.on_bm_header_changed_state) + gc_item.connect('state-changed', gtkgui_helpers.on_bm_header_changed_state) gc_sub_menu.append(gc_item) self.add_bookmarks_list(gc_sub_menu, account) @@ -1084,8 +1088,12 @@ class RosterWindow: win.redraw_tab(ctrl) name = contact.get_shown_name() - if contact.resource != '': + + # if multiple resources (or second one disconnecting) + if (len(contact_instances) > 1 or (len(contact_instances) == 1 and \ + show in ('offline', 'error'))) and contact.resource != '': name += '/' + contact.resource + uf_show = helpers.get_uf_show(show) if status: ctrl.print_conversation(_('%s is now %s (%s)') % (name, uf_show, @@ -1204,7 +1212,7 @@ class RosterWindow: gajim.connections[account].request_register_agent_info(contact.jid) def on_remove_agent(self, widget, list_): - '''When an agent is requested to log in or off. list_ is a list of + '''When an agent is requested to be removed. list_ is a list of (contact, account) tuple''' for (contact, account) in list_: if gajim.config.get_per('accounts', account, 'hostname') == \ @@ -1226,6 +1234,17 @@ class RosterWindow: gajim.contacts.remove_jid(account, contact.jid) gajim.contacts.remove_contact(account, contact) + # Check if there are unread events from some contacts + has_unread_events = False + for (contact, account) in list_: + for jid in gajim.events.get_events(account): + if jid.endswith(contact.jid): + has_unread_events = True + break + if has_unread_events: + dialogs.ErrorDialog(_('You have unread messages'), + _('You must read them before removing this transport.')) + return if len(list_) == 1: pritext = _('Transport "%s" will be removed') % contact.jid sectext = _('You will no longer be able to send and receive messages to contacts from this transport.') @@ -1332,7 +1351,7 @@ class RosterWindow: '''Make contact's popup menu''' model = self.tree.get_model() jid = model[iter][C_JID].decode('utf-8') - path = model.get_path(iter) + tree_path = model.get_path(iter) account = model[iter][C_ACCOUNT].decode('utf-8') our_jid = jid == gajim.get_jid_from_account(account) contact = gajim.contacts.get_contact_with_highest_priority(account, jid) @@ -1368,6 +1387,12 @@ class RosterWindow: img.set_from_file(path_to_kbd_input_img) rename_menuitem.set_image(img) + iconset = gajim.config.get('iconset') + path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') + state_images = self.load_iconset(path) + if state_images.has_key('muc_active'): + invite_menuitem.set_image(state_images['muc_active']) + above_subscription_separator = xml.get_widget( 'above_subscription_separator') subscription_menuitem = xml.get_widget('subscription_menuitem') @@ -1387,8 +1412,6 @@ class RosterWindow: start_chat_menuitem.set_submenu(sub_menu) iconset = gajim.config.get('iconset') - if not iconset: - iconset = DEFAULT_ICONSET path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') for c in contacts: # icon MUST be different instance for every item @@ -1403,7 +1426,7 @@ class RosterWindow: else: # one resource start_chat_menuitem.connect('activate', - self.on_roster_treeview_row_activated, path) + self.on_roster_treeview_row_activated, tree_path) if contact.resource: send_file_menuitem.connect('activate', @@ -1444,7 +1467,7 @@ class RosterWindow: menuitem.connect('activate', self.on_invite_to_room, [(contact, account)], room_jid, acct) submenu.append(menuitem) - rename_menuitem.connect('activate', self.on_rename, iter, path) + rename_menuitem.connect('activate', self.on_rename, iter, tree_path) remove_from_roster_menuitem.connect('activate', self.on_req_usub, [(contact, account)]) information_menuitem.connect('activate', self.on_info, contact, @@ -1774,8 +1797,6 @@ class RosterWindow: # we have to create our own set of icons for the menu # using self.jabber_status_images is poopoo iconset = gajim.config.get('iconset') - if not iconset: - iconset = DEFAULT_ICONSET path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') state_images = self.load_iconset(path) @@ -1908,8 +1929,6 @@ class RosterWindow: else: menu = gtk.Menu() iconset = gajim.config.get('iconset') - if not iconset: - iconset = DEFAULT_ICONSET path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') accounts = [] # Put accounts in a list to sort them for account in gajim.connections: @@ -2419,6 +2438,41 @@ _('If "%s" accepts this request you will know his or her status.') % jid) self.send_status(acct, status, message) self.update_status_combobox() + ## enable setting status msg from currently playing music track + def enable_syncing_status_msg_from_current_music_track(self, enabled): + '''if enabled is True, we listen to events from music players about + currently played music track, and we update our + status message accordinly''' + if not dbus_support.supported: + # do nothing if user doesn't have D-Bus bindings + return + if enabled: + if self._music_track_changed_signal is None: + 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) + 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) + + def _music_track_changed(self, unused_listener, music_track_info): + accounts = gajim.connections.keys() + if music_track_info is None: + status_message = '' + else: + status_message = _('♪ "%(title)s" by %(artist)s ♪') % \ + {'title': music_track_info.title, + 'artist': music_track_info.artist } + for acct in accounts: + current_show = gajim.SHOW_LIST[gajim.connections[acct].connected] + self.send_status(acct, current_show, status_message) + + def update_status_combobox(self): # table to change index in connection.connected to index in combobox table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2, @@ -2599,12 +2653,12 @@ _('If "%s" accepts this request you will know his or her status.') % jid) # We save it in a queue type_ = 'chat' + event_type = 'message_received' if msg_type == 'normal': type_ = 'normal' - show_in_roster = notify.get_show_in_roster('message_received', account, - contact) - show_in_systray = notify.get_show_in_systray('message_received', account, - contact) + event_type = 'single_message_received' + show_in_roster = notify.get_show_in_roster(event_type, account, contact) + show_in_systray = notify.get_show_in_systray(event_type, account, contact) event = gajim.events.create_event(type_, (msg, subject, msg_type, tim, encrypted, resource, msg_id), show_in_roster = show_in_roster, show_in_systray = show_in_systray) @@ -3134,8 +3188,13 @@ _('If "%s" accepts this request you will know his or her status.') % jid) def make_jabber_state_images(self): '''initialise jabber_state_images dict''' iconset = gajim.config.get('iconset') - if not iconset: - iconset = 'dcraven' + if iconset: + path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') + if not os.path.exists(path): + iconset = gajim.config.DEFAULT_ICONSET + else: + iconset = gajim.config.DEFAULT_ICONSET + path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '32x32') self.jabber_state_images['32'] = self.load_iconset(path) @@ -3174,7 +3233,8 @@ _('If "%s" accepts this request you will know his or her status.') % jid) model[iter][1] = self.jabber_state_images['16'][model[iter][2]] iter = model.iter_next(iter) # Update the systray - gajim.interface.systray.set_img() + if gajim.interface.systray_enabled: + gajim.interface.systray.set_img() for win in gajim.interface.msg_win_mgr.windows(): for ctrl in win.controls(): @@ -3722,6 +3782,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid) def __init__(self): self.xml = gtkgui_helpers.get_glade('roster_window.glade') self.window = self.xml.get_widget('roster_window') + self._music_track_changed_signal = None gajim.interface.msg_win_mgr = MessageWindowMgr() self.advanced_menus = [] # We keep them to destroy them if gajim.config.get('roster_window_skip_taskbar'): @@ -3898,6 +3959,13 @@ _('If "%s" accepts this request you will know his or her status.') % jid) 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. + gobject.timeout_add(1000, + self.enable_syncing_status_msg_from_current_music_track, + gajim.config.get('set_status_msg_from_current_music_track')) + if gajim.config.get('show_roster_on_startup'): self.window.show_all() else: diff --git a/src/systray.py b/src/systray.py index 926c077d1..ade7ffd81 100644 --- a/src/systray.py +++ b/src/systray.py @@ -124,11 +124,12 @@ class Systray: # We need our own set of status icons, let's make 'em! iconset = gajim.config.get('iconset') - if not iconset: - iconset = 'dcraven' path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') state_images = gajim.interface.roster.load_iconset(path) + if state_images.has_key('muc_active'): + join_gc_menuitem.set_image(state_images['muc_active']) + for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): uf_show = helpers.get_uf_show(show, use_mnemonic = True) item = gtk.ImageMenuItem(uf_show) @@ -194,6 +195,7 @@ class Systray: label.set_use_underline(False) gc_item = gtk.MenuItem() gc_item.add(label) + gc_item.connect('state-changed', gtkgui_helpers.on_bm_header_changed_state) gc_sub_menu.append(gc_item) gajim.interface.roster.add_bookmarks_list(gc_sub_menu, account) @@ -250,11 +252,11 @@ class Systray: if len(gajim.events.get_systray_events()) == 0: # no pending events, so toggle visible/hidden for roster window if win.get_property('visible'): # visible in ANY virtual desktop? - win.hide() # we hide it from VD that was visible in - # but we could be in another VD right now. eg vd2 - # and we want not only to hide it in vd1 but also show it in vd2 - gtkgui_helpers.possibly_move_window_in_current_desktop(win) + # we could be in another VD right now. eg vd2 + # and we want to show it in vd2 + if not gtkgui_helpers.possibly_move_window_in_current_desktop(win): + win.hide() # else we hide it from VD that was visible in else: win.present() else: diff --git a/src/vcard.py b/src/vcard.py index 009605b38..975d39a74 100644 --- a/src/vcard.py +++ b/src/vcard.py @@ -61,6 +61,7 @@ class VcardWindow: # the contact variable is the jid if vcard is true self.xml = gtkgui_helpers.get_glade('vcard_information_window.glade') self.window = self.xml.get_widget('vcard_information_window') + self.progressbar = self.xml.get_widget('progressbar') self.contact = contact self.account = account @@ -68,13 +69,23 @@ class VcardWindow: self.avatar_mime_type = None self.avatar_encoded = None + self.vcard_arrived = False + self.os_info_arrived = False + self.update_progressbar_timeout_id = gobject.timeout_add(100, + self.update_progressbar) self.fill_jabber_page() self.xml.signal_autoconnect(self) self.window.show_all() + def update_progressbar(self): + self.progressbar.pulse() + return True # loop forever + def on_vcard_information_window_destroy(self, widget): + if self.update_progressbar_timeout_id is not None: + gobject.source_remove(self.update_progressbar_timeout_id) del gajim.interface.instances[self.account]['infos'][self.contact.jid] def on_vcard_information_window_key_press_event(self, widget, event): @@ -113,7 +124,15 @@ class VcardWindow: def set_value(self, entry_name, value): try: - self.xml.get_widget(entry_name).set_text(value) + if value and entry_name == 'URL_label': + if gtk.pygtk_version >= (2, 10, 0) and gtk.gtk_version >= (2, 10, 0): + widget = gtk.LinkButton(value, value) + else: + widget = gtk.Label(value) + table = self.xml.get_widget('personal_info_table') + table.attach(widget, 1, 4, 3, 4, yoptions = 0) + else: + self.xml.get_widget(entry_name).set_text(value) except AttributeError: pass @@ -144,8 +163,17 @@ class VcardWindow: if i == 'DESC': self.xml.get_widget('DESC_textview').get_buffer().set_text( vcard[i], 0) - else: + elif i != 'jid': # Do not override jid_label self.set_value(i + '_label', vcard[i]) + self.vcard_arrived = True + self.test_remove_progressbar() + + def test_remove_progressbar(self): + if self.update_progressbar_timeout_id is not None and \ + self.vcard_arrived and self.os_info_arrived: + gobject.source_remove(self.update_progressbar_timeout_id) + self.progressbar.hide() + self.update_progressbar_timeout_id = None def set_last_status_time(self): self.fill_status_label() @@ -174,6 +202,8 @@ class VcardWindow: os = Q_('?OS:Unknown') self.xml.get_widget('client_name_version_label').set_text(client) self.xml.get_widget('os_label').set_text(os) + self.os_info_arrived = True + self.test_remove_progressbar() def fill_status_label(self): if self.xml.get_widget('information_notebook').get_n_pages() < 4: @@ -251,8 +281,10 @@ class VcardWindow: gajim.connections[self.account].request_last_status_time(self.contact.jid, self.contact.resource) - # Request os info in contact is connected - if self.contact.show not in ('offline', 'error'): + # do not wait for os_info if contact is not connected + if self.contact.show in ('offline', 'error'): + self.os_info_arrived = True + else: # Request os info if contact is connected gobject.idle_add(gajim.connections[self.account].request_os_info, self.contact.jid, self.contact.resource) self.os_info = {0: {'resource': self.contact.resource, 'client': '', @@ -283,3 +315,6 @@ class VcardWindow: self.fill_status_label() gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake) + + def on_close_button_clicked(self, widget): + self.window.destroy()