From 0d0ac51fa8b7fe351fdb49094e25dafcfd899306 Mon Sep 17 00:00:00 2001 From: Stefan Bethge Date: Mon, 9 Oct 2006 00:27:03 +0000 Subject: [PATCH] merge with trunk --- AUTHORS | 2 +- ChangeLog | 208 ---------------------- Changelog | 12 +- README | 86 +-------- README.html | 130 ++++++++++++++ data/glade/join_groupchat_window.glade | 155 ++++++---------- data/glade/preferences_window.glade | 5 - data/glade/vcard_information_window.glade | 4 +- po/POTFILES.in | 114 ------------ po/de.po | 4 +- src/advanced.py | 18 +- src/chat_control.py | 10 +- src/common/check_paths.py | 21 +-- src/common/config.py | 4 +- src/common/connection.py | 19 +- src/common/connection_handlers.py | 3 + src/common/exceptions.py | 15 +- src/common/gajim.py | 13 +- src/common/helpers.py | 109 ++++++++++++ src/common/logger.py | 18 +- src/common/optparser.py | 52 +++++- src/common/passwords.py | 85 +++++++++ src/common/rst_xhtml_generator.py | 126 +++++++++++++ src/config.py | 31 ++-- src/conversation_textview.py | 86 +++++---- src/dialogs.py | 108 +++++------ src/disco.py | 47 ++--- src/gajim.py | 67 +++---- src/groupchat_control.py | 51 +++--- src/gtkexcepthook.py | 1 + src/gtkgui_helpers.py | 52 ++---- src/history_manager.py | 23 +-- src/history_window.py | 7 +- src/notify.py | 4 +- src/roster_window.py | 37 ++-- src/rst_xhtml_generator.py | 116 ------------ src/statusicon.py | 69 +++++++ src/systray.py | 51 +++--- src/tooltips.py | 129 ++++---------- src/vcard.py | 57 +++--- 40 files changed, 1042 insertions(+), 1107 deletions(-) delete mode 100644 ChangeLog create mode 100644 README.html delete mode 100644 po/POTFILES.in create mode 100644 src/common/passwords.py create mode 100644 src/common/rst_xhtml_generator.py delete mode 100644 src/rst_xhtml_generator.py create mode 100644 src/statusicon.py diff --git a/AUTHORS b/AUTHORS index 29f8040f0..b3dd37b13 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,6 +1,7 @@ CURRENT DEVELOPERS: Yann Le Boulanger (asterix AT lagaule.org) +Nikos Kouremenos (kourem AT gmail.com) Dimitur Kirov (dkirov AT gmail.com) Travis Shirk (travis AT pobox.com) Jean-Marie Traissard (jim AT lapin.org) @@ -8,4 +9,3 @@ Jean-Marie Traissard (jim AT lapin.org) PAST DEVELOPERS: Vincent Hanquez (tab AT snarc.org) -Nikos Kouremenos (kourem AT gmail.com) diff --git a/ChangeLog b/ChangeLog deleted file mode 100644 index f21490300..000000000 --- a/ChangeLog +++ /dev/null @@ -1,208 +0,0 @@ -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 - * gajim.desktop is generated with translation (#834) - * Preventing several TBs and annoyances (r6273, r6275, r6279, r6301, - r6308, r6311, r6323, r6326, r6327, r6335, r6342, r6346, r6348) - -Gajim 0.10 (01 May 2006) - - * One Messages Window ability (default to it) with tab reordering ability - * Non blocking socket connections. Gajim no longer remains unresponsive. - * Gajim now uses less memory - * File Transfer improvements (now should work out of the box for all) - * Meta Contacts ability (relationships between contacts) - * Support for legacy composing event (JEP-0022). Now 'Contact is composing a message' will always work - * Gajim now defaults to theme that uses GTK colors - * Roster Management Improvements (f.e. editablity of transport names, extended Drag and Drop Functionality) - * History (chat logs) Manager (search globally, delete, etc) - * Animated Emoticons ability - * Support for GTalk email notifications for GMail - * Room administrators can modify room ban list - * Gajim no longer optionally depends on pydns or dnspython. Requires - dnsutils (or whatever package provides the nslookup binary) - * gajim-remote has extended functionality - * Improved Preset Status Messages Experience - * Detection for CRUX as user's operating system - * New art included, appropriate sizes of icons used where available - * Translations under Windows now work okay - * Tons of fixes for bugs and annoyances: http://trac.gajim.org/query?status=closed&milestone=0.10 - - -Gajim 0.9.1 (27 December 2005) - - * Fix bug when joining a Groupchat - * Fix bug when starting Gajim without old logs - -Gajim 0.9 (23 December 2005) - - * Avatars and status messages in roster window - * Improved service discovery window - * Emoticons selector, Cooler Popup Windows (notification-daemon). Read more information in case you did not notice something different in http://trac.gajim.org/wiki/GajimDBus#notif_daemon - * Caching of Avatars, Less UI freezing - * New Account creation wizard - * Better History Window with searching capabilities - * Gajim now tries to reconnect to a jabber server if connection is lost - * Queue for all events (File Transfer, private messages, etc) - * A lot of new irc-like commands in group chat. Do for example /help invite - * X11 Session Management support - * Gajim registers and handles xmpp: and xmpp:// (GNOME/gconfd only) - * Use pysqlite for conversation history. Automigration for old logs - * New translations: Italian, Swedish, Slovak, Basque - -Gajim 0.8.2 (06 Sep 2005) - - * Fix so Gajim runs in pygtk2.8.x - * Gajim can use pydns too (apart from dnspython) to do SRV lookup - * Other minor fixes - -Gajim 0.8.1 (02 Sep 2005) - - * Systray icon for windows - * Gajim is available in Dutch - * Gajim can use gpg-agent - -Gajim 0.8 (18 Aug 2005) - - * Avatars (JEP-0153) - * Chat state notifications aka. typing notification (JEP-0085) - * Bookmark storage (JEP-0048) - * File Transfer (JEP-0096) - * Major changes to adhere to GNOME HIG - * Complete vcard fields support - * New and better user interface for chat and groupchat windows - * SRV capabilities and custom hostname/port - * Many improvements in group chat and IRC emulation (eg. nick autocompletation and cycling) - * Gajim can now send and receive single messages - * New iconsets and new dialog for customizing the user interface - * Mouseover information for contacts in the roster window (aka tooltips) - * DBus Capabilities. Now Gajim can be remote controlled - * Authenticating HTTP Requests via XMPP (JEP-0070) - * Now you can lookup a word in Wikipedia, dictionary or in search engine - * XML Console - * Gajim is now also available in norwegian and czech language - - -Gajim 0.7.1 (5 Jun 2005) - - * Transports icon as an option and error/mesage icon for transports - * Gajim is more HIG compatible - * Editing registration information on transports - * Messages stanza without element are not printed - * SASL bugfix - * GtkSpell capabilities - * Support SSL (legacy) connection - * Assign gpg key to specific contact - * Contacts are sortable by status - * Gajim remembers last lines when reopening chat - * New translations available: German, Russian, Spanish, Bulgarian - -Gajim 0.7 (23 May 2005) - - * Ability for groupchat reserved rooms with full affiliations and roles support - * Popup notification for incoming events - * Protocol icons for contacts from transports - * Gajim's user interface is now more HIG compliant - * Gajim now detects and can send operating system information - * Gajim now can inform the user about new version availability - * Gajim jabber library migration from jabberpy to xmpppy - * Rewrite the plugin system to remove threads and improve latency - * Gajim now supports Nodes in Service Discovery - * Greek and Polish translations - - -Gajim 0.6.1 (03 April 2005) - - * Rewrite of service discovery. It doesn't freeze Gajim anymore. - * More HIG Compliant. - * Gajim is faster (do not redraw preferences_window each time we open it, use - of psyco if available) - -Gajim 0.6 (23 March 2005) - - * Gajim's user interface is now nicer. - * Groupchat just got better. - * URL, mailto and ascii formatin (* / _) detection - * Better transports detection, group management, and many minor additions/bugfixes - -Gajim 0.5.1 (27 February 2005) - - * Minor bugfixes. - -Gajim 0.5 (26 February 2005) - - * Possibility to use tabbed chat window - * Sound support under GNU/linux - * Autoaway available under Microsoft Windows - -Gajim 0.4.1 (23 January 2005) - - * Bugfix in config file parser (fix config file parser to handle emoticons) - * Bugfix with GPG signatures - -Gajim 0.4 (21 January 2005) - - * New option: regroup accounts - * Emoticons support with a binder - * GUI improvements - * Bugfixes - -Gajim 0.3 (18 December 2004) - - * GUI improvements - * group chat support with MUC (JEP 45) - * New agent browser (JEP 30) - * GnuPG support - * Autoconnect at startup - * New socket plugin - -Gajim 0.2.1 (1 July 2004) - - * bugfixes : when configfile is incomplete - * icon in systray with popup menu (for linux) - * "auto away even if not online" option - * always show contacts with unread messages - * new imageCellRenderer to show animated gifs - * allow agents unregistration - -Gajim 0.2 (8 June 2004) - - * bugfix for french translation - * multi-resource support - * auto away support (for linux) - * invisible support - * priority support - -Gajim 0.1 (21 May 2004) - - * Initial release. diff --git a/Changelog b/Changelog index f21490300..cf890cd9c 100644 --- a/Changelog +++ b/Changelog @@ -1,17 +1,19 @@ 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) + * Intoducing View Menu (GNOME HIG) + * GNOME Keyring Support (if GNOME keyring available, manage passwords and save them in an encrypted file). + * Ability to now hide the Transports group + * Support for notify-python so if notification-daemon is not available, we still can show cool popups + * Ability to 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) + * Ability to 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 + * Ability to 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 diff --git a/README b/README index 49fdbefff..87b9af82a 100644 --- a/README +++ b/README @@ -1,85 +1 @@ -Welcome and thanks for trying out Gajim. - -=RUNTIME REQUIREMENTS= -python2.4 or higher -pygtk2.6 or higher -python-libglade -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 -the xml lib that *comes* with python and not pyxml or whatever - -=COMPILE-TIME REQUIREMENTS= -python-dev -python-gtk2-dev -libgtk2.0-dev # aka. gtk2-devel -libxss-dev # for idle detection module (Some distributions (f.e. Debian) split xscreensaver) -libgtkspell-dev # for gtkspell module -intltool - -NOTE: -if you still have problems compiling, you may want to try removing the gtk1 series of the above dependencies - -Optionally: -dnsutils (or whatever package provides the nslookup binary) for SRV support; if you don't know what that is, you don't need it -gtkspell and aspell-LANG where lang is your locale eg. en, fr etc -GnomePythonExtras 2.10 or above so you can avoid compiling trayicon and gtkspell -notification-daemon or notify-python (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 -which is gnome dep - -=INSTALLATION PROCEDURE= -tar jxvf gajim-version.tar.bz2 -cd gajim -make # builds all modules -su -c make install - -To specify what modules to build do: -make help - -To specify where to install do: -su -c make PREFIX=custom_path install - -=RUNNING GAJIM= -gajim - -or if you didn't 'make install' you can also run from gajim folder with -./launch.sh - -Last but not least, you can run Gajim from your GNOME/XFCE/KDE/whatever menus. - -=UNINSTALLATION PROCEDURE= -su -c make uninstall -this will try to remove Gajim from the default directories. -If you want to remove it from custom directory provide it as: -make PREFIX=custom_path uninstall - -=MISCELLANEOUS= -XML & Debugging: -If you want to see the xml stanzas and/or help us debugging -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. - -=FAQ/Wiki= -FAQ can be found at http://trac.gajim.org/wiki/GajimFaq -Wiki can be found at http://trac.gajim.org/wiki - - -That is all, enjoy! - -(C) 2003-2006 -The Gajim Team -http://gajim.org - - -PS. -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. Thank you +see README.html diff --git a/README.html b/README.html new file mode 100644 index 000000000..3be3024eb --- /dev/null +++ b/README.html @@ -0,0 +1,130 @@ + + + + Gajim - Read Me + + + +

Gajim Read Me

+ +

+Welcome to Gajim and thank you for trying out our client. +

+ +

Runtime Requirements

+ + +

+Note to packagers +Gajim is a GTK+ app that loves GNOME. You can do 'make' so you don't require gnomepythonextras (aka gnome-python-desktop) which is gnome dep, but you will miss gnomekeyring intergration. +

+ +

Optional Runtime Requirements

+ + +

+Some distributions also split too much python standard library. +I know SUSE does. In such distros you also need python-xml +the xml lib that *comes* with python and not pyxml or whatever. +

+ +

Compile-time Requirements

+ + +

+NOTE: +If you still have problems compiling, you may want to try removing the gtk1 series of the above dependencies. +

+ +

Installation Procedure

+
    +
  1. tar jxvf gajim-version.tar.bz2
  2. +cd gajim +make (builds all modules) +su -c make install +
+ +

+To specify what modules to build do: +make help +

+ +

+To specify where to install do: +su -c make PREFIX=custom_path install +

+ +

Running Gajim

+

+Just do gajim or you can run Gajim from your GNOME/XFCE/KDE/whatever menus.

+ +or if you didn't 'make install' you can also run from gajim folder with +./launch.sh +

+ +

Uninstallation Procedure

+

+su -c make uninstall
+this will try to remove Gajim from the default directories. +If you want to remove it from custom directory provide it as:
+make PREFIX=custom_path uninstall +

+ +

Miscellaneous

+

XML & Debugging

+

+If you want to see the xml stanzas and/or help us debugging +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. +

+ +

FAQ/Wiki

+

+FAQ can be found at http://trac.gajim.org/wiki/GajimFaq
+Wiki can be found at http://trac.gajim.org/wiki +

+ + +

+That is all, enjoy! +

+ +

+
+
+
+(C) 2003-2006
+The Gajim Team
+http://gajim.org
+
+
+ +PS. +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. Thank you. +

+ diff --git a/data/glade/join_groupchat_window.glade b/data/glade/join_groupchat_window.glade index e6960a86a..618dff121 100644 --- a/data/glade/join_groupchat_window.glade +++ b/data/glade/join_groupchat_window.glade @@ -17,6 +17,7 @@ GDK_WINDOW_TYPE_HINT_NORMAL GDK_GRAVITY_NORTH_WEST True + False @@ -29,58 +30,14 @@ True - 5 + 4 2 False 6 12 - - True - True - True - False - 0 - - True - * - True - - - 1 - 2 - 4 - 5 - - - - - - - True - True - True - True - 0 - - True - * - True - - - - - 1 - 2 - 3 - 4 - - - - - - + True True True @@ -91,7 +48,6 @@ True * True - @@ -125,62 +81,6 @@ - - - True - Password: - False - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - 1 - 4 - 5 - fill - - - - - - - True - Server: - False - False - GTK_JUSTIFY_LEFT - False - False - 0 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - 1 - 3 - 4 - fill - - - - True @@ -281,6 +181,55 @@ fill + + + + True + Password: + False + False + GTK_JUSTIFY_LEFT + False + False + 0 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + 1 + 3 + 4 + fill + + + + + + + True + True + True + False + 0 + + True + * + True + + + 1 + 2 + 3 + 4 + + + 0 diff --git a/data/glade/preferences_window.glade b/data/glade/preferences_window.glade index 573c182a8..d9d2165ce 100644 --- a/data/glade/preferences_window.glade +++ b/data/glade/preferences_window.glade @@ -900,7 +900,6 @@ Per type True True - Επιλογή μιας γραμματοσειράς True True False @@ -952,7 +951,6 @@ Per type True True False - Επιλογή χρώματος True @@ -971,7 +969,6 @@ Per type True True False - Επιλογή χρώματος True @@ -1051,7 +1048,6 @@ Per type True True False - Επιλογή χρώματος True @@ -1070,7 +1066,6 @@ Per type True True False - Επιλογή χρώματος True diff --git a/data/glade/vcard_information_window.glade b/data/glade/vcard_information_window.glade index 3b88416c0..f677804ff 100644 --- a/data/glade/vcard_information_window.glade +++ b/data/glade/vcard_information_window.glade @@ -448,7 +448,7 @@ - + True Subscription: False @@ -476,7 +476,7 @@ - + True Ask: False diff --git a/po/POTFILES.in b/po/POTFILES.in deleted file mode 100644 index 5bf9ba2c6..000000000 --- a/po/POTFILES.in +++ /dev/null @@ -1,114 +0,0 @@ -# ls data/gajim.desktop.in.in data/glade/*.glade src/*py -# src/common/*py src/common/zeroconf/*.py -1 -U -# to produce this list - -[encoding: UTF-8] -data/gajim.desktop.in.in -data/glade/account_context_menu.glade -data/glade/account_creation_wizard_window.glade -data/glade/account_modification_window.glade -data/glade/accounts_window.glade -data/glade/add_new_contact_window.glade -data/glade/advanced_configuration_window.glade -data/glade/advanced_menuitem_menu.glade -data/glade/advanced_notifications_window.glade -data/glade/change_password_dialog.glade -data/glade/change_status_message_dialog.glade -data/glade/chat_context_menu.glade -data/glade/chat_control_popup_menu.glade -data/glade/choose_gpg_key_dialog.glade -data/glade/data_form_window.glade -data/glade/edit_groups_dialog.glade -data/glade/filetransfers.glade -data/glade/gajim_themes_window.glade -data/glade/gc_control_popup_menu.glade -data/glade/gc_occupants_menu.glade -data/glade/history_manager.glade -data/glade/history_window.glade -data/glade/input_dialog.glade -data/glade/invitation_received_dialog.glade -data/glade/join_groupchat_window.glade -data/glade/manage_accounts_window.glade -data/glade/manage_bookmarks_window.glade -data/glade/manage_proxies_window.glade -data/glade/message_window.glade -data/glade/passphrase_dialog.glade -data/glade/popup_notification_window.glade -data/glade/preferences_window.glade -data/glade/privacy_lists_window.glade -data/glade/privacy_list_window.glade -data/glade/profile_window.glade -data/glade/progress_dialog.glade -data/glade/remove_account_window.glade -data/glade/roster_contact_context_menu.glade -data/glade/roster_window.glade -data/glade/service_discovery_window.glade -data/glade/service_registration_window.glade -data/glade/single_message_window.glade -data/glade/subscription_request_popup_menu.glade -data/glade/subscription_request_window.glade -data/glade/systray_context_menu.glade -data/glade/vcard_information_window.glade -data/glade/xml_console_window.glade -data/glade/zeroconf_contact_context_menu.glade -data/glade/zeroconf_context_menu.glade -data/glade/zeroconf_information_window.glade -data/glade/zeroconf_properties_window.glade -src/advanced.py -src/cell_renderer_image.py -src/chat_control.py -src/config.py -src/conversation_textview.py -src/dbus_support.py -src/dialogs.py -src/disco.py -src/filetransfers_window.py -src/gajim.py -src/gajim-remote.py -src/gajim_themes_window.py -src/groupchat_control.py -src/gtkexcepthook.py -src/gtkgui_helpers.py -src/history_manager.py -src/history_window.py -src/htmltextview.py -src/message_control.py -src/message_textview.py -src/message_window.py -src/music_track_listener.py -src/notify.py -src/profile_window.py -src/remote_control.py -src/roster_window.py -src/rst_xhtml_generator.py -src/systray.py -src/systraywin32.py -src/tooltips.py -src/vcard.py -src/common/check_paths.py -src/common/config.py -src/common/connection_handlers.py -src/common/connection.py -src/common/contacts.py -src/common/dbus_support.py -src/common/events.py -src/common/exceptions.py -src/common/fuzzyclock.py -src/common/gajim.py -src/common/GnuPGInterface.py -src/common/GnuPG.py -src/common/helpers.py -src/common/i18n.py -src/common/__init__.py -src/common/logger.py -src/common/nslookup.py -src/common/optparser.py -src/common/proxy65_manager.py -src/common/sleepy.py -src/common/socks5.py -src/common/xmpp_stringprep.py -src/common/zeroconf/client_zeroconf.py -src/common/zeroconf/connection_handlers_zeroconf.py -src/common/zeroconf/connection_zeroconf.py -src/common/zeroconf/__init__.py -src/common/zeroconf/roster_zeroconf.py diff --git a/po/de.po b/po/de.po index 0c265fdc8..226bdd27e 100644 --- a/po/de.po +++ b/po/de.po @@ -373,7 +373,7 @@ msgstr "Konto-Status mit globalem Status abgleichen" #: ../data/glade/account_modification_window.glade.h:40 msgid "Use _SSL (legacy)" -msgstr "_SSL verwenden" +msgstr "_SSL verwenden (veraltet)" #: ../data/glade/account_modification_window.glade.h:41 msgid "Use custom hostname/port" @@ -4485,7 +4485,7 @@ msgstr "für Konto %s" #. History manager #: ../src/roster_window.py:933 msgid "History Manager" -msgstr "Verlaufmanager" +msgstr "Verlaufsmanager" #: ../src/roster_window.py:942 msgid "_Join New Room" diff --git a/src/advanced.py b/src/advanced.py index c0b8df496..0640c02d7 100644 --- a/src/advanced.py +++ b/src/advanced.py @@ -1,18 +1,8 @@ ## advanced.py ## -## Contributors for this file: -## - Yann Le Boulanger -## - Nikos Kouremenos -## - Vincent Hanquez -## -## 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 Vincent Hanquez ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published @@ -42,7 +32,7 @@ C_TYPE GTKGUI_GLADE = 'manage_accounts_window.glade' -class AdvancedConfigurationWindow: +class AdvancedConfigurationWindow(object): def __init__(self): self.xml = gtkgui_helpers.get_glade('advanced_configuration_window.glade') self.window = self.xml.get_widget('advanced_configuration_window') diff --git a/src/chat_control.py b/src/chat_control.py index 9c5e33efa..4b4592f8d 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -34,7 +34,7 @@ from message_textview import MessageTextView from common.contacts import GC_Contact from common.logger import Constants constants = Constants() -from rst_xhtml_generator import create_xhtml +from common.rst_xhtml_generator import create_xhtml from common.xmpp.protocol import NS_XHTML try: @@ -1045,7 +1045,7 @@ class ChatControl(ChatControlBase): status = contact.status if status is not None: banner_name_label.set_ellipsize(pango.ELLIPSIZE_END) - status = gtkgui_helpers.reduce_chars_newlines(status, max_lines = 2) + status = helpers.reduce_chars_newlines(status, max_lines = 2) status_escaped = gtkgui_helpers.escape_for_pango_markup(status) font_attrs, font_attrs_small = self.get_font_attrs() @@ -1254,7 +1254,9 @@ class ChatControl(ChatControlBase): kind = 'outgoing' name = gajim.nicks[self.account] if not xhtml and not encrypted and gajim.config.get('rst_formatting_outgoing_messages'): - xhtml = '%s' % (NS_XHTML, create_xhtml(text)) + xhtml = create_xhtml(text) + if xhtml: + xhtml = '%s' % (NS_XHTML, xhtml) ChatControlBase.print_conversation_line(self, text, kind, name, tim, subject = subject, old_kind = self.old_msg_kind, xhtml = xhtml) if text.startswith('/me ') or text.startswith('/me\n'): @@ -1674,7 +1676,7 @@ class ChatControl(ChatControlBase): else: kind = 'print_queue' self.print_conversation(data[0], kind, tim = data[3], - encrypted = data[4], subject = data[1]) + encrypted = data[4], subject = data[1], xhtml = data[7]) if len(data) > 6 and isinstance(data[6], int): message_ids.append(data[6]) if message_ids: diff --git a/src/common/check_paths.py b/src/common/check_paths.py index a6bc52bf1..a504c054d 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -1,16 +1,7 @@ -## Contributors for this file: -## - Yann Le Boulanger -## - Nikos Kouremenos -## - Travis Shirk ## -## 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 Travis Shirk ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published @@ -57,11 +48,13 @@ def create_log_db(): jid_id INTEGER ); + CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id); + CREATE TABLE transports_cache ( transport TEXT UNIQUE, type INTEGER ); - + CREATE TABLE logs( log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE, jid_id INTEGER, @@ -72,6 +65,8 @@ def create_log_db(): message TEXT, subject TEXT ); + + CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind); ''' ) diff --git a/src/common/config.py b/src/common/config.py index 7527528f3..960b39516 100644 --- a/src/common/config.py +++ b/src/common/config.py @@ -94,7 +94,7 @@ class Config: 'show_ascii_formatting_chars': [ opt_bool, True , _('If True, do not ' 'remove */_ . So *abc* will be bold but with * * not removed.')], 'rst_formatting_outgoing_messages': [ opt_bool, False, - _('Uses ReStructured text markup for HTML, plus ascii formatting if selected.')], + _('Uses ReStructured text markup for HTML, plus ascii formatting if selected. (If you want to use this, install docutils)')], 'sounds_on': [ opt_bool, True ], # 'aplay', 'play', 'esdplay', 'artsplay' detected first time only 'soundplayer': [ opt_str, '' ], @@ -143,7 +143,7 @@ class Config: 'send_on_ctrl_enter': [opt_bool, False, _('Send message on Ctrl+Enter and with Enter make new line (Mirabilis ICQ Client default behaviour).')], 'show_roster_on_startup': [opt_bool, True], 'key_up_lines': [opt_int, 25, _('How many lines to store for Ctrl+KeyUP.')], - 'version': [ opt_str, '0.10.1.3' ], # which version created the config + 'version': [ opt_str, '0.10.1.5' ], # which version created the config 'search_engine': [opt_str, 'http://www.google.com/search?&q=%s&sourceid=gajim'], 'dictionary_url': [opt_str, 'WIKTIONARY', _("Either custom url with %s in it where %s is the word/phrase or 'WIKTIONARY' which means use wiktionary.")], 'always_english_wikipedia': [opt_bool, False], diff --git a/src/common/connection.py b/src/common/connection.py index d59a0add0..72b2767ae 100644 --- a/src/common/connection.py +++ b/src/common/connection.py @@ -38,11 +38,12 @@ import common.xmpp from common import helpers from common import gajim from common import GnuPG +from common import passwords from connection_handlers import * USE_GPG = GnuPG.USE_GPG -from rst_xhtml_generator import create_xhtml +from common.rst_xhtml_generator import create_xhtml class Connection(ConnectionHandlers): '''Connection class''' @@ -69,7 +70,7 @@ class Connection(ConnectionHandlers): self.last_io = gajim.idlequeue.current_time() self.last_sent = [] self.last_history_line = {} - self.password = gajim.config.get_per('accounts', name, 'password') + self.password = passwords.get_password(name) self.server_resource = gajim.config.get_per('accounts', name, 'resource') if gajim.config.get_per('accounts', self.name, 'keep_alives_enabled'): self.keepalives = gajim.config.get_per('accounts', self.name,'keep_alive_every_foo_secs') @@ -687,7 +688,7 @@ class Connection(ConnectionHandlers): user_nick = None, xhtml = None): if not self.connection: return - if not xhtml and gajim.config.get('rst_formatting_outgoing_messages'): + if msg and not xhtml and gajim.config.get('rst_formatting_outgoing_messages'): xhtml = create_xhtml(msg) if not msg and chatstate is None: return @@ -977,14 +978,15 @@ class Connection(ConnectionHandlers): p = self.add_sha(p, ptype != 'unavailable') self.connection.send(p) - def join_gc(self, nick, room, server, password): + def join_gc(self, nick, room_jid, password): + # FIXME: This room JID needs to be normalized; see #1364 if not self.connection: return show = helpers.get_xmpp_show(STATUS_LIST[self.connected]) if show == 'invisible': # Never join a room when invisible return - p = common.xmpp.Presence(to = '%s@%s/%s' % (room, server, nick), + p = common.xmpp.Presence(to = '%s/%s' % (room_jid, nick), show = show, status = self.status) if gajim.config.get('send_sha_in_gc_presence'): p = self.add_sha(p) @@ -993,12 +995,11 @@ class Connection(ConnectionHandlers): t.setTagData('password', password) self.connection.send(p) #last date/time in history to avoid duplicate - # FIXME: This JID needs to be normalized; see #1364 - jid='%s@%s' % (room, server) - last_log = gajim.logger.get_last_date_that_has_logs(jid, is_room = True) + last_log = gajim.logger.get_last_date_that_has_logs(room_jid, + is_room = True) if last_log is None: last_log = 0 - self.last_history_line[jid]= last_log + self.last_history_line[room_jid]= last_log def send_gc_message(self, jid, msg, xhtml = None): if not self.connection: diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py index 3e955b35a..326b8d478 100644 --- a/src/common/connection_handlers.py +++ b/src/common/connection_handlers.py @@ -712,6 +712,7 @@ class ConnectionDisco: q.addChild('feature', attrs = {'var': common.xmpp.NS_SI}) q.addChild('feature', attrs = {'var': common.xmpp.NS_FILE}) q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC}) + q.addChild('feature', attrs = {'var': common.xmpp.NS_XHTML_IM}) self.connection.send(iq) raise common.xmpp.NodeProcessed @@ -819,6 +820,8 @@ class ConnectionVcard: puny_jid = helpers.sanitize_filename(jid) path = os.path.join(gajim.VCARD_PATH, puny_jid) if jid in self.room_jids or os.path.isdir(path): + if not nick: + return # remove room_jid file if needed if os.path.isfile(path): os.remove(path) diff --git a/src/common/exceptions.py b/src/common/exceptions.py index 4731829f5..bceef79a4 100644 --- a/src/common/exceptions.py +++ b/src/common/exceptions.py @@ -1,11 +1,7 @@ ## exceptions.py ## -## Contributors for this file: -## - Yann Le Boulanger -## - -## ## Copyright (C) 2005-2006 Yann Le Boulanger -## Nikos Kouremenos +## Copyright (C) 2005-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 @@ -48,3 +44,12 @@ class SessionBusNotPresent(Exception): def __str__(self): return _('Session bus is not available.\nTry reading http://trac.gajim.org/wiki/GajimDBus') + +class GajimGeneralException(Exception): + '''This exception ir our general exception''' + def __init__(self, text=''): + Exception.__init__(self) + self.text = text + + def __str__(self): + return self.text diff --git a/src/common/gajim.py b/src/common/gajim.py index 03c04ac45..e353f284c 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -139,10 +139,10 @@ def get_nick_from_fjid(jid): # gaim@conference.jabber.no/nick/nick-continued return jid.split('/', 1)[1] -def get_room_name_and_server_from_room_jid(jid): - room_name = get_nick_from_jid(jid) +def get_name_and_server_from_jid(jid): + name = get_nick_from_jid(jid) server = get_server_from_jid(jid) - return room_name, server + return name, server def get_room_and_nick_from_fjid(jid): # fake jid is the jid for a contact in a room @@ -335,7 +335,8 @@ def get_priority(account, show): '''return the priority an account must have''' if not show: show = 'online' - if show in priority_dict and config.get_per('accounts', account, - 'adjust_priority_with_status'): - return priority_dict[show] + + if show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible') and \ + config.get_per('accounts', account, 'adjust_priority_with_status'): + return config.get_per('accounts', account, 'autopriority_' + show) return config.get_per('accounts', account, 'priority') diff --git a/src/common/helpers.py b/src/common/helpers.py index 96a1b790e..c482427b7 100644 --- a/src/common/helpers.py +++ b/src/common/helpers.py @@ -28,8 +28,10 @@ from encodings.punycode import punycode_encode import gajim from i18n import Q_ +from i18n import ngettext from xmpp_stringprep import nodeprep, resourceprep, nameprep + try: import winsound # windows-only built-in module for playing wav import win32api @@ -814,3 +816,110 @@ def get_chat_control(account, contact): highest_contact.resource: return None return gajim.interface.msg_win_mgr.get_control(contact.jid, account) + +def reduce_chars_newlines(text, max_chars = 0, max_lines = 0): + '''Cut the chars after 'max_chars' on each line + and show only the first 'max_lines'. + If any of the params is not present (None or 0) the action + on it is not performed''' + + def _cut_if_long(string): + if len(string) > max_chars: + string = string[:max_chars - 3] + '...' + return string + + if isinstance(text, str): + text = text.decode('utf-8') + + if max_lines == 0: + lines = text.split('\n') + else: + lines = text.split('\n', max_lines)[:max_lines] + if max_chars > 0: + if lines: + lines = map(lambda e: _cut_if_long(e), lines) + if lines: + reduced_text = reduce(lambda e, e1: e + '\n' + e1, lines) + else: + reduced_text = '' + return reduced_text + +def get_notification_icon_tooltip_text(): + text = None + unread_chat = gajim.events.get_nb_events(types = ['printed_chat', + 'chat']) + unread_single_chat = gajim.events.get_nb_events(types = ['normal']) + unread_gc = gajim.events.get_nb_events(types = ['printed_gc_msg', + 'gc_msg']) + unread_pm = gajim.events.get_nb_events(types = ['printed_pm', 'pm']) + + accounts = get_accounts_info() + + if unread_chat or unread_single_chat or unread_gc or unread_pm: + text = 'Gajim ' + awaiting_events = unread_chat + unread_single_chat + unread_gc + unread_pm + if awaiting_events == unread_chat or awaiting_events == unread_single_chat \ + or awaiting_events == unread_gc or awaiting_events == unread_pm: + # This condition is like previous if but with xor... + # Print in one line + text += '-' + else: + # Print in multiple lines + text += '\n ' + if unread_chat: + text += ngettext( + ' %d unread message', + ' %d unread messages', + unread_chat, unread_chat, unread_chat) + text += '\n ' + if unread_single_chat: + text += ngettext( + ' %d unread single message', + ' %d unread single messages', + unread_single_chat, unread_single_chat, unread_single_chat) + text += '\n ' + if unread_gc: + text += ngettext( + ' %d unread group chat message', + ' %d unread group chat messages', + unread_gc, unread_gc, unread_gc) + text += '\n ' + if unread_pm: + text += ngettext( + ' %d unread private message', + ' %d unread private messages', + unread_pm, unread_pm, unread_pm) + text += '\n ' + text = text[:-4] # remove latest '\n ' + elif len(accounts) > 1: + text = _('Gajim') + elif len(accounts) == 1: + message = accounts[0]['status_line'] + message = reduce_chars_newlines(message, 100, 1) + text = _('Gajim - %s') % message + else: + text = _('Gajim - %s') % get_uf_show('offline') + + return text + +def get_accounts_info(): + '''helper for notification icon tooltip''' + accounts = [] + accounts_list = gajim.contacts.get_accounts() + accounts_list.sort() + for account in accounts_list: + status_idx = gajim.connections[account].connected + # uncomment the following to hide offline accounts + # if status_idx == 0: continue + status = gajim.SHOW_LIST[status_idx] + message = gajim.connections[account].status + single_line = get_uf_show(status) + if message is None: + message = '' + else: + message = message.strip() + if message != '': + single_line += ': ' + message + accounts.append({'name': account, 'status_line': single_line, + 'show': status, 'message': message}) + return accounts diff --git a/src/common/logger.py b/src/common/logger.py index dfc06941d..5a3343f18 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -1,17 +1,7 @@ ## logger.py ## -## Contributors for this file: -## - Yann Le Boulanger -## - Nikos Kouremenos -## -## 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 Nikos Kouremenos +## Copyright (C) 2005-2006 Yann Le Boulanger ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published @@ -136,9 +126,11 @@ class Logger: and after that all okay''' possible_room_jid, possible_nick = jid.split('/', 1) + return self.jid_is_room_jid(possible_room_jid) + def jid_is_room_jid(self, jid): self.cur.execute('SELECT jid_id FROM jids WHERE jid=? AND type=?', - (possible_room_jid, constants.JID_ROOM_TYPE)) + (jid, constants.JID_ROOM_TYPE)) row = self.cur.fetchone() if row is None: return False diff --git a/src/common/optparser.py b/src/common/optparser.py index 69e736c0a..596e99d0b 100644 --- a/src/common/optparser.py +++ b/src/common/optparser.py @@ -126,8 +126,7 @@ class OptionsParser: os.chmod(self.__filename, 0600) def update_config(self, old_version, new_version): - # Convert '0.x.y' to (0, x, y) - old_version_list = old_version.split('.') + old_version_list = old_version.split('.') # convert '0.x.y' to (0, x, y) old = [] while len(old_version_list): old.append(int(old_version_list.pop(0))) @@ -146,7 +145,11 @@ class OptionsParser: self.update_config_to_01012() if old < [0, 10, 1, 3] and new >= [0, 10, 1, 3]: self.update_config_to_01013() - + if old < [0, 10, 1, 4] and new >= [0, 10, 1, 4]: + self.update_config_to_01014() + if old < [0, 10, 1, 5] and new >= [0, 10, 1, 5]: + self.update_config_to_01015() + gajim.logger.init_vars() gajim.config.set('version', new_version) @@ -301,3 +304,46 @@ class OptionsParser: pass con.close() gajim.config.set('version', '0.10.1.3') + + def update_config_to_01014(self): + '''apply indeces to the logs database''' + import exceptions + try: + from pysqlite2 import dbapi2 as sqlite + except ImportError: + raise exceptions.PysqliteNotAvailable + import logger + print _('migrating logs database to indeces') + con = sqlite.connect(logger.LOG_DB_PATH) + cur = con.cursor() + # apply indeces + try: + cur.executescript( + ''' + CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind); + CREATE INDEX idx_unread_messages_jid_id ON unread_messages (jid_id); + ''' + ) + + con.commit() + except: + pass + con.close() + gajim.config.set('version', '0.10.1.4') + + def update_config_to_01015(self): + '''clean show values in logs database''' + from pysqlite2 import dbapi2 as sqlite + import logger + con = sqlite.connect(logger.LOG_DB_PATH) + cur = con.cursor() + status = dict((i[5:].lower(), logger.constants.__dict__[i]) for i in \ + logger.constants.__dict__.keys() if i.startswith('SHOW_')) + for show in status: + cur.execute('update logs set show = ? where show = ?;', (status[show], + show)) + cur.execute('update logs set show = NULL where show not in (0, 1, 2, 3, 4, 5);') + con.commit() + cur.close() + con.close() + gajim.config.set('version', '0.10.1.5') diff --git a/src/common/passwords.py b/src/common/passwords.py new file mode 100644 index 000000000..3c7b7237c --- /dev/null +++ b/src/common/passwords.py @@ -0,0 +1,85 @@ +## +## Copyright (C) 2006 Gustavo J. A. M. 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. +## + +__all__ = ['get_password', 'save_password'] + +import gobject + +from common import gajim + +try: + import gnomekeyring +except ImportError: + USER_HAS_GNOMEKEYRING = False +else: + USER_HAS_GNOMEKEYRING = True + + +class SimplePasswordStorage(object): + def get_password(self, account_name): + return gajim.config.get_per('accounts', account_name, 'password') + + def save_password(self, account_name, password): + gajim.connections[account_name].password = password + + +class GnomePasswordStorage(object): + def __init__(self): + self.keyring = gnomekeyring.get_default_keyring_sync() + + def get_password(self, account_name): + conf = gajim.config.get_per('accounts', account_name, 'password') + if conf is None: + return None + try: + unused, auth_token = conf.split('gnomekeyring:') + auth_token = int(auth_token) + except ValueError: + password = conf + ## migrate the password over to keyring + self.save_password(account_name, password, update=False) + return password + try: + return gnomekeyring.item_get_info_sync(self.keyring, + auth_token).get_secret() + except gnomekeyring.DeniedError: + return None + + def save_password(self, account_name, password, update=True): + display_name = _('Gajim account %s') % account_name + attributes = dict(account_name=str(account_name), gajim=1) + auth_token = gnomekeyring.item_create_sync( + self.keyring, gnomekeyring.ITEM_GENERIC_SECRET, + display_name, attributes, password, update) + token = 'gnomekeyring:%i' % auth_token + gajim.config.set_per('accounts', account_name, 'password', token) + + +storage = None +def get_storage(): + global storage + if storage is None: # None is only in first time get_storage is called + if USER_HAS_GNOMEKEYRING: + #FIXME: detect if we're running under GNOME or not + #before deciding to use the GnomeKeyring backend + storage = GnomePasswordStorage() + else: + storage = SimplePasswordStorage() + return storage + +def get_password(account_name): + return get_storage().get_password(account_name) + +def save_password(account_name, password): + return get_storage().save_password(account_name, password) diff --git a/src/common/rst_xhtml_generator.py b/src/common/rst_xhtml_generator.py new file mode 100644 index 000000000..c69771506 --- /dev/null +++ b/src/common/rst_xhtml_generator.py @@ -0,0 +1,126 @@ +## rst_xhtml_generator.py +## +## Copyright (C) 2006 Yann Le Boulanger +## Copyright (C) 2006 Nikos Kouremenos +## Copyright (C) 2006 Santiago Gala +## +## 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. +## + +try: + from docutils import io + from docutils.core import Publisher + from docutils.parsers.rst import roles + from docutils import nodes,utils + from docutils.parsers.rst.roles import set_classes +except: + def create_xhtml(text): + return None +else: + def jep_reference_role(role, rawtext, text, lineno, inliner, + options={}, content=[]): + '''Role to make handy references to Jabber Enhancement Proposals (JEP). + + Use as :JEP:`71` (or jep, or jep-reference). + Modeled after the sample in docutils documentation. + ''' + + jep_base_url = 'http://www.jabber.org/jeps/' + jep_url = 'jep-%04d.html' + try: + jepnum = int(text) + if jepnum <= 0: + raise ValueError + except ValueError: + msg = inliner.reporter.error( + 'JEP number must be a number greater than or equal to 1; ' + '"%s" is invalid.' % text, line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + return [prb], [msg] + ref = jep_base_url + jep_url % jepnum + set_classes(options) + node = nodes.reference(rawtext, 'JEP ' + utils.unescape(text), refuri=ref, + **options) + return [node], [] + + roles.register_canonical_role('jep-reference', jep_reference_role) + from docutils.parsers.rst.languages.en import roles + roles['jep-reference'] = 'jep-reference' + roles['jep'] = 'jep-reference' + + class HTMLGenerator: + '''Really simple HTMLGenerator starting from publish_parts. + + It reuses the docutils.core.Publisher class, which means it is *not* + threadsafe. + ''' + def __init__(self, + settings_spec=None, + settings_overrides=dict(report_level=5, halt_level=5), + config_section='general'): + self.pub = Publisher(reader=None, parser=None, writer=None, + settings=None, + source_class=io.StringInput, + destination_class=io.StringOutput) + self.pub.set_components(reader_name='standalone', + parser_name='restructuredtext', + writer_name='html') + # hack: JEP-0071 does not allow HTML char entities, so we hack our way + # out of it. + # — == u"\u2014" + # a setting to only emit charater entities in the writer would be nice + # FIXME: several   are emitted, and they are explicitly forbidden + # in the JEP + #   == u"\u00a0" + self.pub.writer.translator_class.attribution_formats['dash'] = ( + u'\u2014', '') + self.pub.process_programmatic_settings(settings_spec, + settings_overrides, + config_section) + + + def create_xhtml(self, text, + destination=None, + destination_path=None, + enable_exit_status=None): + ''' Create xhtml for a fragment of IM dialog. + We can use the source_name to store info about + the message.''' + self.pub.set_source(text, None) + self.pub.set_destination(destination, destination_path) + output = self.pub.publish(enable_exit_status=enable_exit_status) + # kludge until we can get docutils to stop generating (rare)   + # entities + return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split( + ' ')) + + Generator = HTMLGenerator() + + def create_xhtml(text): + return Generator.create_xhtml(text) + + +if __name__ == '__main__': + print Generator.create_xhtml(''' +test:: + + >>> print 1 + 1 + +*I* like it. It is for :JEP:`71` + +this `` should trigger`` should trigger the   problem. + +''') + print Generator.create_xhtml(''' +*test1 + +test2_ +''') diff --git a/src/config.py b/src/config.py index 0db7eccfb..cf1676bd7 100644 --- a/src/config.py +++ b/src/config.py @@ -38,8 +38,11 @@ except: from common import helpers from common import gajim from common import connection +from common import passwords from common import zeroconf +from common.exceptions import GajimGeneralException as GajimGeneralException + #---------- PreferencesWindow class -------------# class PreferencesWindow: '''Class for Preferences window''' @@ -435,8 +438,7 @@ class PreferencesWindow: self.xml.get_widget('custom_apps_frame').set_no_show_all(True) if gajim.config.get('autodetect_browser_mailer'): self.applications_combobox.set_active(0) - gtkgui_helpers.autodetect_browser_mailer() - # autodetect_browser_mailer is now False. + # else autodetect_browser_mailer is False. # so user has 'Always Use GNOME/KDE' or Custom elif gajim.config.get('openwith') == 'gnome-open': self.applications_combobox.set_active(1) @@ -1324,6 +1326,8 @@ class AccountModificationWindow: config['password'] = self.xml.get_widget('password_entry').get_text().\ decode('utf-8') config['resource'] = resource + config['adjust_priority_with_status'] = self.xml.get_widget( + 'adjust_priority_with_status_checkbutton').get_active() config['priority'] = self.xml.get_widget('priority_spinbutton').\ get_value_as_int() config['autoconnect'] = self.xml.get_widget('autoconnect_checkbutton').\ @@ -1442,27 +1446,28 @@ class AccountModificationWindow: # check if relogin is needed relogin_needed = False if self.options_changed_need_relogin(config, - ('resource', 'proxy', 'usessl', 'keyname', - 'use_custom_host', 'custom_host')): + ('resource', 'proxy', 'usessl', 'keyname', + 'use_custom_host', 'custom_host')): relogin_needed = True elif config['use_custom_host'] and (self.option_changed(config, - 'custom_host') or self.option_changed(config, 'custom_port')): + 'custom_host') or self.option_changed(config, 'custom_port')): relogin_needed = True if self.option_changed(config, 'use_ft_proxies') and \ config['use_ft_proxies']: gajim.connections[self.account].discover_ft_proxies() - if self.option_changed(config, 'priority'): + if self.option_changed(config, 'priority') or self.option_changed( + config, 'adjust_priority_with_status'): resend_presence = True for opt in config: gajim.config.set_per('accounts', name, opt, config[opt]) if config['savepass']: - gajim.connections[name].password = config['password'] + passwords.save_password(name, config['password']) else: - gajim.connections[name].password = None + passwords.save_password(name, None) # refresh accounts window if gajim.interface.instances.has_key('accounts'): gajim.interface.instances['accounts'].init_accounts() @@ -1511,7 +1516,7 @@ class AccountModificationWindow: def on_change_password_button_clicked(self, widget): try: dialog = dialogs.ChangePasswordDialog(self.account) - except RuntimeError: + except GajimGeneralException: #if we showed ErrorDialog, there will not be dialog instance return @@ -2497,10 +2502,10 @@ class ManageBookmarksWindow: self.window = self.xml.get_widget('manage_bookmarks_window') self.window.set_transient_for(gajim.interface.roster.window) - #Account-JID, RoomName, Room-JID, Autojoin, Passowrd, Nick, Show_Status + # Account-JID, RoomName, Room-JID, Autojoin, Passowrd, Nick, Show_Status self.treestore = gtk.TreeStore(str, str, str, bool, str, str, str) - #Store bookmarks in treeview. + # Store bookmarks in treeview. for account in gajim.connections: if gajim.connections[account].connected <= 1: continue @@ -2605,7 +2610,7 @@ class ManageBookmarksWindow: account = model[add_to][1].decode('utf-8') nick = gajim.nicks[account] - self.treestore.append(add_to, [account, _('New Room'), '', False, '', + self.treestore.append(add_to, [account, _('New Group Chat'), '', False, '', nick, 'in_and_out']) self.view.expand_row(model.get_path(add_to), True) @@ -2943,7 +2948,7 @@ class AccountCreationWizardWindow: self.account = server + str(i) i += 1 - username, server = gajim.get_room_name_and_server_from_room_jid(jid) + username, server = gajim.get_name_and_server_from_jid(jid) self.save_account(username, server, savepass, password) self.cancel_button.hide() self.back_button.hide() diff --git a/src/conversation_textview.py b/src/conversation_textview.py index 18e907a6f..ae0f1474e 100644 --- a/src/conversation_textview.py +++ b/src/conversation_textview.py @@ -1,17 +1,8 @@ ## conversation_textview.py ## -## Contributors for this file: -## - Yann Le Boulanger -## - Nikos Kouremenos -## -## 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 Travis Shirk ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published @@ -40,13 +31,17 @@ from calendar import timegm from common.fuzzyclock import FuzzyClock from htmltextview import HtmlTextView - +from common.exceptions import GajimGeneralException as GajimGeneralException class ConversationTextview: '''Class for the conversation textview (where user reads already said messages) for chat/groupchat windows''' - def __init__(self, account): - # no need to inherit TextView, use it as property is safer + def __init__(self, account, used_in_history_window = False): + '''if used_in_history_window is True, then we do not show + Clear menuitem in context menu''' + self.used_in_history_window = used_in_history_window + + # no need to inherit TextView, use it as atrribute is safer self.tv = HtmlTextView() self.tv.html_hyperlink_handler = self.html_hyperlink_handler @@ -61,11 +56,13 @@ class ConversationTextview: self.handlers = {} # connect signals - id = self.tv.connect('motion_notify_event', self.on_textview_motion_notify_event) + id = self.tv.connect('motion_notify_event', + self.on_textview_motion_notify_event) self.handlers[id] = self.tv id = self.tv.connect('populate_popup', self.on_textview_populate_popup) self.handlers[id] = self.tv - id = self.tv.connect('button_press_event', self.on_textview_button_press_event) + id = self.tv.connect('button_press_event', + self.on_textview_button_press_event) self.handlers[id] = self.tv self.account = account @@ -154,7 +151,7 @@ class ConversationTextview: self.handlers[i].disconnect(i) del self.handlers self.tv.destroy() - #TODO + #FIXME: # self.line_tooltip.destroy() def update_tags(self): @@ -320,19 +317,29 @@ class ConversationTextview: def on_textview_populate_popup(self, textview, menu): '''we override the default context menu and we prepend Clear + (only if used_in_history_window is False) and if we have sth selected we show a submenu with actions on the phrase (see on_conversation_textview_button_press_event)''' - item = gtk.SeparatorMenuItem() - menu.prepend(item) - item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) - menu.prepend(item) - id = item.connect('activate', self.clear) - self.handlers[id] = item + + separator_menuitem_was_added = False + if not self.used_in_history_window: + item = gtk.SeparatorMenuItem() + menu.prepend(item) + separator_menuitem_was_added = True + + item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) + menu.prepend(item) + id = item.connect('activate', self.clear) + self.handlers[id] = item + if self.selected_phrase: - s = self.selected_phrase - if len(s) > 25: - s = s[:21] + '...' - item = gtk.MenuItem(_('Actions for "%s"') % s) + if not separator_menuitem_was_added: + item = gtk.SeparatorMenuItem() + menu.prepend(item) + + self.selected_phrase = helpers.reduce_chars_newlines( + self.selected_phrase, 25) + item = gtk.MenuItem(_('_Actions for "%s"') % self.selected_phrase) menu.prepend(item) submenu = gtk.Menu() item.set_submenu(submenu) @@ -369,7 +376,8 @@ class ConversationTextview: item.set_property('sensitive', False) else: link = dict_link % self.selected_phrase - id = item.connect('activate', self.visit_url_from_menuitem, link) + id = item.connect('activate', self.visit_url_from_menuitem, + link) self.handlers[id] = item submenu.append(item) @@ -385,13 +393,17 @@ class ConversationTextview: id = item.connect('activate', self.visit_url_from_menuitem, link) self.handlers[id] = item submenu.append(item) + + item = gtk.MenuItem(_('Open as _Link')) + id = item.connect('activate', self.visit_url_from_menuitem, link) + self.handlers[id] = item + submenu.append(item) menu.show_all() def on_textview_button_press_event(self, widget, event): # If we clicked on a taged text do NOT open the standard popup menu # if normal text check if we have sth selected - self.selected_phrase = '' if event.button != 3: # if not right click @@ -430,18 +442,16 @@ class ConversationTextview: def on_start_chat_activate(self, widget, jid): gajim.interface.roster.new_chat_from_jid(self.account, jid) - def on_join_group_chat_menuitem_activate(self, widget, jid): - room, server = jid.split('@') - if gajim.interface.instances[self.account].has_key('join_gc'): + def on_join_group_chat_menuitem_activate(self, widget, room_jid): + if 'join_gc' in gajim.interface.instances[self.account]: instance = gajim.interface.instances[self.account]['join_gc'] - instance.xml.get_widget('server_entry').set_text(server) - instance.xml.get_widget('room_entry').set_text(room) + instance.xml.get_widget('room_jid_entry').set_text(room_jid) gajim.interface.instances[self.account]['join_gc'].window.present() else: try: gajim.interface.instances[self.account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(self.account, server, room) - except RuntimeError: + dialogs.JoinGroupchatWindow(self.account, room_jid) + except GajimGeneralException: pass def on_add_to_roster_activate(self, widget, jid): @@ -463,6 +473,7 @@ class ConversationTextview: childs[6].hide() # join group chat childs[7].hide() # add to roster else: # It's a mail or a JID + text = text.lower() id = childs[2].connect('activate', self.on_copy_link_activate, text) self.handlers[id] = childs[2] id = childs[3].connect('activate', self.on_open_link_activate, kind, text) @@ -701,7 +712,6 @@ class ConversationTextview: # if tim_format comes as unicode because of day_str. # we convert it to the encoding that we want (and that is utf-8) tim_format = helpers.ensure_utf8_string(tim_format) - tim_format = tim_format.encode('utf-8') buffer.insert_with_tags_by_name(end_iter, tim_format + ' ', *other_tags_for_time) elif current_print_time == 'sometimes' and kind != 'info': diff --git a/src/dialogs.py b/src/dialogs.py index b5f919454..eae459362 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -40,6 +40,7 @@ from advanced import AdvancedConfigurationWindow from common import gajim from common import helpers +from common.exceptions import GajimGeneralException as GajimGeneralException class EditGroupsDialog: '''Class for the edit group dialog window''' @@ -139,6 +140,9 @@ class EditGroupsDialog: group = self.xml.get_widget('group_entry').get_text().decode('utf-8') if not group: return + # Do not allow special groups + if group in helpers.special_groups: + return # check if it already exists model = self.list.get_model() iter = model.get_iter_root() @@ -180,14 +184,16 @@ class EditGroupsDialog: if account not in accounts: accounts.append(account) for g in gajim.groups[account].keys(): - if g in helpers.special_groups: - continue if g in groups: continue groups[g] = 0 for g in contact.groups: groups[g] += 1 - group_list = groups.keys() + group_list = [] + # Remove special groups if they are empty + for group in groups: + if group not in helpers.special_groups or groups[group] > 0: + group_list.append(group) group_list.sort() for group in group_list: iter = store.append() @@ -506,7 +512,7 @@ _('Please fill in the data of the contact you want to add in account %s') %accou if type_ == 'jabber': self.uid_entry.set_text(jid) else: - uid, transport = gajim.get_room_name_and_server_from_room_jid(jid) + uid, transport = gajim.get_name_and_server_from_jid(jid) self.uid_entry.set_text(uid.replace('%', '@', 1)) #set protocol_combobox model = self.protocol_combobox.get_model() @@ -1076,32 +1082,33 @@ class SubscriptionRequestWindow: class JoinGroupchatWindow: - def __init__(self, account, server = '', room = '', nick = '', - automatic = False): + def __init__(self, account, room_jid = '', nick = '', automatic = False): '''automatic is a dict like {'invities': []} If automatic is not empty, this means room must be automaticaly configured and when done, invities must be automatically invited''' - if server and room: - jid = room + '@' + server - if jid in gajim.gc_connected[account] and gajim.gc_connected[account][jid]: - ErrorDialog(_('You are already in room %s') % jid) - raise RuntimeError, 'You are already in this room' + if room_jid != '': + if room_jid in gajim.gc_connected[account] and\ + gajim.gc_connected[account][room_jid]: + ErrorDialog(_('You are already in room %s') % room_jid) + raise GajimGeneralException, 'You are already in this room' self.account = account self.automatic = automatic if nick == '': nick = gajim.nicks[self.account] if gajim.connections[account].connected < 2: ErrorDialog(_('You are not connected to the server'), -_('You can not join a group chat unless you are connected.')) - raise RuntimeError, 'You must be connected to join a groupchat' + _('You can not join a group chat unless you are connected.')) + raise GajimGeneralException, 'You must be connected to join a groupchat' self._empty_required_widgets = [] self.xml = gtkgui_helpers.get_glade('join_groupchat_window.glade') self.window = self.xml.get_widget('join_groupchat_window') - self.xml.get_widget('server_entry').set_text(server) - self.xml.get_widget('room_entry').set_text(room) - self.xml.get_widget('nickname_entry').set_text(nick) + self._room_jid_entry = self.xml.get_widget('room_jid_entry') + self._nickname_entry = self.xml.get_widget('nickname_entry') + + self._room_jid_entry.set_text(room_jid) + self._nickname_entry.set_text(nick) self.xml.signal_autoconnect(self) gajim.interface.instances[account]['join_gc'] = self #now add us to open windows if len(gajim.connections) > 1: @@ -1121,18 +1128,13 @@ _('You can not join a group chat unless you are connected.')) self.recently_combobox.append_text(g) if len(self.recently_groupchat) == 0: self.recently_combobox.set_sensitive(False) - elif server == '' and room == '': + elif room_jid == '': self.recently_combobox.set_active(0) - self.xml.get_widget('room_entry').select_region(0, -1) - elif room and server: + self._room_jid_entry.select_region(0, -1) + elif room_jid != '': self.xml.get_widget('join_button').grab_focus() - self._server_entry = self.xml.get_widget('server_entry') - self._room_entry = self.xml.get_widget('room_entry') - self._nickname_entry = self.xml.get_widget('nickname_entry') - if not self._server_entry.get_text(): - self._empty_required_widgets.append(self._server_entry) - if not self._room_entry.get_text(): + if not self._room_jid_entry.get_text(): self._empty_required_widgets.append(self._room_entry) if not self._nickname_entry.get_text(): self._empty_required_widgets.append(self._nickname_entry) @@ -1160,27 +1162,11 @@ _('You can not join a group chat unless you are connected.')) if len(self._empty_required_widgets) == 0: self.xml.get_widget('join_button').set_sensitive(True) - def on_room_entry_key_press_event(self, widget, event): - # Check for pressed @ and jump to server_entry if found - if event.keyval == gtk.keysyms.at: - self.xml.get_widget('server_entry').grab_focus() - return True - - def on_server_entry_key_press_event(self, widget, event): - # If backspace is pressed in empty server_entry, return to the room entry - backspace = event.keyval == gtk.keysyms.BackSpace - server_entry = self.xml.get_widget('server_entry') - empty = len(server_entry.get_text()) == 0 - if backspace and empty: - self.xml.get_widget('room_entry').grab_focus() - return True - def on_recently_combobox_changed(self, widget): model = widget.get_model() - iter = widget.get_active_iter() - gid = model[iter][0].decode('utf-8') - self.xml.get_widget('room_entry').set_text(gid.split('@')[0]) - self.xml.get_widget('server_entry').set_text(gid.split('@')[1]) + iter_ = widget.get_active_iter() + room_jid = model[iter_][0].decode('utf-8') + self._room_jid_entry.set_text(room_jid) def on_cancel_button_clicked(self, widget): '''When Cancel button is clicked''' @@ -1188,30 +1174,29 @@ _('You can not join a group chat unless you are connected.')) def on_join_button_clicked(self, widget): '''When Join button is clicked''' - nickname = self.xml.get_widget('nickname_entry').get_text().decode( - 'utf-8') - room = self.xml.get_widget('room_entry').get_text().decode('utf-8') - server = self.xml.get_widget('server_entry').get_text().decode('utf-8') + nickname = self._nickname_entry.get_text().decode('utf-8') + room_jid = self._room_jid_entry.get_text().decode('utf-8') password = self.xml.get_widget('password_entry').get_text().decode( 'utf-8') - jid = '%s@%s' % (room, server) try: - jid = helpers.parse_jid(jid) + room_jid = helpers.parse_jid(room_jid) except: - ErrorDialog(_('Invalid room or server name'), - _('The room name or server name has not allowed characters.')) + ErrorDialog(_('Invalid room Jabber ID'), + _('The room Jabber ID has not allowed characters.')) return - if jid in self.recently_groupchat: - self.recently_groupchat.remove(jid) - self.recently_groupchat.insert(0, jid) + if room_jid in self.recently_groupchat: + self.recently_groupchat.remove(room_jid) + self.recently_groupchat.insert(0, room_jid) if len(self.recently_groupchat) > 10: self.recently_groupchat = self.recently_groupchat[0:10] - gajim.config.set('recently_groupchat', ' '.join(self.recently_groupchat)) + gajim.config.set('recently_groupchat', + ' '.join(self.recently_groupchat)) if self.automatic: - gajim.automatic_rooms[self.account][jid] = self.automatic - gajim.interface.roster.join_gc_room(self.account, jid, nickname, password) + gajim.automatic_rooms[self.account][room_jid] = self.automatic + gajim.interface.roster.join_gc_room(self.account, room_jid, nickname, + password) self.window.destroy() @@ -1271,7 +1256,7 @@ class ChangePasswordDialog: if not account or gajim.connections[account].connected < 2: ErrorDialog(_('You are not connected to the server'), _('Without a connection, you can not change your password.')) - raise RuntimeError, 'You are not connected to the server' + raise GajimGeneralException, 'You are not connected to the server' self.account = account self.xml = gtkgui_helpers.get_glade('change_password_dialog.glade') self.dialog = self.xml.get_widget('change_password_dialog') @@ -2189,10 +2174,9 @@ class InvitationReceivedDialog: def on_accept_button_clicked(self, widget): self.dialog.destroy() - room, server = gajim.get_room_name_and_server_from_room_jid(self.room_jid) try: - JoinGroupchatWindow(self.account, server = server, room = room) - except RuntimeError: + JoinGroupchatWindow(self.account, self.room_jid) + except GajimGeneralException: pass class ProgressDialog: diff --git a/src/disco.py b/src/disco.py index ff1e12c63..4dd082a5b 100644 --- a/src/disco.py +++ b/src/disco.py @@ -49,6 +49,7 @@ import gtkgui_helpers from common import gajim from common import xmpp +from common.exceptions import GajimGeneralException as GajimGeneralException # Dictionary mapping category, type pairs to browser class, image pairs. # This is a function, so we can call it after the classes are declared. @@ -121,6 +122,13 @@ class CacheDictionary: def __call__(self): return self.value + def cleanup(self): + for key in self.cache.keys(): + item = self.cache[key] + if item.source: + gobject.source_remove(item.source) + del self.cache[key] + def _expire_timeout(self, key): '''The timeout has expired, remove the object.''' if key in self.cache: @@ -132,8 +140,9 @@ class CacheDictionary: item = self.cache[key] if item.source: gobject.source_remove(item.source) - source = gobject.timeout_add(self.lifetime, self._expire_timeout, key) - item.source = source + if self.lifetime: + source = gobject.timeout_add(self.lifetime, self._expire_timeout, key) + item.source = source def __getitem__(self, key): item = self.cache[key] @@ -205,10 +214,14 @@ class ServicesCache: ServiceCache instance.''' def __init__(self, account): self.account = account - self._items = CacheDictionary(1, getrefresh = True) - self._info = CacheDictionary(1, getrefresh = True) + self._items = CacheDictionary(0, getrefresh = False) + self._info = CacheDictionary(0, getrefresh = False) self._cbs = {} + def cleanup(self): + self._items.cleanup() + self._info.cleanup() + def _clean_closure(self, cb, type, addr): # A closure died, clean up cbkey = (type, addr) @@ -584,6 +597,7 @@ _('Without a connection, you can not browse available services')) self.browser = None self.window.destroy() + self.cache.cleanup() for child in self.children[:]: child.parent = None if chain: @@ -1178,16 +1192,10 @@ class ToplevelAgentBrowser(AgentBrowser): if not iter: return service = model[iter][0].decode('utf-8') - if service.find('@') != -1: - services = service.split('@', 1) - room = services[0] - service = services[1] - else: - room = '' if not gajim.interface.instances[self.account].has_key('join_gc'): try: - dialogs.JoinGroupchatWindow(self.account, service, room) - except RuntimeError: + dialogs.JoinGroupchatWindow(self.account, service) + except GajimGeneralException: pass else: gajim.interface.instances[self.account]['join_gc'].window.present() @@ -1489,7 +1497,8 @@ class MucBrowser(AgentBrowser): self.vadj = self.window.services_scrollwin.get_property('vadjustment') self.vadj_cbid = self.vadj.connect('value-changed', self.on_scroll) # And to size changes - self.size_cbid = self.window.services_scrollwin.connect('size-allocate', self.on_scroll) + self.size_cbid = self.window.services_scrollwin.connect( + 'size-allocate', self.on_scroll) def _clean_treemodel(self): if self.size_cbid: @@ -1518,16 +1527,12 @@ class MucBrowser(AgentBrowser): if not iter: return service = model[iter][0].decode('utf-8') - if service.find('@') != -1: - services = service.split('@', 1) - room = services[0] - service = services[1] - else: - room = model[iter][1].decode('utf-8') + room = model[iter][1].decode('utf-8') if 'join_gc' not in gajim.interface.instances[self.account]: try: - dialogs.JoinGroupchatWindow(self.account, service, room) - except RuntimeError: + room_jid = '%s@%s' % (service, room) + dialogs.JoinGroupchatWindow(self.account, service) + except GajimGeneralException: pass else: gajim.interface.instances[self.account]['join_gc'].window.present() diff --git a/src/gajim.py b/src/gajim.py index 611536643..e4e8693b6 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -4,20 +4,11 @@ exec python -OOt "$0" ${1+"$@"} ' ''' ## gajim.py ## -## Contributors for this file: -## - Yann Le Boulanger -## - Nikos Kouremenos -## - Dimitur Kirov -## - Travis Shirk ## -## 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) 2003-2006 Yann Le Boulanger +## Copyright (C) 2005-2006 Nikos Kouremenos +## Copyright (C) 2005-2006 Dimitur Kirov +## Copyright (C) 2005 Travis Shirk ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published @@ -82,16 +73,14 @@ except exceptions.PysqliteNotAvailable, e: if os.name == 'nt': try: import winsound # windows-only built-in module for playing wav - import win32api - import win32con except: pritext = _('Gajim needs pywin32 to run') sectext = _('Please make sure that Pywin32 is installed on your system. You can get it at %s') % 'http://sourceforge.net/project/showfiles.php?group_id=78018' if pritext: dlg = gtk.MessageDialog(None, - gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, - gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext) + gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, message_format = pritext) dlg.format_secondary_text(sectext) dlg.run() @@ -194,7 +183,6 @@ atexit.register(on_exit) parser = optparser.OptionsParser(config_filename) import roster_window -import systray import profile_window import config @@ -509,7 +497,8 @@ class Interface: def handle_event_msg(self, account, array): # 'MSG' (account, (jid, msg, time, encrypted, msg_type, subject, - # chatstate, msg_id, composing_jep, user_nick, xhtml)) user_nick is JEP-0172 + # chatstate, msg_id, composing_jep, user_nick, xhtml)) + # user_nick is JEP-0172 full_jid_with_resource = array[0] jid = gajim.get_jid_without_resource(full_jid_with_resource) @@ -609,7 +598,7 @@ class Interface: msg_type, subject, resource, msg_id, array[9], advanced_notif_num) else: - #xhtml in last element + # xhtml in last element self.roster.on_message(jid, message, array[2], account, array[3], msg_type, subject, resource, msg_id, array[9], advanced_notif_num, xhtml = array[10]) @@ -1092,12 +1081,16 @@ class Interface: if gajim.config.get('notify_on_new_gmail_email'): img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events', 'new_email_recv.png') - title = _('New E-mail on %(gmail_mail_address)s') % \ + title = _('New 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) + text = i18n.ngettext('You have %d new mail conversation', + 'You have %d new mail conversations', gmail_new_messages, + gmail_new_messages, gmail_new_messages) if gajim.config.get('notify_on_new_gmail_email_extra'): for gmessage in gmail_messages_list: + #FIXME: emulate Gtalk client popups. find out what they parse and how + #they decide what to show # each message has a 'From', 'Subject' and 'Snippet' field text += _('\nFrom: %(from_address)s') % \ {'from_address': gmessage['From']} @@ -1382,12 +1375,11 @@ class Interface: if gajim.gc_connected[account].has_key(room_jid) and\ gajim.gc_connected[account][room_jid]: continue - room, server = gajim.get_room_name_and_server_from_room_jid(room_jid) nick = gc_control.nick password = '' if gajim.gc_passwords.has_key(room_jid): password = gajim.gc_passwords[room_jid] - gajim.connections[account].join_gc(nick, room, server, password) + gajim.connections[account].join_gc(nick, room_jid, password) def handle_event_metacontacts(self, account, tags_list): gajim.contacts.define_metacontacts(account, tags_list) @@ -1946,21 +1938,18 @@ class Interface: self.systray_enabled = False self.systray_capabilities = False - if os.name == 'nt': - pass - ''' - try: - import systraywin32 - except: # user doesn't have trayicon capabilities - pass - else: - self.systray_capabilities = True - self.systray = systraywin32.SystrayWin32() - ''' - else: + if os.name == 'nt' and gtk.pygtk_version >= (2, 10, 0) and\ + gtk.gtk_version >= (2, 10, 0): + import statusicon + self.systray = statusicon.StatusIcon() + self.systray_capabilities = True + else: # use ours, not GTK+ one + # [FIXME: remove this when we migrate to 2.10 and we can do + # cool tooltips somehow and (not dying to keep) animation] + import systray self.systray_capabilities = systray.HAS_SYSTRAY_CAPABILITIES if self.systray_capabilities: - self.systray = systray.Systray() + self.systray = systray.Systray() if self.systray_capabilities and gajim.config.get('trayicon'): self.show_systray() @@ -2013,7 +2002,8 @@ if __name__ == '__main__': cli = gnome.ui.master_client() cli.connect('die', die_cb) - path_to_gajim_script = gtkgui_helpers.get_abspath_for_script('gajim') + path_to_gajim_script = gtkgui_helpers.get_abspath_for_script( + 'gajim') if path_to_gajim_script: argv = [path_to_gajim_script] @@ -2028,5 +2018,6 @@ if __name__ == '__main__': gtkgui_helpers.possibly_set_gajim_as_xmpp_handler() check_paths.check_and_possibly_create_paths() + Interface() gtk.main() diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 3590163d9..18e029a10 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -39,12 +39,13 @@ from common import helpers from chat_control import ChatControl from chat_control import ChatControlBase from conversation_textview import ConversationTextview +from common.exceptions import GajimGeneralException as GajimGeneralException #(status_image, type, nick, shown_nick) ( C_IMG, # image to show state (online, new message etc) -C_TYPE, # type of the row ('contact' or 'group') -C_NICK, # contact nickame or group name +C_NICK, # contact nickame or ROLE name +C_TYPE, # type of the row ('contact' or 'role') C_TEXT, # text shown in the cellrenderer C_AVATAR, # avatar of the contact ) = range(5) @@ -253,7 +254,7 @@ class GroupchatControl(ChatControlBase): id = self.list_treeview.connect('size-allocate', self.on_treeview_size_allocate) self.handlers[id] = self.list_treeview - #status_image, type, nickname, shown_nick + #status_image, shown_nick, type, nickname, avatar store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf) store.set_sort_column_id(C_TEXT, gtk.SORT_ASCENDING) self.list_treeview.set_model(store) @@ -397,7 +398,9 @@ class GroupchatControl(ChatControlBase): jid = self.contact.jid num_unread = len(gajim.events.get_events(self.account, jid, ['printed_gc_msg'])) - if num_unread > 1: + if num_unread == 1: + unread = '*' + elif num_unread > 1: unread = '[' + unicode(num_unread) + ']' label_str = unread + label_str return (label_str, color) @@ -664,7 +667,7 @@ class GroupchatControl(ChatControlBase): self.subject = subject self.name_label.set_ellipsize(pango.ELLIPSIZE_END) - subject = gtkgui_helpers.reduce_chars_newlines(subject, max_lines = 2) + subject = helpers.reduce_chars_newlines(subject, max_lines = 2) subject = gtkgui_helpers.escape_for_pango_markup(subject) font_attrs, font_attrs_small = self.get_font_attrs() text = '%s' % (font_attrs, self.room_jid) @@ -672,8 +675,6 @@ class GroupchatControl(ChatControlBase): text += '\n%s' % (font_attrs_small, subject) self.name_label.set_markup(text) event_box = self.name_label.get_parent() - if subject == '': - self.subject = _('This room has no subject') # tooltip must always hold ALL the subject self.subject_tooltip.set_tip(event_box, self.subject) @@ -735,7 +736,7 @@ class GroupchatControl(ChatControlBase): if status and gajim.config.get('show_status_msgs_in_roster'): status = status.strip() if status != '': - status = gtkgui_helpers.reduce_chars_newlines(status, max_lines = 1) + status = helpers.reduce_chars_newlines(status, max_lines = 1) # escape markup entities and make them small italic and fg color color = gtkgui_helpers._get_fade_color(self.list_treeview, selected, focus) @@ -913,9 +914,9 @@ class GroupchatControl(ChatControlBase): role_iter = self.get_role_iter(role) if not role_iter: role_iter = model.append(None, - (gajim.interface.roster.jabber_state_images['16']['closed'], 'role', - role, '%s' % role_name, None)) - iter = model.append(role_iter, (None, 'contact', nick, name, None)) + (gajim.interface.roster.jabber_state_images['16']['closed'], role, + 'role', '%s' % role_name, None)) + iter = model.append(role_iter, (None, nick, 'contact', name, None)) if not nick in gajim.contacts.get_nick_list(self.account, self.room_jid): gc_contact = gajim.contacts.create_gc_contact(room_jid = self.room_jid, name = nick, show = show, status = status, role = role, @@ -924,7 +925,7 @@ class GroupchatControl(ChatControlBase): self.draw_contact(nick) self.draw_avatar(nick) # Do not ask avatar to irc rooms as irc transports reply with messages - r, server = gajim.get_room_name_and_server_from_room_jid(self.room_jid) + server = gajim.get_server_from_jid(self.room_jid) if gajim.config.get('ask_avatars_on_startup') and \ not server.startswith('irc'): fjid = self.room_jid + '/' + nick @@ -1038,8 +1039,10 @@ class GroupchatControl(ChatControlBase): new_topic = message_array.pop(0) gajim.connections[self.account].send_gc_subject(self.room_jid, new_topic) - else: + elif self.subject is not '': self.print_conversation(self.subject, 'info') + else: + self.print_conversation(_('This room has no subject'), 'info') self.clear(self.msg_textview) return True elif command == 'invite': @@ -1067,15 +1070,13 @@ class GroupchatControl(ChatControlBase): elif command == 'join': # example: /join room@conference.example.com/nick if len(message_array): - message_array = message_array[0] - if message_array.find('@') >= 0: - room, servernick = message_array.split('@') - if servernick.find('/') >= 0: - server, nick = servernick.split('/', 1) + room_jid = message_array[0] + if room_jid.find('@') >= 0: + if room_jid.find('/') >= 0: + room_jid, nick = room_jid.split('/', 1) else: - server = servernick nick = '' - #join_gc window is needed in order to provide for password entry. + # join_gc window is needed in order to provide for password entry. if gajim.interface.instances[self.account].has_key('join_gc'): gajim.interface.instances[self.account]['join_gc'].\ window.present() @@ -1083,13 +1084,13 @@ class GroupchatControl(ChatControlBase): try: gajim.interface.instances[self.account]['join_gc'] =\ dialogs.JoinGroupchatWindow(self.account, - server = server, room = room, nick = nick) - except RuntimeError: + room_jid = room_jid, nick = nick) + except GajimGeneralException: pass self.clear(self.msg_textview) else: #%s is something the user wrote but it is not a jid so we inform - s = _('%s does not appear to be a valid JID') % message_array + s = _('%s does not appear to be a valid JID') % message_array[0] self.print_conversation(s, 'info') else: self.get_command_help(command) @@ -1376,7 +1377,7 @@ class GroupchatControl(ChatControlBase): if bookmark['jid'] == bm['jid']: dialogs.ErrorDialog( _('Bookmark already set'), - _('Room "%s" is already in your bookmarks.') % bm['jid']) + _('Group Chat "%s" is already in your bookmarks.') % bm['jid']) return gajim.connections[self.account].bookmarks.append(bm) @@ -1832,7 +1833,7 @@ class GroupchatControl(ChatControlBase): present() else: gajim.interface.instances[self.account]['infos'][c2.jid] = \ - vcard.VcardWindow(c2, self.account, is_fake = True) + vcard.VcardWindow(c2, self.account, c) def on_history(self, widget, nick): jid = gajim.construct_fjid(self.room_jid, nick) diff --git a/src/gtkexcepthook.py b/src/gtkexcepthook.py index 1bc552a24..7e398979d 100644 --- a/src/gtkexcepthook.py +++ b/src/gtkexcepthook.py @@ -22,6 +22,7 @@ import threading import gtk import pango +from common import i18n import dialogs from cStringIO import StringIO diff --git a/src/gtkgui_helpers.py b/src/gtkgui_helpers.py index e1f094a29..e8608c511 100644 --- a/src/gtkgui_helpers.py +++ b/src/gtkgui_helpers.py @@ -169,33 +169,6 @@ def get_default_font(): return None -def reduce_chars_newlines(text, max_chars = 0, max_lines = 0): - '''Cut the chars after 'max_chars' on each line - and show only the first 'max_lines'. - If any of the params is not present (None or 0) the action - on it is not performed''' - - def _cut_if_long(string): - if len(string) > max_chars: - string = string[:max_chars - 3] + '...' - return string - - if isinstance(text, str): - text = text.decode('utf-8') - - if max_lines == 0: - lines = text.split('\n') - else: - lines = text.split('\n', max_lines)[:max_lines] - if max_chars > 0: - if lines: - lines = map(lambda e: _cut_if_long(e), lines) - if lines: - reduced_text = reduce(lambda e, e1: e + '\n' + e1, lines) - else: - reduced_text = '' - return reduced_text - def escape_for_pango_markup(string): # escapes < > & ' " # for pango markup not to break @@ -210,7 +183,22 @@ def escape_for_pango_markup(string): return escaped_str def autodetect_browser_mailer(): - # recognize the environment for appropriate browser/mailer + # recognize the environment and set appropriate browser/mailer + if user_runs_gnome(): + gajim.config.set('openwith', 'gnome-open') + elif user_runs_kde(): + gajim.config.set('openwith', 'kfmclient exec') + else: + gajim.config.set('openwith', 'custom') + +def user_runs_gnome(): + return 'gnome-session' in get_running_processes() + +def user_runs_kde(): + return 'startkde' in get_running_processes() + +def get_running_processes(): + '''returns running processes or None (if not /proc exists)''' if os.path.isdir('/proc'): # under Linux: checking if 'gnome-session' or # 'startkde' programs were run before gajim, by @@ -240,12 +228,8 @@ def autodetect_browser_mailer(): # list of processes processes = [os.path.basename(os.readlink('/proc/' + f +'/exe')) for f in files] - if 'gnome-session' in processes: - gajim.config.set('openwith', 'gnome-open') - elif 'startkde' in processes: - gajim.config.set('openwith', 'kfmclient exec') - else: - gajim.config.set('openwith', 'custom') + + return processes def move_window(window, x, y): '''moves the window but also checks if out of screen''' diff --git a/src/history_manager.py b/src/history_manager.py index a4bd75528..86810d3db 100755 --- a/src/history_manager.py +++ b/src/history_manager.py @@ -1,7 +1,4 @@ -#!/bin/sh -''':' -exec python -OOt "$0" ${1+"$@"} -' ''' +#!/usr/bin/env python ## history_manager.py ## ## Copyright (C) 2006 Nikos Kouremenos @@ -33,6 +30,9 @@ import dialogs import gtkgui_helpers from common.logger import LOG_DB_PATH, constants +#FIXME: constants should implement 2 way mappings +status = dict((constants.__dict__[i], i[5:].lower()) for i in \ + constants.__dict__.keys() if i.startswith('SHOW_')) from common import gajim from common import helpers @@ -51,7 +51,6 @@ except ImportError: class HistoryManager: - def __init__(self): path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png') pix = gtk.gdk.pixbuf_new_from_file(path_to_file) @@ -312,6 +311,7 @@ class HistoryManager: except ValueError: pass else: + color = None if kind in (constants.KIND_SINGLE_MSG_RECV, constants.KIND_CHAT_MSG_RECV, constants.KIND_GC_MSG): # it is the other side @@ -327,11 +327,14 @@ class HistoryManager: message = '' else: message = ' : ' + message - message = helpers.get_uf_show(show) + message - - message = '%s' % (color, - gtkgui_helpers.escape_for_pango_markup(message)) - self.logs_liststore.append((log_line_id, jid_id, time_, message, + message = helpers.get_uf_show(gajim.SHOW_LIST[show]) + message + + message_ = ' -## Copyright (C) 2006 Nikos Kouremenos -## Copyright (C) 2006 Santiago Gala -## -## 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. -## - -from docutils import io -from docutils.core import Publisher -from docutils.parsers.rst import roles - -def jep_reference_role(role, rawtext, text, lineno, inliner, - options={}, content=[]): - '''Role to make handy references to Jabber Enhancement Proposals (JEP). - - Use as :JEP:`71` (or jep, or jep-reference). - Modeled after the sample in docutils documentation. - ''' - from docutils import nodes,utils - from docutils.parsers.rst.roles import set_classes - - jep_base_url = 'http://www.jabber.org/jeps/' - jep_url = 'jep-%04d.html' - try: - jepnum = int(text) - if jepnum <= 0: - raise ValueError - except ValueError: - msg = inliner.reporter.error( - 'JEP number must be a number greater than or equal to 1; ' - '"%s" is invalid.' % text, line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - ref = jep_base_url + jep_url % jepnum - set_classes(options) - node = nodes.reference(rawtext, 'JEP ' + utils.unescape(text), refuri=ref, - **options) - return [node], [] - -roles.register_canonical_role('jep-reference', jep_reference_role) -from docutils.parsers.rst.languages.en import roles -roles['jep-reference'] = 'jep-reference' -roles['jep'] = 'jep-reference' - -class HTMLGenerator: - '''Really simple HTMLGenerator starting from publish_parts. - - It reuses the docutils.core.Publisher class, which means it is *not* - threadsafe. - ''' - def __init__(self, - settings_spec=None, - settings_overrides=dict(report_level=5, halt_level=5), - config_section='general'): - self.pub = Publisher(reader=None, parser=None, writer=None, - settings=None, - source_class=io.StringInput, - destination_class=io.StringOutput) - self.pub.set_components(reader_name='standalone', - parser_name='restructuredtext', - writer_name='html') - #hack: JEP-0071 does not allow HTML char entities, so we hack our way out of it. - # — == u"\u2014" - # a setting to only emit charater entities in the writer would be nice - # FIXME: several   are emitted, and they are explicitly forbidden in the JEP - #   == u"\u00a0" - self.pub.writer.translator_class.attribution_formats['dash'] = (u'\u2014', '') - self.pub.process_programmatic_settings(settings_spec, - settings_overrides, - config_section) - - - def create_xhtml(self, text, - destination=None, - destination_path=None, - enable_exit_status=None): - ''' Create xhtml for a fragment of IM dialog. - We can use the source_name to store info about - the message.''' - self.pub.set_source(text, None) - self.pub.set_destination(destination, destination_path) - output = self.pub.publish(enable_exit_status=enable_exit_status) - #kludge until we can get docutils to stop generating (rare)   entities - return u'\u00a0'.join(self.pub.writer.parts['fragment'].strip().split(' ')) - -Generator = HTMLGenerator() - -def create_xhtml(text): - return Generator.create_xhtml(text) - -if __name__ == '__main__': - print Generator.create_xhtml(''' -test:: - - >>> print 1 - 1 - -*I* like it. It is for :JEP:`71` - -this `` should trigger`` should trigger the   problem. - -''') - print Generator.create_xhtml(''' -*test1 - -test2_ -''') diff --git a/src/statusicon.py b/src/statusicon.py new file mode 100644 index 000000000..f6e785ce9 --- /dev/null +++ b/src/statusicon.py @@ -0,0 +1,69 @@ +## statusicon.py +## +## 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; either version 2 +## of the License, or (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import gtk +import gobject +import systray + +from common import gajim +from common import helpers + +class StatusIcon(systray.Systray): + '''Class for the notification area icon''' + #FIXME: when we migrate to GTK 2.10 stick only to this class + # (move base stuff from systray.py and rm it) + #NOTE: gtk api does NOT allow: + # leave, enter motion notify + # and can't do cool tooltips we use + # and we could use blinking instead of unsupported animation + # or we could emulate animation by every foo ms chaning the image + def __init__(self): + systray.Systray.__init__(self) + self.status_icon = gtk.StatusIcon() + + def show_icon(self): + self.status_icon.connect('activate', self.on_status_icon_left_clicked) + self.status_icon.connect('popup-menu', self.on_status_icon_right_clicked) + + self.set_img() + self.status_icon.props.visible = True + + def on_status_icon_right_clicked(self, widget, event_button, event_time): + self.make_menu(event_button, event_time) + + def hide_icon(self): + self.status_icon.props.visible = False + + def on_status_icon_left_clicked(self, widget): + self.on_left_click() + + def set_img(self): + '''apart from image, we also update tooltip text here' + if not gajim.interface.systray_enabled: + return + text = helpers.get_notification_icon_tooltip_text() + self.status_icon.set_tooltip(text) + if gajim.events.get_nb_systray_events(): + state = 'message' + else: + state = self.status + #FIXME: do not always use 16x16 (ask actually used size and use that) + image = gajim.interface.roster.jabber_state_images['16'][state] + if image.get_storage_type() == gtk.IMAGE_PIXBUF: + self.status_icon.props.pixbuf = image.get_pixbuf() + #FIXME: oops they forgot to support GIF animation? + #or they were lazy to get it to work under Windows! WTF! + #elif image.get_storage_type() == gtk.IMAGE_ANIMATION: + # self.img_tray.set_from_animation(image.get_animation()) diff --git a/src/systray.py b/src/systray.py index ade7ffd81..607739756 100644 --- a/src/systray.py +++ b/src/systray.py @@ -43,7 +43,7 @@ except: class Systray: '''Class for icon in the notification area - This class is both base class (for systraywin32.py) and normal class + This class is both base class (for statusicon.py) and normal class for trayicon in GNU/Linux''' def __init__(self): @@ -94,16 +94,14 @@ class Systray: def on_new_chat(self, widget, account): dialogs.NewChatDialog(account) - def make_menu(self, event = None): - '''create chat with and new message (sub) menus/menuitems - event is None when we're in Windows - ''' - + def make_menu(self, event_button, event_time): + '''create chat with and new message (sub) menus/menuitems''' for m in self.popup_menus: m.destroy() chat_with_menuitem = self.xml.get_widget('chat_with_menuitem') - single_message_menuitem = self.xml.get_widget('single_message_menuitem') + single_message_menuitem = self.xml.get_widget( + 'single_message_menuitem') status_menuitem = self.xml.get_widget('status_menu') join_gc_menuitem = self.xml.get_widget('join_gc_menuitem') @@ -171,7 +169,8 @@ class Systray: self.popup_menus.append(account_menu_for_chat_with) account_menu_for_single_message = gtk.Menu() - single_message_menuitem.set_submenu(account_menu_for_single_message) + single_message_menuitem.set_submenu( + account_menu_for_single_message) self.popup_menus.append(account_menu_for_single_message) accounts_list = gajim.contacts.get_accounts() @@ -195,9 +194,11 @@ 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_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) + gajim.interface.roster.add_bookmarks_list(gc_sub_menu, + account) elif connected_accounts == 1: # one account # one account connected, no need to show 'as jid' @@ -207,24 +208,24 @@ class Systray: 'activate', self.on_new_chat, account) # for single message single_message_menuitem.remove_submenu() - self.single_message_handler_id = single_message_menuitem.connect( - 'activate', self.on_single_message_menuitem_activate, account) + self.single_message_handler_id = single_message_menuitem.\ + connect('activate', + self.on_single_message_menuitem_activate, account) # join gc - gajim.interface.roster.add_bookmarks_list(gc_sub_menu, account) + gajim.interface.roster.add_bookmarks_list(gc_sub_menu, + account) break # No other connected account - if event is None: - # None means windows (we explicitly popup in systraywin32.py) - if self.added_hide_menuitem is False: - self.systray_context_menu.prepend(gtk.SeparatorMenuItem()) - item = gtk.MenuItem(_('Hide this menu')) - self.systray_context_menu.prepend(item) - self.added_hide_menuitem = True + if os.name == 'nt' and gtk.pygtk_version >= (2, 10, 0) and\ + gtk.gtk_version >= (2, 10, 0): + self.systray_context_menu.popup(None, None, + gtk.status_icon_position_menu, event_button, + event_time, self.status_icon) else: # GNU and Unices - self.systray_context_menu.popup(None, None, None, event.button, - event.time) + self.systray_context_menu.popup(None, None, None, event_button, + event_time) self.systray_context_menu.show_all() def on_show_all_events_menuitem_activate(self, widget): @@ -277,12 +278,14 @@ class Systray: def on_clicked(self, widget, event): self.on_tray_leave_notify_event(widget, None) - if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: # Left click + if event.type != gtk.gdk.BUTTON_PRESS: + return + if event.button == 1: # Left click self.on_left_click() elif event.button == 2: # middle click self.on_middle_click() elif event.button == 3: # right click - self.make_menu(event) + self.make_menu(event.button, event.time) def on_show_menuitem_activate(self, widget, show): # we all add some fake (we cannot select those nor have them as show) diff --git a/src/tooltips.py b/src/tooltips.py index 53915df8a..66989f12d 100644 --- a/src/tooltips.py +++ b/src/tooltips.py @@ -38,7 +38,7 @@ class BaseTooltip: tooltip.hide_tooltip() * data - the text to be displayed (extenders override this argument and - dislpay more complex contents) + display more complex contents) * widget_height - the height of the widget on which we want to show tooltip * widget_y_position - the vertical position of the widget on the screen @@ -135,7 +135,7 @@ class BaseTooltip: preferred_y = widget_y_position + widget_height + 4 self.preferred_position = [pointer_x, preferred_y] - self.widget_height =widget_height + self.widget_height = widget_height self.win.ensure_style() self.win.show_all() @@ -177,10 +177,12 @@ class StatusTable: # make sure 'status' is unicode before we send to to reduce_chars if isinstance(status, str): status = unicode(status, encoding='utf-8') - # reduce to 200 chars, 1 line - status = gtkgui_helpers.reduce_chars_newlines(status, 200, 1) - str_status += ' - ' + status - return gtkgui_helpers.escape_for_pango_markup(str_status) + # reduce to 100 chars, 1 line + status = helpers.reduce_chars_newlines(status, 100, 1) + str_status = gtkgui_helpers.escape_for_pango_markup(str_status) + status = gtkgui_helpers.escape_for_pango_markup(status) + str_status += ' - ' + status + '' + return str_status def add_status_row(self, file_path, show, str_status, status_time = None, show_lock = False): ''' appends a new row with status icon to the table ''' @@ -227,27 +229,6 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable): BaseTooltip.__init__(self) StatusTable.__init__(self) - def get_accounts_info(self): - accounts = [] - accounts_list = gajim.contacts.get_accounts() - accounts_list.sort() - for account in accounts_list: - status_idx = gajim.connections[account].connected - # uncomment the following to hide offline accounts - # if status_idx == 0: continue - status = gajim.SHOW_LIST[status_idx] - message = gajim.connections[account].status - single_line = helpers.get_uf_show(status) - if message is None: - message = '' - else: - message = message.strip() - if message != '': - single_line += ': ' + message - accounts.append({'name': account, 'status_line': single_line, - 'show': status, 'message': message}) - return accounts - def fill_table_with_accounts(self, accounts): iconset = gajim.config.get('iconset') if not iconset: @@ -259,7 +240,7 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable): # there are possible pango TBs on 'set_markup' if isinstance(message, str): message = unicode(message, encoding = 'utf-8') - message = gtkgui_helpers.reduce_chars_newlines(message, 50, 1) + message = helpers.reduce_chars_newlines(message, 100, 1) message = gtkgui_helpers.escape_for_pango_markup(message) if gajim.con_types.has_key(acct['name']) and \ gajim.con_types[acct['name']] in ('tls', 'ssl'): @@ -278,67 +259,16 @@ class NotificationAreaTooltip(BaseTooltip, StatusTable): def populate(self, data): self.create_window() self.create_table() - self.hbox = gtk.HBox() - self.table.set_property('column-spacing', 1) - text, single_line = '', '' - - unread_chat = gajim.events.get_nb_events(types = ['printed_chat', 'chat']) - unread_single_chat = gajim.events.get_nb_events(types = ['normal']) - unread_gc = gajim.events.get_nb_events(types = ['printed_gc_msg', - 'gc_msg']) - unread_pm = gajim.events.get_nb_events(types = ['printed_pm', 'pm']) - - accounts = self.get_accounts_info() - - if unread_chat or unread_single_chat or unread_gc or unread_pm: - text = 'Gajim ' - awaiting_events = unread_chat + unread_single_chat + unread_gc + unread_pm - if awaiting_events == unread_chat or awaiting_events == unread_single_chat \ - or awaiting_events == unread_gc or awaiting_events == unread_pm: - # This condition is like previous if but with xor... - # Print in one line - text += '-' - else: - # Print in multiple lines - text += '\n ' - if unread_chat: - text += i18n.ngettext( - ' %d unread message', - ' %d unread messages', - unread_chat, unread_chat, unread_chat) - text += '\n ' - if unread_single_chat: - text += i18n.ngettext( - ' %d unread single message', - ' %d unread single messages', - unread_single_chat, unread_single_chat, unread_single_chat) - text += '\n ' - if unread_gc: - text += i18n.ngettext( - ' %d unread group chat message', - ' %d unread group chat messages', - unread_gc, unread_gc, unread_gc) - text += '\n ' - if unread_pm: - text += i18n.ngettext( - ' %d unread private message', - ' %d unread private messages', - unread_pm, unread_pm, unread_pm) - text += '\n ' - text = text[:-4] # remove latest '\n ' - elif len(accounts) > 1: - text = _('Gajim') - self.current_current_row = 1 + accounts = helpers.get_accounts_info() + if len(accounts) > 1: self.table.resize(2, 1) self.fill_table_with_accounts(accounts) + self.hbox = gtk.HBox() + self.table.set_property('column-spacing', 1) - elif len(accounts) == 1: - message = accounts[0]['status_line'] - message = gtkgui_helpers.reduce_chars_newlines(message, 50, 1) - message = gtkgui_helpers.escape_for_pango_markup(message) - text = _('Gajim - %s') % message - else: - text = _('Gajim - %s') % helpers.get_uf_show('offline') + text = helpers.get_notification_icon_tooltip_text() + text = gtkgui_helpers.escape_for_pango_markup(text) + self.add_text_row(text) self.hbox.add(self.table) self.win.add(self.hbox) @@ -364,7 +294,25 @@ class GCTooltip(BaseTooltip): vcard_table.set_homogeneous(False) vcard_current_row = 1 properties = [] - + + nick_markup = '' + \ + gtkgui_helpers.escape_for_pango_markup(contact.get_shown_name()) \ + + '' + properties.append((nick_markup, None)) + + if contact.status: # status message + status = contact.status.strip() + if status != '': + # escape markup entities + status = helpers.reduce_chars_newlines(status, 100, 5) + status = '' +\ + gtkgui_helpers.escape_for_pango_markup(status) + '' + properties.append((status, None)) + else: # no status message, show SHOW instead + show = helpers.get_uf_show(contact.show) + show = '' + show + '' + properties.append((show, None)) + if contact.jid.strip() != '': jid_markup = '' + contact.jid + '' else: @@ -445,8 +393,7 @@ class RosterTooltip(NotificationAreaTooltip): self.create_table() if not contacts or len(contacts) == 0: # Tooltip for merged accounts row - accounts = self.get_accounts_info() - self.current_current_row = 0 + accounts = helpers.get_accounts_info() self.table.resize(2, 1) self.spacer_label = '' self.fill_table_with_accounts(accounts) @@ -539,8 +486,8 @@ class RosterTooltip(NotificationAreaTooltip): status = contact.status.strip() if status: # reduce long status - # (no more than 200 chars on line and no more than 5 lines) - status = gtkgui_helpers.reduce_chars_newlines(status, 200, 5) + # (no more than 100 chars on line and no more than 5 lines) + status = helpers.reduce_chars_newlines(status, 100, 5) # escape markup entities. status = gtkgui_helpers.escape_for_pango_markup(status) show += ' - ' + status diff --git a/src/vcard.py b/src/vcard.py index 0ee005e72..48bb83efc 100644 --- a/src/vcard.py +++ b/src/vcard.py @@ -60,7 +60,7 @@ def get_avatar_pixbuf_encoded_mime(photo): class VcardWindow: '''Class for contact's information window''' - def __init__(self, contact, account, is_fake = False): + def __init__(self, contact, account, gc_contact = None): # 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') @@ -68,7 +68,7 @@ class VcardWindow: self.contact = contact self.account = account - self.is_fake = is_fake + self.gc_contact = gc_contact self.avatar_mime_type = None self.avatar_encoded = None @@ -246,26 +246,38 @@ class VcardWindow: self.contact.get_shown_name() + '') self.xml.get_widget('jid_label').set_text(self.contact.jid) - uf_sub = helpers.get_uf_sub(self.contact.sub) - self.xml.get_widget('subscription_label').set_text(uf_sub) - eb = self.xml.get_widget('subscription_label_eventbox') - if self.contact.sub == 'from': - tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence") - elif self.contact.sub == 'to': - tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours") - elif self.contact.sub == 'both': - tt_text = _("You and the contact are interested in each other's presence information") - else: # None - tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours") - tooltips.set_tip(eb, tt_text) + + subscription_label = self.xml.get_widget('subscription_label') + ask_label = self.xml.get_widget('ask_label') + if self.gc_contact: + self.xml.get_widget('subscription_title_label').set_text(_("Role:")) + uf_role = helpers.get_uf_role(self.gc_contact.role) + subscription_label.set_text(uf_role) + + self.xml.get_widget('ask_title_label').set_text(_("Affiliation:")) + uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation) + ask_label.set_text(uf_affiliation) + else: + uf_sub = helpers.get_uf_sub(self.contact.sub) + subscription_label.set_text(uf_sub) + eb = self.xml.get_widget('subscription_label_eventbox') + if self.contact.sub == 'from': + tt_text = _("This contact is interested in your presence information, but you are not interested in his/her presence") + elif self.contact.sub == 'to': + tt_text = _("You are interested in the contact's presence information, but he/she is not interested in yours") + elif self.contact.sub == 'both': + tt_text = _("You and the contact are interested in each other's presence information") + else: # None + tt_text = _("You are not interested in the contact's presence, and neither he/she is interested in yours") + tooltips.set_tip(eb, tt_text) + + uf_ask = helpers.get_uf_ask(self.contact.ask) + ask_label.set_text(uf_ask) + eb = self.xml.get_widget('ask_label_eventbox') + if self.contact.ask == 'subscribe': + tooltips.set_tip(eb, + _("You are waiting contact's answer about your subscription request")) - label = self.xml.get_widget('ask_label') - uf_ask = helpers.get_uf_ask(self.contact.ask) - label.set_text(uf_ask) - eb = self.xml.get_widget('ask_label_eventbox') - if self.contact.ask == 'subscribe': - tooltips.set_tip(eb, - _("You are waiting contact's answer about your subscription request")) log = True if self.contact.jid in gajim.config.get_per('accounts', self.account, 'no_log_for').split(' '): @@ -318,7 +330,8 @@ class VcardWindow: self.fill_status_label() - gajim.connections[self.account].request_vcard(self.contact.jid, self.is_fake) + gajim.connections[self.account].request_vcard(self.contact.jid, + self.gc_contact is not None) def on_close_button_clicked(self, widget): self.window.destroy()