merge from trunk

This commit is contained in:
Yann Leboulanger 2010-08-16 17:32:23 +02:00
commit a3863703f0
150 changed files with 58147 additions and 42666 deletions

View File

@ -1,4 +1,47 @@
Gajim 0.13 (XX November 2009) Gajim 0.14 (XX)
* Jingle audio / video chat
* Improve Startup time
* Copy emoticons, LaTeX expressions when they are selected
* Fix status icon transparency by using gtk.statusicon
* Groupchat auto-rejoin
* geolocation (with geoclue)
* use XDG standards
* SCRAM-SHA-1 and SASL EXTERNAL authentication
* MUC captcha
* Lots of refactoring
Gajim 0.13.4 (02 April 2010)
* Add japanese translation
* Fix some TLS connection
* Don't raise a lot of "DB Error" dialog
* Fix contact synchronisation
* Minor fixes
Gajim 0.13.3 (23 February 2010)
* Fix facebook xmpp server connection
* Fix copy / paste with Ctrl+C on non-latin keyboard
* Fix sending PEP information when connecting
* Fix parsing HTML messages that have ascii markup
Gajim 0.13.2 (14 January 2010)
* Fix some translations
* Fix string comparison according to locales
* Fix resizing of groupchat occupant treeview
* Fix some gnomekeyring glitches
* better SRV usage with libasyncns
* copy emoticons when we copy / paste in conversations
Gajim 0.13.1 (28 November 2009)
* Fix a bug when no account exists and bonjour is not available
* Fix a bug when opening advanced option in MUC
* Fix a bug when using non-BOSH proxies
Gajim 0.13 (24 November 2009)
* Improve gtkspell (fix memleak) * Improve gtkspell (fix memleak)
* BOSH connection * BOSH connection
@ -44,7 +87,7 @@ Gajim 0.12.2 (07 June 2009)
* Improve error messages handling * Improve error messages handling
* Totem support for played music * Totem support for played music
* Fix SSL with some servers * Fix SSL with some servers
* Handle Xfce notification-daemon * Handle XFCE notification-daemon
* Restore old behaviour of click on systray: left click to open events * Restore old behaviour of click on systray: left click to open events
* Network manager 0.7 support * Network manager 0.7 support
* Move logs file under windows to $APPDATA/gajim * Move logs file under windows to $APPDATA/gajim

View File

@ -1,4 +1,4 @@
SUBDIRS = src data po icons SUBDIRS = src data plugins po icons
ACLOCAL_AMFLAGS = -I m4 ACLOCAL_AMFLAGS = -I m4

View File

@ -63,7 +63,7 @@
<p>To specify where to install do:</p> <p>To specify where to install do:</p>
<pre> <pre>
su -c make PREFIX=custom_path install ./configure --prefix=custom_path
</pre> </pre>
<h2>Running Gajim</h2> <h2>Running Gajim</h2>
@ -98,7 +98,8 @@ or if you didn't 'make install' you can also run from gajim folder with<em>./lau
If you want to remove it from custom directory provide it as: If you want to remove it from custom directory provide it as:
</p> </p>
<pre> <pre>
make PREFIX=custom_path uninstall ./configure --prefix=custom_path
make uninstall
</pre> </pre>
<h2>Miscellaneous</h2> <h2>Miscellaneous</h2>

View File

@ -1,4 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
gajimversion="0.13.90.1"
if [ -d ".hg" ]; then
hgversion="-$(hexdump -n6 -e'6/1 "%02x"' .hg/dirstate)"
else
hgversion=""
fi
echo "define([AC_PACKAGE_VERSION], [${gajimversion}${hgversion}])" > m4/hgversion.m4
AM_ARGS="--add-missing --gnu --copy" AM_ARGS="--add-missing --gnu --copy"
CONF_ARGS="" CONF_ARGS=""
if test x`uname -s 2>/dev/null` = 'xDarwin' -a -f /Library/Frameworks/GTK+.framework/Versions/Current/env; then if test x`uname -s 2>/dev/null` = 'xDarwin' -a -f /Library/Frameworks/GTK+.framework/Versions/Current/env; then

View File

@ -1,5 +1,5 @@
AC_INIT([Gajim - A Jabber Instant Messager], AC_INIT([Gajim - A Jabber Instant Messager],
[0.13.10.2-dev],[http://trac.gajim.org/],[gajim]) ["version-set-in-hgversion"],[http://trac.gajim.org/],[gajim])
AC_PREREQ([2.59]) AC_PREREQ([2.59])
AC_CONFIG_HEADER(config.h) AC_CONFIG_HEADER(config.h)
@ -65,14 +65,17 @@ AC_ARG_ENABLE(site-packages,
instead of DATADIR/gajim/src.])] instead of DATADIR/gajim/src.])]
, ,
AC_SUBST([gajim_srcdir], [\${pkgpythondir}]) AC_SUBST([gajim_srcdir], [\${pkgpythondir}])
AC_SUBST([gajim_pluginsdir], [\${pkgpythondir}])
, ,
AC_SUBST([gajim_srcdir], [\${datadir}/\${PACKAGE}/src]) AC_SUBST([gajim_srcdir], [\${datadir}/\${PACKAGE}/src])
AC_SUBST([gajim_pluginsdir], [\${datadir}/\${PACKAGE}/plugins])
) )
AS_AC_EXPAND(GAJIM_SRCDIR, "${gajim_srcdir}") AS_AC_EXPAND(GAJIM_SRCDIR, "${gajim_srcdir}")
AS_AC_EXPAND(PKGDATADIR, "${datadir}/${PACKAGE}") AS_AC_EXPAND(PKGDATADIR, "${datadir}/${PACKAGE}")
AS_AC_EXPAND(DOCDIR, "${docdir}") AS_AC_EXPAND(DOCDIR, "${docdir}")
AS_AC_EXPAND(LOCALEDIR, "${localedir}") AS_AC_EXPAND(LOCALEDIR, "${localedir}")
AS_AC_EXPAND(GAJIM_PLUGINSDIR, "${gajim_pluginsdir}")
AC_SUBST(VERSION) AC_SUBST(VERSION)
AC_SUBST(PACKAGE) AC_SUBST(PACKAGE)
@ -94,6 +97,7 @@ AC_CONFIG_FILES([
scripts/gajim-remote:scripts/gajim.in scripts/gajim-remote:scripts/gajim.in
scripts/gajim-history-manager:scripts/gajim.in scripts/gajim-history-manager:scripts/gajim.in
po/Makefile.in po/Makefile.in
plugins/Makefile
]) ])
AC_OUTPUT AC_OUTPUT
echo " echo "
@ -101,6 +105,7 @@ echo "
Installation: Installation:
Prefix ........... ${prefix} Prefix ........... ${prefix}
Python modules ... ${GAJIM_SRCDIR} Python modules ... ${GAJIM_SRCDIR}
Plugins .. ....... ${GAJIM_PLUGINSDIR}
Documentation .... ${DOCDIR} Documentation .... ${DOCDIR}
Others ........... ${PKGDATADIR} Others ........... ${PKGDATADIR}
*****************************" *****************************"

View File

@ -14,7 +14,7 @@ sounds_DATA = $(srcdir)/sounds/*.wav
otherdir = $(pkgdatadir)/data/other otherdir = $(pkgdatadir)/data/other
other_DATA = other/servers.xml other/cacerts.pem other_DATA = other/servers.xml other/cacerts.pem
man_MANS = gajim.1 gajim-remote.1 man_MANS = gajim.1 gajim-remote.1 gajim-history-manager.1
EXTRA_DIST = $(desktop_in_files) \ EXTRA_DIST = $(desktop_in_files) \

View File

@ -0,0 +1,21 @@
.\" 20050901
.TH "Gajim-history-manager" "1" "September 01, 2005" "Gajim dev team" ""
.SH "NAME"
Gajim-history-manager \- Tool to manage gajim logs
.SH "SYNOPSIS"
.B gajim-history-manager [\-c config-path] [\-h]
.SH "DESCRIPTION"
.B Gajim-history-manager
is a tool to manage (do some cleanup) log file of Gajim jabber client.
.PP
.SH "OPTIONS"
.TP
\fB\-c\fR, \fB\-\-config-path\fR path
Path where logs.db is located. ~/.gajim by default.
.TP
\fB\-h\fR, \fB\-\-help\fR
Print this help.
.SH "FEEDBACK"
You can report bugs or feature requests in http://trac.gajim.org or in the mailing list: http://lists.gajim.org/cgi\-bin/listinfo/gajim\-devel. You can also find us in our room gajim@conference.gajim.org
.SH "AUTHORS"
Written by Yann Le Boulanger <asterix@lagaule.org>, Nikos Kouremenos <kourem@gmail.com> and Dimitur Kirov <dkirov@gmail.com>.

View File

@ -217,7 +217,7 @@ to the Jabber network.</property>
<object class="GtkLabel" id="label1"> <object class="GtkLabel" id="label1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="xalign">0</property> <property name="xalign">0</property>
<property name="label" translatable="yes">@</property> <property name="label">@</property>
</object> </object>
<packing> <packing>
<property name="left_attach">2</property> <property name="left_attach">2</property>

View File

@ -422,6 +422,21 @@
<property name="position">3</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkButton" id="finish_button">
<property name="label" translatable="yes">F_inish</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_underline">True</property>
<signal name="clicked" handler="on_finish_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">4</property>
</packing>
</child>
<child> <child>
<object class="GtkButton" id="close_button"> <object class="GtkButton" id="close_button">
<property name="label">gtk-close</property> <property name="label">gtk-close</property>
@ -435,7 +450,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">4</property> <property name="position">5</property>
</packing> </packing>
</child> </child>
</object> </object>

View File

@ -9,6 +9,13 @@
<property name="use_underline">True</property> <property name="use_underline">True</property>
</object> </object>
</child> </child>
<child>
<object class="GtkMenuItem" id="archiving_preferences_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes">Edit Archi_ving Preferences</property>
<property name="use_underline">True</property>
</object>
</child>
<child> <child>
<object class="GtkMenuItem" id="privacy_lists_menuitem"> <object class="GtkMenuItem" id="privacy_lists_menuitem">
<property name="label" translatable="yes">Edit _Privacy Lists...</property> <property name="label" translatable="yes">Edit _Privacy Lists...</property>

View File

@ -0,0 +1,312 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">No</col>
</row>
<row>
<col id="0" translatable="yes">Yes</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore2">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Prefer</col>
</row>
<row>
<col id="0" translatable="yes">Concede</col>
</row>
<row>
<col id="0" translatable="yes">Forbid</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore3">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Prefer</col>
</row>
<row>
<col id="0" translatable="yes">Concede</col>
</row>
<row>
<col id="0" translatable="yes">Forbid</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore4">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Prefer</col>
</row>
<row>
<col id="0" translatable="yes">Concede</col>
</row>
<row>
<col id="0" translatable="yes">Forbid</col>
</row>
</data>
</object>
<object class="GtkWindow" id="archiving_preferences_window">
<property name="border_width">12</property>
<signal name="destroy" handler="on_archiving_preferences_window_destroy"/>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;i&gt;Method Manual&lt;/i&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;i&gt;Method Local&lt;/i&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;i&gt;Method Auto&lt;/i&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="method_manual_combobox">
<property name="visible">True</property>
<property name="model">liststore4</property>
<signal name="changed" handler="on_method_foo_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext4"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="method_local_combobox">
<property name="visible">True</property>
<property name="model">liststore3</property>
<signal name="changed" handler="on_method_foo_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext3"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="method_auto_combobox">
<property name="visible">True</property>
<property name="model">liststore2</property>
<signal name="changed" handler="on_method_foo_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="label" translatable="yes">Auto</property>
</object>
</child>
<child>
<object class="GtkComboBox" id="auto_combobox">
<property name="visible">True</property>
<property name="model">liststore1</property>
<signal name="changed" handler="on_auto_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="item_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="cursor_changed" handler="on_item_treeview_cursor_changed"/>
</object>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property>
<property name="layout_style">spread</property>
<child>
<object class="GtkButton" id="add_button">
<property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_add_item_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_button">
<property name="label">gtk-remove</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_remove_item_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="edit_button">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_edit_item_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="close_button">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_close_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

File diff suppressed because it is too large Load Diff

View File

@ -75,6 +75,85 @@
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkHPaned" id="hpaned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">3</property>
<property name="position">495</property>
<property name="position_set">True</property>
<child>
<object class="GtkVBox" id="gc_textviews_vbox">
<property name="width_request">0</property>
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkScrolledWindow" id="conversation_scrolledwindow">
<property name="width_request">200</property>
<property name="height_request">60</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="message_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="list_scrolledwindow">
<property name="width_request">100</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="list_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">1</property>
<property name="headers_visible">False</property>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child> <child>
<object class="GtkHBox" id="actions_hbox"> <object class="GtkHBox" id="actions_hbox">
<property name="visible">True</property> <property name="visible">True</property>
@ -133,7 +212,6 @@
<object class="GtkVSeparator" id="vseparator2"> <object class="GtkVSeparator" id="vseparator2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -233,7 +311,6 @@
<object class="GtkVSeparator" id="vseparator4"> <object class="GtkVSeparator" id="vseparator4">
<property name="visible">True</property> <property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="orientation">vertical</property>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
@ -269,6 +346,14 @@
<property name="position">8</property> <property name="position">8</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkComboBox" id="label_selector">
<property name="visible">True</property>
</object>
<packing>
<property name="position">9</property>
</packing>
</child>
<child> <child>
<object class="GtkAlignment" id="alignment2"> <object class="GtkAlignment" id="alignment2">
<property name="visible">True</property> <property name="visible">True</property>
@ -278,7 +363,7 @@
</child> </child>
</object> </object>
<packing> <packing>
<property name="position">9</property> <property name="position">10</property>
</packing> </packing>
</child> </child>
<child> <child>
@ -326,7 +411,7 @@
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">False</property> <property name="fill">False</property>
<property name="position">10</property> <property name="position">11</property>
</packing> </packing>
</child> </child>
</object> </object>
@ -335,94 +420,5 @@
<property name="position">2</property> <property name="position">2</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkHPaned" id="hpaned">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">3</property>
<property name="position">495</property>
<property name="position_set">True</property>
<child>
<object class="GtkVBox" id="vbox108">
<property name="width_request">0</property>
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkVBox" id="vbox109">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkScrolledWindow" id="conversation_scrolledwindow">
<property name="width_request">200</property>
<property name="height_request">60</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="message_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">never</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="list_scrolledwindow">
<property name="width_request">100</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTreeView" id="list_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">1</property>
<property name="headers_visible">False</property>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">False</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object> </object>
</interface> </interface>

View File

@ -20,7 +20,7 @@
<object class="GtkLabel" id="label1"> <object class="GtkLabel" id="label1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="xalign">0</property> <property name="xalign">0</property>
<property name="label" translatable="yes">From</property> <property name="label" translatable="yes">From:</property>
</object> </object>
<packing> <packing>
<property name="x_options">GTK_FILL</property> <property name="x_options">GTK_FILL</property>
@ -31,7 +31,7 @@
<object class="GtkLabel" id="label2"> <object class="GtkLabel" id="label2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="xalign">0</property> <property name="xalign">0</property>
<property name="label" translatable="yes">Subject</property> <property name="label" translatable="yes">Subject:</property>
</object> </object>
<packing> <packing>
<property name="top_attach">1</property> <property name="top_attach">1</property>

View File

@ -0,0 +1,227 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkWindow" id="item_archiving_preferences_window">
<property name="border_width">12</property>
<signal name="destroy" handler="on_item_archiving_preferences_window_destroy"/>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkTable" id="table3">
<property name="visible">True</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="label" translatable="yes">expire</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="expire_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="otr_combobox">
<property name="visible">True</property>
<property name="model">liststore2</property>
<signal name="changed" handler="on_otr_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="save_combobox">
<property name="visible">True</property>
<property name="model">liststore1</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="label" translatable="yes">save</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label9">
<property name="visible">True</property>
<property name="label" translatable="yes">otr</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">jid</property>
</object>
</child>
<child>
<object class="GtkEntry" id="jid_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<object class="GtkProgressBar" id="progressbar"/>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_cancel_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="ok_button">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_ok_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">body</col>
</row>
<row>
<col id="0" translatable="yes">false</col>
</row>
<row>
<col id="0" translatable="yes">message</col>
</row>
<row>
<col id="0" translatable="yes">stream</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore2">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">approve</col>
</row>
<row>
<col id="0" translatable="yes">concede</col>
</row>
<row>
<col id="0" translatable="yes">forbid</col>
</row>
<row>
<col id="0" translatable="yes">oppose</col>
</row>
<row>
<col id="0" translatable="yes">prefer</col>
</row>
<row>
<col id="0" translatable="yes">require</col>
</row>
</data>
</object>
</interface>

View File

@ -44,6 +44,7 @@
<property name="xalign">0</property> <property name="xalign">0</property>
<property name="use_markup">True</property> <property name="use_markup">True</property>
<property name="ellipsize">end</property> <property name="ellipsize">end</property>
<property name="max_width_chars">9</property>
</object> </object>
<packing> <packing>
<property name="position">1</property> <property name="position">1</property>

644
data/gui/plugins_window.ui Normal file
View File

@ -0,0 +1,644 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="plugins_window">
<property name="width_request">650</property>
<property name="height_request">500</property>
<property name="border_width">6</property>
<property name="title" translatable="yes">Plugins</property>
<property name="default_width">650</property>
<property name="default_height">500</property>
<signal name="destroy" handler="on_plugins_window_destroy"/>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkNotebook" id="plugins_notebook">
<property name="visible">True</property>
<property name="can_focus">True</property>
<child>
<object class="GtkHPaned" id="hpaned1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="position">250</property>
<property name="position_set">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="border_width">6</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="installed_plugins_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="border_width">6</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<child>
<object class="GtkHBox" id="hbox4">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="plugin_name_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&amp;lt;empty&amp;gt;</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="label" translatable="yes">Version:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="plugin_version_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="spacing">6</property>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="yalign">0</property>
<property name="label" translatable="yes">Authors:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="plugin_authors_label">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="yalign">0</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="wrap_mode">word-char</property>
<property name="selectable">True</property>
<property name="ellipsize">end</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox3">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="label" translatable="yes">Homepage:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLinkButton" id="plugin_homepage_linkbutton">
<property name="label" translatable="yes">homepage url</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox3">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox5">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label7">
<property name="visible">True</property>
<property name="label" translatable="yes">Descrition:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTextView" id="plugin_description_textview">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="pixels_above_lines">6</property>
<property name="editable">False</property>
<property name="wrap_mode">word</property>
<property name="left_margin">6</property>
<property name="right_margin">6</property>
<property name="indent">1</property>
<property name="buffer">textbuffer1</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property>
<property name="spacing">5</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="uninstall_plugin_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_uninstall_plugin_button_clicked"/>
<child>
<object class="GtkHBox" id="hbox11">
<property name="visible">True</property>
<child>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="stock">gtk-cancel</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="uninstall_plugin_button_label">
<property name="visible">True</property>
<property name="label" translatable="yes">Uninstall</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="configure_plugin_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_configure_plugin_button_clicked"/>
<child>
<object class="GtkHBox" id="hbox12">
<property name="visible">True</property>
<child>
<object class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="stock">gtk-preferences</property>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="configure_plugin_button_label">
<property name="visible">True</property>
<property name="label" translatable="yes">Configure</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
</child>
<child type="tab">
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">Installed</property>
</object>
<packing>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<object class="GtkHPaned" id="hpaned2">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow2">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="treeview2">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
</child>
</object>
<packing>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox4">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox6">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="plugin_name_label1">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment3">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox7">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="label" translatable="yes">Version:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="plugin_version_label1">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox8">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="label" translatable="yes">Authors:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="plugin_authors_label1">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;empty&gt;</property>
<property name="selectable">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox9">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label9">
<property name="visible">True</property>
<property name="label" translatable="yes">Homepage:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLinkButton" id="plugin_homepage_linkbutton1">
<property name="label" translatable="yes">button</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="relief">none</property>
<property name="focus_on_click">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox5">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkHBox" id="hbox10">
<property name="visible">True</property>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="label" translatable="yes">Descrition:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment4">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkTextView" id="plugin_description_textview1">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox3">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="uninstall_plugin_button1">
<property name="label" translatable="yes">button</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="configure_plugin_button1">
<property name="label" translatable="yes">button</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">5</property>
</packing>
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="shrink">True</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child type="tab">
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Available</property>
</object>
<packing>
<property name="position">1</property>
<property name="tab_fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child type="tab">
<placeholder/>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="spacing">15</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="close_button">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_close_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkTextBuffer" id="textbuffer1">
<property name="text" translatable="yes">Plug-in decription should be displayed here. This text will be erased during PluginsWindow initialization.</property>
</object>
</interface>

View File

@ -2136,7 +2136,7 @@ $T will be replaced by auto-not-available timeout</property>
<child> <child>
<object class="GtkTable" id="table8"> <object class="GtkTable" id="table8">
<property name="visible">True</property> <property name="visible">True</property>
<property name="n_rows">2</property> <property name="n_rows">4</property>
<property name="n_columns">2</property> <property name="n_columns">2</property>
<property name="column_spacing">6</property> <property name="column_spacing">6</property>
<property name="row_spacing">6</property> <property name="row_spacing">6</property>
@ -2184,6 +2184,52 @@ $T will be replaced by auto-not-available timeout</property>
<property name="bottom_attach">2</property> <property name="bottom_attach">2</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkLabel" id="label26">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Video framerate</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label27">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Video size</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="video_framerate_combobox">
<property name="visible">True</property>
<signal name="changed" handler="on_video_framerate_combobox_changed"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="video_size_combobox">
<property name="visible">True</property>
<signal name="changed" handler="on_video_size_combobox_changed"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
</object> </object>
</child> </child>
</object> </object>

View File

@ -54,8 +54,8 @@
<child> <child>
<object class="GtkImageMenuItem" id="join_gc_menuitem"> <object class="GtkImageMenuItem" id="join_gc_menuitem">
<property name="label" translatable="yes">Join _Group Chat...</property> <property name="label" translatable="yes">Join _Group Chat...</property>
<property name="use_underline">True</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="use_underline">True</property>
<property name="image">image3</property> <property name="image">image3</property>
<property name="use_stock">False</property> <property name="use_stock">False</property>
<property name="accel_group">accelgroup1</property> <property name="accel_group">accelgroup1</property>
@ -69,8 +69,8 @@
<child> <child>
<object class="GtkImageMenuItem" id="add_new_contact_menuitem"> <object class="GtkImageMenuItem" id="add_new_contact_menuitem">
<property name="label" translatable="yes">Add _Contact...</property> <property name="label" translatable="yes">Add _Contact...</property>
<property name="use_underline">True</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="use_underline">True</property>
<property name="image">image4</property> <property name="image">image4</property>
<property name="use_stock">False</property> <property name="use_stock">False</property>
<property name="accel_group">accelgroup1</property> <property name="accel_group">accelgroup1</property>
@ -159,6 +159,16 @@
<signal name="activate" handler="on_preferences_menuitem_activate"/> <signal name="activate" handler="on_preferences_menuitem_activate"/>
</object> </object>
</child> </child>
<child>
<object class="GtkImageMenuItem" id="plugins_menuitem">
<property name="label" translatable="yes">P_lugins</property>
<property name="visible">True</property>
<property name="use_underline">True</property>
<property name="image">image13</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_plugins_menuitem_activate"/>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>
@ -437,4 +447,9 @@
<property name="stock">gtk-properties</property> <property name="stock">gtk-properties</property>
<property name="icon-size">1</property> <property name="icon-size">1</property>
</object> </object>
<object class="GtkImage" id="image13">
<property name="visible">True</property>
<property name="stock">gtk-disconnect</property>
<property name="icon-size">1</property>
</object>
</interface> </interface>

13
debian/changelog vendored
View File

@ -1,3 +1,16 @@
gajim (0.13.90-1) unstable; urgency=low
* New upstream release.
-- Yann Leboulanger <asterix@lagaule.org> Sat, 24 Jul 2010 10:25:26 +0200
gajim (0.13.4-1) unstable; urgency=low
* New upstream release.
* Fix flood when trying to join a full MUC. Closes: #575688
-- Yann Leboulanger <asterix@lagaule.org> Fri, 02 Apr 2010 10:19:59 +0200
gajim (0.13.3-1) unstable; urgency=low gajim (0.13.3-1) unstable; urgency=low
* New upstream release. * New upstream release.

10
debian/control vendored
View File

@ -2,21 +2,21 @@ Source: gajim
Section: net Section: net
Priority: optional Priority: optional
Maintainer: Yann Leboulanger <asterix@lagaule.org> Maintainer: Yann Leboulanger <asterix@lagaule.org>
Build-Depends: debhelper (>= 7), cdbs (>= 0.4.43), python-support (>= 0.7.1), python-dev, libgtk2.0-dev, python-gtk2-dev, gettext (>= 0.17-4), intltool (>= 0.40.1), imagemagick, python-central (>= 0.5) Build-Depends: debhelper (>= 7), cdbs (>= 0.4.43), python-support (>= 0.7.1), python-dev, libgtk2.0-dev, python-gtk2-dev, gettext (>= 0.17-4), intltool (>= 0.40.1), imagemagick
Build-Conflicts: python2.3 Build-Conflicts: python2.3
XS-Python-Version: >= 2.4 XS-Python-Version: >= 2.5
Standards-Version: 3.8.3 Standards-Version: 3.8.3
Homepage: http://www.gajim.org Homepage: http://www.gajim.org
Vcs-Hg: http://hg.gajim.org/gajim/ Vcs-Hg: http://hg.gajim.org/gajim/
Vcs-Browser: http://hg.gajim.org/gajim/file Vcs-Browser: http://hg.gajim.org/gajim/file
Package: gajim Package: gajim
Architecture: any Architecture: all
XB-Python-Version: ${python:Versions} XB-Python-Version: ${python:Versions}
Depends: ${misc:Depends}, ${shlibs:Depends}, ${python:Depends}, python-support (>= 0.7.1), python-glade2 (>= 2.12.0), python-gtk2 (>= 2.12.0), dnsutils Depends: ${misc:Depends}, ${shlibs:Depends}, ${python:Depends}, python-support (>= 0.7.1), python-glade2 (>= 2.12.0), python-gtk2 (>= 2.12.0), dnsutils
Recommends: dbus, python-dbus, notification-daemon, python-gnupginterface, python-openssl, python-crypto Recommends: dbus, python-dbus, notification-daemon, python-gnupginterface, python-openssl, python-crypto
Suggests: python-gconf, python-gnome2, nautilus-sendto, avahi-daemon, python-avahi, network-manager, libgtkspell0, aspell-en, python-gnomekeyring, gnome-keyring, python-sexy, python-kerberos (>= 1.1), texlive-latex-base, dvipng Suggests: python-gconf, python-gnome2, nautilus-sendto, avahi-daemon, python-avahi, network-manager, libgtkspell0, aspell-en, python-gnomekeyring, gnome-keyring, python-sexy, python-kerberos (>= 1.1), texlive-latex-base, dvipng, python-farsight, gstreamer0.10-plugins-ugly
Description: Jabber client written in PyGTK Description: Jabber client written in PyGTK
Gajim is a Jabber client. It has a tabbed user interface with normal chats, Gajim is a Jabber client. It has a tabbed user interface with normal chats,
group chats, and has many features such as, TLS, GPG, SSL, multiple accounts, group chats, and has many features such as, TLS, GPG, SSL, multiple accounts,
avatars, file transfers, D-Bus and Metacontacts. avatars, file transfers, audio/video call, D-Bus and Metacontacts.

View File

@ -1,3 +1,3 @@
data/gajim.1 data/gajim.1
data/gajim-remote.1 data/gajim-remote.1
debian/gajim-history-manager.1 data/gajim-history-manager.1

1
debian/pycompat vendored
View File

@ -1 +0,0 @@
2

2
debian/pyversions vendored
View File

@ -1 +1 @@
2.4 2.5-

7
debian/rules vendored
View File

@ -7,12 +7,11 @@ include /usr/share/cdbs/1/rules/debhelper.mk
include /usr/share/cdbs/1/rules/simple-patchsys.mk include /usr/share/cdbs/1/rules/simple-patchsys.mk
include /usr/share/cdbs/1/class/autotools.mk include /usr/share/cdbs/1/class/autotools.mk
PYTHONVER = 2.5
DEB_CONFIGURE_EXTRA_FLAGS := --prefix=/usr DEB_CONFIGURE_EXTRA_FLAGS := --prefix=/usr
DEB_MAKE_BUILD_TARGET := all PYTHON=python$(PYTHONVER) DEB_MAKE_BUILD_TARGET := all
DEB_MAKE_INSTALL_TARGET = install PYTHON=python$(PYTHONVER) DESTDIR=$(DEB_DESTDIR) DEB_MAKE_INSTALL_TARGET = install DESTDIR=$(DEB_DESTDIR)
binary-install/gajim:: binary-install/gajim::
rm $(DEB_DESTDIR)/usr/share/gajim/src/common/GnuPGInterface.py* rm $(DEB_DESTDIR)/usr/share/gajim/src/common/GnuPGInterface.py*
dh_pysupport -pgajim dh_pysupport -pgajim
convert $(DEB_DESTDIR)/usr/share/pixmaps/gajim.png -resize 32x32 $(DEB_DESTDIR)/usr/share/pixmaps/gajim.xpm convert $(DEB_DESTDIR)/usr/share/icons/hicolor/64x64/apps/gajim.png -resize 32x32 $(DEB_DESTDIR)/usr/share/pixmaps/gajim.xpm

8
debian/watch vendored Normal file
View File

@ -0,0 +1,8 @@
# Control file for uscan
# Run the "uscan" command to check for upstream updates and more.
# See uscan(1) for format
# Compulsory line, this is a version 3 file
version=3
http://www.gajim.org/downloads/([\d.]*)/gajim-([\d\.]*)\.tar\.gz

Binary file not shown.

After

Width:  |  Height:  |  Size: 828 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 767 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

View File

@ -47,3 +47,8 @@ AC_DEFUN([AS_AC_EXPAND],
prefix=$prefix_save prefix=$prefix_save
exec_prefix=$exec_prefix_save exec_prefix=$exec_prefix_save
]) ])
# Fix autoconf: They don't allow shell variables in AC_INIT
# So we have to define them via m4 to be accepted....
m4_include(m4/hgversion.m4)

10
plugins/Makefile.am Normal file
View File

@ -0,0 +1,10 @@
INCLUDES = \
$(PYTHON_INCLUDES)
gajimpluginsdir = $(gajim_pluginsdir)
nobase_dist_gajimplugins_PYTHON = \
$(srcdir)/*.py \
$(srcdir)/*/*.py \
$(srcdir)/*/*.ui
MAINTAINERCLEANFILES = Makefile.in

View File

@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Acronyms expander plugin.
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 9th June 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import sys
import gtk
import gobject
from plugins import GajimPlugin
from plugins.helpers import log, log_calls
class AcronymsExpanderPlugin(GajimPlugin):
name = u'Acronyms Expander'
short_name = u'acronyms_expander'
version = u'0.1'
description = u'''Replaces acronyms (or other strings) with given expansions/substitutes.'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
@log_calls('AcronymsExpanderPlugin')
def init(self):
self.config_dialog = None
self.gui_extension_points = {
'chat_control_base': (self.connect_with_chat_control_base,
self.disconnect_from_chat_control_base)
}
self.config_default_values = {
'INVOKER': (' ', _('')),
'ACRONYMS': ({'RTFM': 'Read The Friendly Manual',
'/slap': '/me slaps',
'PS-': 'plug-in system',
'G-': 'Gajim',
'GNT-': 'http://trac.gajim.org/newticket',
'GW-': 'http://trac.gajim.org/',
'GTS-': 'http://trac.gajim.org/report',
},
_('')),
}
@log_calls('AcronymsExpanderPlugin')
def textbuffer_live_acronym_expander(self, tb):
"""
@param tb gtk.TextBuffer
"""
#assert isinstance(tb,gtk.TextBuffer)
ACRONYMS = self.config['ACRONYMS']
INVOKER = self.config['INVOKER']
t = tb.get_text(tb.get_start_iter(), tb.get_end_iter())
#log.debug('%s %d'%(t, len(t)))
if t and t[-1] == INVOKER:
#log.debug('changing msg text')
base, sep, head=t[:-1].rpartition(INVOKER)
log.debug('%s | %s | %s'%(base, sep, head))
if head in ACRONYMS:
head = ACRONYMS[head]
#log.debug('head: %s'%(head))
t = ''.join((base, sep, head, INVOKER))
#log.debug("setting text: '%s'"%(t))
gobject.idle_add(tb.set_text, t)
@log_calls('AcronymsExpanderPlugin')
def connect_with_chat_control_base(self, chat_control):
d = {}
tv = chat_control.msg_textview
tb = tv.get_buffer()
h_id = tb.connect('changed', self.textbuffer_live_acronym_expander)
d['h_id'] = h_id
chat_control.acronyms_expander_plugin_data = d
return True
@log_calls('AcronymsExpanderPlugin')
def disconnect_from_chat_control_base(self, chat_control):
d = chat_control.acronyms_expander_plugin_data
tv = chat_control.msg_textview
tv.get_buffer().disconnect(d['h_id'])

View File

@ -0,0 +1,2 @@
from plugin import BannerTweaksPlugin

View File

@ -0,0 +1,75 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="window1">
<child>
<object class="GtkVBox" id="banner_tweaks_config_vbox">
<property name="visible">True</property>
<property name="border_width">9</property>
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkCheckButton" id="show_banner_image_checkbutton">
<property name="label" translatable="yes">Display status icon</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, status icon will be displayed in chat window banner.</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_show_banner_image_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="show_banner_online_msg_checkbutton">
<property name="label" translatable="yes">Display status message of contact</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, status message of contact will be displayed in chat window banner.</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_show_banner_online_msg_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="show_banner_resource_checkbutton">
<property name="label" translatable="yes">Display resource name of contact</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, resource name of contact will be displayed in chat window banner.</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_show_banner_resource_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="banner_small_fonts_checkbutton">
<property name="label" translatable="yes">Use small fonts for contact name and resource name</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">If checked, smaller font will be used to display resource name and contact name in chat window banner.</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_banner_small_fonts_checkbutton_toggled"/>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -0,0 +1,205 @@
# -*- coding: utf-8 -*-
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Adjustable chat window banner.
Includes tweaks to make it compact.
Based on patch by pb in ticket #4133:
http://trac.gajim.org/attachment/ticket/4133/gajim-chatbanneroptions-svn10008.patch
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 30 July 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import sys
import gtk
import gobject
import message_control
from common import i18n
from common import gajim
from common import helpers
from plugins import GajimPlugin
from plugins.helpers import log, log_calls
from plugins.gui import GajimPluginConfigDialog
class BannerTweaksPlugin(GajimPlugin):
name = u'Banner Tweaks'
short_name = u'banner_tweaks'
version = u'0.1'
description = u'''Allows user to tweak chat window banner appearance (eg. make it compact).
Based on patch by pb in ticket #4133:
http://trac.gajim.org/attachment/ticket/4133'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
@log_calls('BannerTweaksPlugin')
def init(self):
self.config_dialog = BannerTweaksPluginConfigDialog(self)
self.gui_extension_points = {
'chat_control_base_draw_banner': (self.chat_control_base_draw_banner_called,
self.chat_control_base_draw_banner_deactivation)
}
self.config_default_values = {
'show_banner_image': (True, _('If True, Gajim will display a status icon in the banner of chat windows.')),
'show_banner_online_msg': (True, _('If True, Gajim will display the status message of the contact in the banner of chat windows.')),
'show_banner_resource': (False, _('If True, Gajim will display the resource name of the contact in the banner of chat windows.')),
'banner_small_fonts': (False, _('If True, Gajim will use small fonts for contact name and resource name in the banner of chat windows.')),
'old_chat_avatar_height': (52, _('chat_avatar_height value before plugin was activated')),
}
@log_calls('BannerTweaksPlugin')
def activate(self):
self.config['old_chat_avatar_height'] = gajim.config.get('chat_avatar_height')
#gajim.config.set('chat_avatar_height', 28)
@log_calls('BannerTweaksPlugin')
def deactivate(self):
gajim.config.set('chat_avatar_height', self.config['old_chat_avatar_height'])
@log_calls('BannerTweaksPlugin')
def chat_control_base_draw_banner_called(self, chat_control):
if not self.config['show_banner_online_msg']:
chat_control.banner_status_label.hide()
chat_control.banner_status_label.set_no_show_all(True)
status_text = ''
chat_control.banner_status_label.set_markup(status_text)
if not self.config['show_banner_image']:
banner_status_img = chat_control.xml.get_object('banner_status_image')
banner_status_img.clear()
# TODO: part below repeats a lot of code from ChatControl.draw_banner_text()
# This could be rewritten using re module: getting markup text from
# banner_name_label and replacing some elements based on plugin config.
# Would it be faster?
if self.config['show_banner_resource'] or self.config['banner_small_fonts']:
banner_name_label = chat_control.xml.get_object('banner_name_label')
label_text = banner_name_label.get_label()
contact = chat_control.contact
jid = contact.jid
name = contact.get_shown_name()
if chat_control.resource:
name += '/' + chat_control.resource
elif contact.resource and self.config['show_banner_resource']:
name += '/' + contact.resource
if chat_control.TYPE_ID == message_control.TYPE_PM:
name = _('%(nickname)s from group chat %(room_name)s') %\
{'nickname': name, 'room_name': chat_control.room_name}
name = gobject.markup_escape_text(name)
# We know our contacts nick, but if another contact has the same nick
# in another account we need to also display the account.
# except if we are talking to two different resources of the same contact
acct_info = ''
for account in gajim.contacts.get_accounts():
if account == chat_control.account:
continue
if acct_info: # We already found a contact with same nick
break
for jid in gajim.contacts.get_jid_list(account):
other_contact_ = \
gajim.contacts.get_first_contact_from_jid(account, jid)
if other_contact_.get_shown_name() == chat_control.contact.get_shown_name():
acct_info = ' (%s)' % \
gobject.markup_escape_text(chat_control.account)
break
font_attrs, font_attrs_small = chat_control.get_font_attrs()
if self.config['banner_small_fonts']:
font_attrs = font_attrs_small
st = gajim.config.get('displayed_chat_state_notifications')
cs = contact.chatstate
if cs and st in ('composing_only', 'all'):
if contact.show == 'offline':
chatstate = ''
elif contact.composing_xep == 'XEP-0085':
if st == 'all' or cs == 'composing':
chatstate = helpers.get_uf_chatstate(cs)
else:
chatstate = ''
elif contact.composing_xep == 'XEP-0022':
if cs in ('composing', 'paused'):
# only print composing, paused
chatstate = helpers.get_uf_chatstate(cs)
else:
chatstate = ''
else:
# When does that happen ? See [7797] and [7804]
chatstate = helpers.get_uf_chatstate(cs)
label_text = '<span %s>%s</span><span %s>%s %s</span>' % \
(font_attrs, name, font_attrs_small, acct_info, chatstate)
else:
# weight="heavy" size="x-large"
label_text = '<span %s>%s</span><span %s>%s</span>' % \
(font_attrs, name, font_attrs_small, acct_info)
banner_name_label.set_markup(label_text)
@log_calls('BannerTweaksPlugin')
def chat_control_base_draw_banner_deactivation(self, chat_control):
pass
#chat_control.draw_banner()
class BannerTweaksPluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = gtk.Builder()
self.xml.set_translation_domain(i18n.APP)
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['banner_tweaks_config_vbox'])
self.config_vbox = self.xml.get_object('banner_tweaks_config_vbox')
self.child.pack_start(self.config_vbox)
self.show_banner_image_checkbutton = self.xml.get_object('show_banner_image_checkbutton')
self.show_banner_online_msg_checkbutton = self.xml.get_object('show_banner_online_msg_checkbutton')
self.show_banner_resource_checkbutton = self.xml.get_object('show_banner_resource_checkbutton')
self.banner_small_fonts_checkbutton = self.xml.get_object('banner_small_fonts_checkbutton')
self.xml.connect_signals(self)
def on_run(self):
self.show_banner_image_checkbutton.set_active(self.plugin.config['show_banner_image'])
self.show_banner_online_msg_checkbutton.set_active(self.plugin.config['show_banner_online_msg'])
self.show_banner_resource_checkbutton.set_active(self.plugin.config['show_banner_resource'])
self.banner_small_fonts_checkbutton.set_active(self.plugin.config['banner_small_fonts'])
def on_show_banner_image_checkbutton_toggled(self, button):
self.plugin.config['show_banner_image'] = button.get_active()
def on_show_banner_online_msg_checkbutton_toggled(self, button):
self.plugin.config['show_banner_online_msg'] = button.get_active()
def on_show_banner_resource_checkbutton_toggled(self, button):
self.plugin.config['show_banner_resource'] = button.get_active()
def on_banner_small_fonts_checkbutton_toggled(self, button):
self.plugin.config['banner_small_fonts'] = button.get_active()

View File

@ -0,0 +1 @@
from plugin import DBusPlugin

View File

@ -0,0 +1,738 @@
# -*- coding: utf-8 -*-
## Copyright (C) 2005-2006 Yann Leboulanger <asterix@lagaule.org>
## Copyright (C) 2005-2006 Nikos Kouremenos <kourem@gmail.com>
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov@gmail.com>
## Copyright (C) 2005-2006 Andrew Sayman <lorien420@myrealbox.com>
## Copyright (C) 2007 Lukas Petrovicky <lukas@petrovicky.net>
## Copyright (C) 2007 Julien Pivotto <roidelapluie@gmail.com>
## Copyright (C) 2007 Travis Shirk <travis@pobox.com>
## Copyright (C) 2008 Mateusz Biliński <mateusz@bilinski.it>
##
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
D-BUS Support plugin.
Based on src/remote_control.py
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 8th August 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import os
import new
import gobject
from common import dbus_support
if dbus_support.supported:
import dbus
if dbus_support:
INTERFACE = 'org.gajim.dbusplugin.RemoteInterface'
OBJ_PATH = '/org/gajim/dbusplugin/RemoteObject'
SERVICE = 'org.gajim.dbusplugin'
import dbus.service
import dbus.glib
# type mapping
# in most cases it is a utf-8 string
DBUS_STRING = dbus.String
# general type (for use in dicts, where all values should have the same type)
DBUS_BOOLEAN = dbus.Boolean
DBUS_DOUBLE = dbus.Double
DBUS_INT32 = dbus.Int32
# dictionary with string key and binary value
DBUS_DICT_SV = lambda : dbus.Dictionary({}, signature="sv")
# dictionary with string key and value
DBUS_DICT_SS = lambda : dbus.Dictionary({}, signature="ss")
# empty type (there is no equivalent of None on D-Bus, but historically gajim
# used 0 instead)
DBUS_NONE = lambda : dbus.Int32(0)
def get_dbus_struct(obj):
''' recursively go through all the items and replace
them with their casted dbus equivalents
'''
if obj is None:
return DBUS_NONE()
if isinstance(obj, (unicode, str)):
return DBUS_STRING(obj)
if isinstance(obj, int):
return DBUS_INT32(obj)
if isinstance(obj, float):
return DBUS_DOUBLE(obj)
if isinstance(obj, bool):
return DBUS_BOOLEAN(obj)
if isinstance(obj, (list, tuple)):
result = dbus.Array([get_dbus_struct(i) for i in obj],
signature='v')
if result == []:
return DBUS_NONE()
return result
if isinstance(obj, dict):
result = DBUS_DICT_SV()
for key, value in obj.items():
result[DBUS_STRING(key)] = get_dbus_struct(value)
if result == {}:
return DBUS_NONE()
return result
# unknown type
return DBUS_NONE()
class SignalObject(dbus.service.Object):
''' Local object definition for /org/gajim/dbus/RemoteObject.
(This docstring is not be visible, because the clients can access only the remote object.)'''
def __init__(self, bus_name):
self.first_show = True
self.vcard_account = None
# register our dbus API
dbus.service.Object.__init__(self, bus_name, OBJ_PATH)
@dbus.service.signal(INTERFACE, signature='av')
def Roster(self, account_and_data):
pass
@dbus.service.signal(INTERFACE, signature='av')
def AccountPresence(self, status_and_account):
pass
@dbus.service.signal(INTERFACE, signature='av')
def ContactPresence(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def ContactAbsence(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def ContactStatus(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def NewMessage(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def Subscribe(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def Subscribed(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def Unsubscribed(self, account_and_jid):
pass
@dbus.service.signal(INTERFACE, signature='av')
def NewAccount(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def VcardInfo(self, account_and_vcard):
pass
@dbus.service.signal(INTERFACE, signature='av')
def LastStatusTime(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def OsInfo(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def GCPresence(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def GCMessage(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def RosterInfo(self, account_and_array):
pass
@dbus.service.signal(INTERFACE, signature='av')
def NewGmail(self, account_and_array):
pass
def raise_signal(self, signal, arg):
'''raise a signal, with a single argument of unspecified type
Instead of obj.raise_signal("Foo", bar), use obj.Foo(bar).'''
getattr(self, signal)(arg)
@dbus.service.method(INTERFACE, in_signature='s', out_signature='s')
def get_status(self, account):
'''Returns status (show to be exact) which is the global one
unless account is given'''
if not account:
# If user did not ask for account, returns the global status
return DBUS_STRING(helpers.get_global_show())
# return show for the given account
index = gajim.connections[account].connected
return DBUS_STRING(gajim.SHOW_LIST[index])
@dbus.service.method(INTERFACE, in_signature='s', out_signature='s')
def get_status_message(self, account):
'''Returns status which is the global one
unless account is given'''
if not account:
# If user did not ask for account, returns the global status
return DBUS_STRING(str(helpers.get_global_status()))
# return show for the given account
status = gajim.connections[account].status
return DBUS_STRING(status)
def _get_account_and_contact(self, account, jid):
'''get the account (if not given) and contact instance from jid'''
connected_account = None
contact = None
accounts = gajim.contacts.get_accounts()
# if there is only one account in roster, take it as default
# if user did not ask for account
if not account and len(accounts) == 1:
account = accounts[0]
if account:
if gajim.connections[account].connected > 1: # account is connected
connected_account = account
contact = gajim.contacts.get_contact_with_highest_priority(account,
jid)
else:
for account in accounts:
contact = gajim.contacts.get_contact_with_highest_priority(account,
jid)
if contact and gajim.connections[account].connected > 1:
# account is connected
connected_account = account
break
if not contact:
contact = jid
return connected_account, contact
def _get_account_for_groupchat(self, account, room_jid):
'''get the account which is connected to groupchat (if not given)
or check if the given account is connected to the groupchat'''
connected_account = None
accounts = gajim.contacts.get_accounts()
# if there is only one account in roster, take it as default
# if user did not ask for account
if not account and len(accounts) == 1:
account = accounts[0]
if account:
if gajim.connections[account].connected > 1 and \
room_jid in gajim.gc_connected[account] and \
gajim.gc_connected[account][room_jid]:
# account and groupchat are connected
connected_account = account
else:
for account in accounts:
if gajim.connections[account].connected > 1 and \
room_jid in gajim.gc_connected[account] and \
gajim.gc_connected[account][room_jid]:
# account and groupchat are connected
connected_account = account
break
return connected_account
@dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
def send_file(self, file_path, jid, account):
'''send file, located at 'file_path' to 'jid', using account
(optional) 'account' '''
jid = self._get_real_jid(jid, account)
connected_account, contact = self._get_account_and_contact(account, jid)
if connected_account:
if file_path[:7] == 'file://':
file_path=file_path[7:]
if os.path.isfile(file_path): # is it file?
gajim.interface.instances['file_transfers'].send_file(
connected_account, contact, file_path)
return DBUS_BOOLEAN(True)
return DBUS_BOOLEAN(False)
def _send_message(self, jid, message, keyID, account, type = 'chat',
subject = None):
'''can be called from send_chat_message (default when send_message)
or send_single_message'''
if not jid or not message:
return DBUS_BOOLEAN(False)
if not keyID:
keyID = ''
connected_account, contact = self._get_account_and_contact(account, jid)
if connected_account:
connection = gajim.connections[connected_account]
connection.send_message(jid, message, keyID, type, subject)
return DBUS_BOOLEAN(True)
return DBUS_BOOLEAN(False)
@dbus.service.method(INTERFACE, in_signature='ssss', out_signature='b')
def send_chat_message(self, jid, message, keyID, account):
'''Send chat 'message' to 'jid', using account (optional) 'account'.
if keyID is specified, encrypt the message with the pgp key '''
jid = self._get_real_jid(jid, account)
return self._send_message(jid, message, keyID, account)
@dbus.service.method(INTERFACE, in_signature='sssss', out_signature='b')
def send_single_message(self, jid, subject, message, keyID, account):
'''Send single 'message' to 'jid', using account (optional) 'account'.
if keyID is specified, encrypt the message with the pgp key '''
jid = self._get_real_jid(jid, account)
return self._send_message(jid, message, keyID, account, type, subject)
@dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
def send_groupchat_message(self, room_jid, message, account):
'''Send 'message' to groupchat 'room_jid',
using account (optional) 'account'.'''
if not room_jid or not message:
return DBUS_BOOLEAN(False)
connected_account = self._get_account_for_groupchat(account, room_jid)
if connected_account:
connection = gajim.connections[connected_account]
connection.send_gc_message(room_jid, message)
return DBUS_BOOLEAN(True)
return DBUS_BOOLEAN(False)
@dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
def open_chat(self, jid, account):
'''Shows the tabbed window for new message to 'jid', using account
(optional) 'account' '''
if not jid:
raise MissingArgument
return DBUS_BOOLEAN(False)
jid = self._get_real_jid(jid, account)
try:
jid = helpers.parse_jid(jid)
except:
# Jid is not conform, ignore it
return DBUS_BOOLEAN(False)
if account:
accounts = [account]
else:
accounts = gajim.connections.keys()
if len(accounts) == 1:
account = accounts[0]
connected_account = None
first_connected_acct = None
for acct in accounts:
if gajim.connections[acct].connected > 1: # account is online
contact = gajim.contacts.get_first_contact_from_jid(acct, jid)
if gajim.interface.msg_win_mgr.has_window(jid, acct):
connected_account = acct
break
# jid is in roster
elif contact:
connected_account = acct
break
# we send the message to jid not in roster, because account is
# specified, or there is only one account
elif account:
connected_account = acct
elif first_connected_acct is None:
first_connected_acct = acct
# if jid is not a conntact, open-chat with first connected account
if connected_account is None and first_connected_acct:
connected_account = first_connected_acct
if connected_account:
gajim.interface.new_chat_from_jid(connected_account, jid)
# preserve the 'steal focus preservation'
win = gajim.interface.msg_win_mgr.get_window(jid,
connected_account).window
if win.get_property('visible'):
win.window.focus()
return DBUS_BOOLEAN(True)
return DBUS_BOOLEAN(False)
@dbus.service.method(INTERFACE, in_signature='sss', out_signature='b')
def change_status(self, status, message, account):
''' change_status(status, message, account). account is optional -
if not specified status is changed for all accounts. '''
if status not in ('offline', 'online', 'chat',
'away', 'xa', 'dnd', 'invisible'):
return DBUS_BOOLEAN(False)
if account:
gobject.idle_add(gajim.interface.roster.send_status, account,
status, message)
else:
# account not specified, so change the status of all accounts
for acc in gajim.contacts.get_accounts():
if not gajim.config.get_per('accounts', acc,
'sync_with_global_status'):
continue
gobject.idle_add(gajim.interface.roster.send_status, acc,
status, message)
return DBUS_BOOLEAN(False)
@dbus.service.method(INTERFACE, in_signature='', out_signature='')
def show_next_pending_event(self):
'''Show the window(s) with next pending event in tabbed/group chats.'''
if gajim.events.get_nb_events():
gajim.interface.systray.handle_first_event()
@dbus.service.method(INTERFACE, in_signature='s', out_signature='a{sv}')
def contact_info(self, jid):
'''get vcard info for a contact. Return cached value of the vcard.
'''
if not isinstance(jid, unicode):
jid = unicode(jid)
if not jid:
raise MissingArgument
return DBUS_DICT_SV()
jid = self._get_real_jid(jid)
cached_vcard = gajim.connections.values()[0].get_cached_vcard(jid)
if cached_vcard:
return get_dbus_struct(cached_vcard)
# return empty dict
return DBUS_DICT_SV()
@dbus.service.method(INTERFACE, in_signature='', out_signature='as')
def list_accounts(self):
'''list register accounts'''
result = gajim.contacts.get_accounts()
result_array = dbus.Array([], signature='s')
if result and len(result) > 0:
for account in result:
result_array.append(DBUS_STRING(account))
return result_array
@dbus.service.method(INTERFACE, in_signature='s', out_signature='a{ss}')
def account_info(self, account):
'''show info on account: resource, jid, nick, prio, message'''
result = DBUS_DICT_SS()
if gajim.connections.has_key(account):
# account is valid
con = gajim.connections[account]
index = con.connected
result['status'] = DBUS_STRING(gajim.SHOW_LIST[index])
result['name'] = DBUS_STRING(con.name)
result['jid'] = DBUS_STRING(gajim.get_jid_from_account(con.name))
result['message'] = DBUS_STRING(con.status)
result['priority'] = DBUS_STRING(unicode(con.priority))
result['resource'] = DBUS_STRING(unicode(gajim.config.get_per(
'accounts', con.name, 'resource')))
return result
@dbus.service.method(INTERFACE, in_signature='s', out_signature='aa{sv}')
def list_contacts(self, account):
'''list all contacts in the roster. If the first argument is specified,
then return the contacts for the specified account'''
result = dbus.Array([], signature='aa{sv}')
accounts = gajim.contacts.get_accounts()
if len(accounts) == 0:
return result
if account:
accounts_to_search = [account]
else:
accounts_to_search = accounts
for acct in accounts_to_search:
if acct in accounts:
for jid in gajim.contacts.get_jid_list(acct):
item = self._contacts_as_dbus_structure(
gajim.contacts.get_contacts(acct, jid))
if item:
result.append(item)
return result
@dbus.service.method(INTERFACE, in_signature='', out_signature='')
def toggle_roster_appearance(self):
''' shows/hides the roster window '''
win = gajim.interface.roster.window
if win.get_property('visible'):
gobject.idle_add(win.hide)
else:
win.present()
# preserve the 'steal focus preservation'
if self._is_first():
win.window.focus()
else:
win.window.focus(long(time()))
@dbus.service.method(INTERFACE, in_signature='', out_signature='')
def toggle_ipython(self):
''' shows/hides the ipython window '''
win = gajim.ipython_window
if win:
if win.window.is_visible():
gobject.idle_add(win.hide)
else:
win.show_all()
win.present()
else:
gajim.interface.create_ipython_window()
@dbus.service.method(INTERFACE, in_signature='', out_signature='a{ss}')
def prefs_list(self):
prefs_dict = DBUS_DICT_SS()
def get_prefs(data, name, path, value):
if value is None:
return
key = ''
if path is not None:
for node in path:
key += node + '#'
key += name
prefs_dict[DBUS_STRING(key)] = DBUS_STRING(value[1])
gajim.config.foreach(get_prefs)
return prefs_dict
@dbus.service.method(INTERFACE, in_signature='', out_signature='b')
def prefs_store(self):
try:
gajim.interface.save_config()
except Exception, e:
return DBUS_BOOLEAN(False)
return DBUS_BOOLEAN(True)
@dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
def prefs_del(self, key):
if not key:
return DBUS_BOOLEAN(False)
key_path = key.split('#', 2)
if len(key_path) != 3:
return DBUS_BOOLEAN(False)
if key_path[2] == '*':
gajim.config.del_per(key_path[0], key_path[1])
else:
gajim.config.del_per(key_path[0], key_path[1], key_path[2])
return DBUS_BOOLEAN(True)
@dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
def prefs_put(self, key):
if not key:
return DBUS_BOOLEAN(False)
key_path = key.split('#', 2)
if len(key_path) < 3:
subname, value = key.split('=', 1)
gajim.config.set(subname, value)
return DBUS_BOOLEAN(True)
subname, value = key_path[2].split('=', 1)
gajim.config.set_per(key_path[0], key_path[1], subname, value)
return DBUS_BOOLEAN(True)
@dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
def add_contact(self, jid, account):
if account:
if account in gajim.connections and \
gajim.connections[account].connected > 1:
# if given account is active, use it
AddNewContactWindow(account = account, jid = jid)
else:
# wrong account
return DBUS_BOOLEAN(False)
else:
# if account is not given, show account combobox
AddNewContactWindow(account = None, jid = jid)
return DBUS_BOOLEAN(True)
@dbus.service.method(INTERFACE, in_signature='ss', out_signature='b')
def remove_contact(self, jid, account):
jid = self._get_real_jid(jid, account)
accounts = gajim.contacts.get_accounts()
# if there is only one account in roster, take it as default
if account:
accounts = [account]
contact_exists = False
for account in accounts:
contacts = gajim.contacts.get_contacts(account, jid)
if contacts:
gajim.connections[account].unsubscribe(jid)
for contact in contacts:
gajim.interface.roster.remove_contact(contact, account)
gajim.contacts.remove_jid(account, jid)
contact_exists = True
return DBUS_BOOLEAN(contact_exists)
def _is_first(self):
if self.first_show:
self.first_show = False
return True
return False
def _get_real_jid(self, jid, account = None):
'''get the real jid from the given one: removes xmpp: or get jid from nick
if account is specified, search only in this account
'''
if account:
accounts = [account]
else:
accounts = gajim.connections.keys()
if jid.startswith('xmpp:'):
return jid[5:] # len('xmpp:') = 5
nick_in_roster = None # Is jid a nick ?
for account in accounts:
# Does jid exists in roster of one account ?
if gajim.contacts.get_contacts(account, jid):
return jid
if not nick_in_roster:
# look in all contact if one has jid as nick
for jid_ in gajim.contacts.get_jid_list(account):
c = gajim.contacts.get_contacts(account, jid_)
if c[0].name == jid:
nick_in_roster = jid_
break
if nick_in_roster:
# We have not found jid in roster, but we found is as a nick
return nick_in_roster
# We have not found it as jid nor as nick, probably a not in roster jid
return jid
def _contacts_as_dbus_structure(self, contacts):
''' get info from list of Contact objects and create dbus dict '''
if not contacts:
return None
prim_contact = None # primary contact
for contact in contacts:
if prim_contact is None or contact.priority > prim_contact.priority:
prim_contact = contact
contact_dict = DBUS_DICT_SV()
contact_dict['name'] = DBUS_STRING(prim_contact.name)
contact_dict['show'] = DBUS_STRING(prim_contact.show)
contact_dict['jid'] = DBUS_STRING(prim_contact.jid)
if prim_contact.keyID:
keyID = None
if len(prim_contact.keyID) == 8:
keyID = prim_contact.keyID
elif len(prim_contact.keyID) == 16:
keyID = prim_contact.keyID[8:]
if keyID:
contact_dict['openpgp'] = keyID
contact_dict['resources'] = dbus.Array([], signature='(sis)')
for contact in contacts:
resource_props = dbus.Struct((DBUS_STRING(contact.resource),
dbus.Int32(contact.priority), DBUS_STRING(contact.status)))
contact_dict['resources'].append(resource_props)
contact_dict['groups'] = dbus.Array([], signature='(s)')
for group in prim_contact.groups:
contact_dict['groups'].append((DBUS_STRING(group),))
return contact_dict
@dbus.service.method(INTERFACE, in_signature='', out_signature='s')
def get_unread_msgs_number(self):
return DBUS_STRING(str(gajim.events.get_nb_events()))
@dbus.service.method(INTERFACE, in_signature='s', out_signature='b')
def start_chat(self, account):
if not account:
# error is shown in gajim-remote check_arguments(..)
return DBUS_BOOLEAN(False)
NewChatDialog(account)
return DBUS_BOOLEAN(True)
@dbus.service.method(INTERFACE, in_signature='ss', out_signature='')
def send_xml(self, xml, account):
if account:
gajim.connections[account].send_stanza(xml)
else:
for acc in gajim.contacts.get_accounts():
gajim.connections[acc].send_stanza(xml)
@dbus.service.method(INTERFACE, in_signature='ssss', out_signature='')
def join_room(self, room_jid, nick, password, account):
if not account:
# get the first connected account
accounts = gajim.connections.keys()
for acct in accounts:
if gajim.account_is_connected(acct):
account = acct
break
if not account:
return
if not nick:
nick = ''
gajim.interface.instances[account]['join_gc'] = \
JoinGroupchatWindow(account, room_jid, nick)
else:
gajim.interface.join_gc_room(account, room_jid, nick, password)
from common import gajim
from common import helpers
from time import time
from dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
from common import ged
class DBusPlugin(GajimPlugin):
name = u'D-Bus Support'
short_name = u'dbus'
version = u'0.1'
description = u'''D-Bus support. Based on remote_control module from
Gajim core but uses new events handling system.'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
@log_calls('DBusPlugin')
def init(self):
self.config_dialog = None
#self.gui_extension_points = {}
#self.config_default_values = {}
self.events_names = ['Roster', 'AccountPresence', 'ContactPresence',
'ContactAbsence', 'ContactStatus', 'NewMessage',
'Subscribe', 'Subscribed', 'Unsubscribed',
'NewAccount', 'VcardInfo', 'LastStatusTime',
'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo',
'NewGmail']
self.signal_object = None
self.events_handlers = {}
self._set_handling_methods()
@log_calls('DBusPlugin')
def activate(self):
session_bus = dbus_support.session_bus.SessionBus()
bus_name = dbus.service.BusName(SERVICE, bus=session_bus)
self.signal_object = SignalObject(bus_name)
@log_calls('DBusPlugin')
def deactivate(self):
self.signal_object.remove_from_connection()
self.signal_object = None
@log_calls('DBusPlugin')
def _set_handling_methods(self):
for event_name in self.events_names:
setattr(self, event_name,
new.instancemethod(
self._generate_handling_method(event_name),
self,
DBusPlugin))
self.events_handlers[event_name] = (ged.POSTCORE,
getattr(self, event_name))
def _generate_handling_method(self, event_name):
def handler(self, arg):
#print "Handler of event %s called"%(event_name)
if self.signal_object:
getattr(self.signal_object, event_name)(get_dbus_struct(arg))
return handler

View File

@ -0,0 +1 @@
from plugin import EventsDumpPlugin

View File

@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
##
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Events Dump plugin.
Dumps info about selected events to console.
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 10th August 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import new
from pprint import pformat
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
from common import ged
class EventsDumpPlugin(GajimPlugin):
name = u'Events Dump'
short_name = u'events_dump'
version = u'0.1'
description = u'''Dumps info about selected events to console.'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
@log_calls('EventsDumpPlugin')
def init(self):
self.config_dialog = None
#self.gui_extension_points = {}
#self.config_default_values = {}
events_from_old_dbus_support = [
'Roster', 'AccountPresence', 'ContactPresence',
'ContactAbsence', 'ContactStatus', 'NewMessage',
'Subscribe', 'Subscribed', 'Unsubscribed',
'NewAccount', 'VcardInfo', 'LastStatusTime',
'OsInfo', 'GCPresence', 'GCMessage', 'RosterInfo',
'NewGmail']
events_from_src_gajim = [
'ROSTER', 'WARNING', 'ERROR',
'INFORMATION', 'ERROR_ANSWER', 'STATUS',
'NOTIFY', 'MSGERROR', 'MSGSENT', 'MSGNOTSENT',
'SUBSCRIBED', 'UNSUBSCRIBED', 'SUBSCRIBE',
'AGENT_ERROR_INFO', 'AGENT_ERROR_ITEMS',
'AGENT_REMOVED', 'REGISTER_AGENT_INFO',
'AGENT_INFO_ITEMS', 'AGENT_INFO_INFO',
'QUIT', 'NEW_ACC_CONNECTED',
'NEW_ACC_NOT_CONNECTED', 'ACC_OK', 'ACC_NOT_OK',
'MYVCARD', 'VCARD', 'LAST_STATUS_TIME', 'OS_INFO',
'GC_NOTIFY', 'GC_MSG', 'GC_SUBJECT', 'GC_CONFIG',
'GC_CONFIG_CHANGE', 'GC_INVITATION',
'GC_AFFILIATION', 'GC_PASSWORD_REQUIRED',
'BAD_PASSPHRASE', 'ROSTER_INFO', 'BOOKMARKS',
'CON_TYPE', 'CONNECTION_LOST', 'FILE_REQUEST',
'GMAIL_NOTIFY', 'FILE_REQUEST_ERROR',
'FILE_SEND_ERROR', 'STANZA_ARRIVED', 'STANZA_SENT',
'HTTP_AUTH', 'VCARD_PUBLISHED',
'VCARD_NOT_PUBLISHED', 'ASK_NEW_NICK', 'SIGNED_IN',
'METACONTACTS', 'ATOM_ENTRY', 'FAILED_DECRYPT',
'PRIVACY_LISTS_RECEIVED', 'PRIVACY_LIST_RECEIVED',
'PRIVACY_LISTS_ACTIVE_DEFAULT',
'PRIVACY_LIST_REMOVED', 'ZC_NAME_CONFLICT',
'PING_SENT', 'PING_REPLY', 'PING_ERROR',
'SEARCH_FORM', 'SEARCH_RESULT',
'RESOURCE_CONFLICT', 'PEP_CONFIG',
'UNIQUE_ROOM_ID_UNSUPPORTED',
'UNIQUE_ROOM_ID_SUPPORTED', 'SESSION_NEG',
'GPG_PASSWORD_REQUIRED', 'SSL_ERROR',
'FINGERPRINT_ERROR', 'PLAIN_CONNECTION',
'PUBSUB_NODE_REMOVED', 'PUBSUB_NODE_NOT_REMOVED']
network_events_from_core = ['raw-message-received',
'raw-iq-received',
'raw-pres-received']
network_events_generated_in_nec = [
'customized-message-received',
'more-customized-message-received',
'modify-only-message-received',
'enriched-chat-message-received']
self.events_names = []
self.events_names += network_events_from_core
self.events_names += network_events_generated_in_nec
self.events_handlers = {}
self._set_handling_methods()
@log_calls('EventsDumpPlugin')
def activate(self):
pass
@log_calls('EventsDumpPlugin')
def deactivate(self):
pass
@log_calls('EventsDumpPlugin')
def _set_handling_methods(self):
for event_name in self.events_names:
setattr(self, event_name,
new.instancemethod(
self._generate_handling_method(event_name),
self,
EventsDumpPlugin))
self.events_handlers[event_name] = (ged.POSTCORE,
getattr(self, event_name))
def _generate_handling_method(self, event_name):
def handler(self, *args):
print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(args))
return handler

View File

@ -0,0 +1 @@
from plugin import GoogleTranslationPlugin

View File

@ -0,0 +1,118 @@
# -*- coding: utf-8 -*-
##
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Google Translation plugin.
Translates (currently only incoming) messages using Google Translate.
:note: consider this as proof-of-concept
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 25th August 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import re
import urllib2
import new
from pprint import pformat
from common import helpers
from common import gajim
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
from common import ged
from common import nec
class GoogleTranslationPlugin(GajimPlugin):
name = u'Google Translation'
short_name = u'google_translation'
version = u'0.1'
description = u'''Translates (currently only incoming) messages using Google Translate.'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
@log_calls('GoogleTranslationPlugin')
def init(self):
self.config_dialog = None
#self.gui_extension_points = {}
self.config_default_values = {'from_lang' : (u'en', _(u'Language of text to be translated')),
'to_lang' : (u'fr', _(u'Language to which translation will be made')),
'user_agent' : (u'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.12) Gecko/20080213 Firefox/2.0.0.11',
_(u'User Agent data to be used with urllib2 when connecting to Google Translate service'))}
#self.events_handlers = {}
self.events = [GoogleTranslateMessageReceivedEvent]
self.translated_text_re = \
re.compile(r'google.language.callbacks.id100\(\'22\',{"translatedText":"(?P<text>[^"]*)"}, 200, null, 200\)')
@log_calls('GoogleTranslationPlugin')
def translate_text(self, text, from_lang, to_lang):
text = self.prepare_text_for_url(text)
headers = { 'User-Agent' : self.config['user_agent'] }
translation_url = u'http://www.google.com/uds/Gtranslate?callback=google.language.callbacks.id100&context=22&q=%(text)s&langpair=%(from_lang)s%%7C%(to_lang)s&key=notsupplied&v=1.0'%locals()
request = urllib2.Request(translation_url, headers=headers)
response = urllib2.urlopen(request)
results = response.read()
translated_text = self.translated_text_re.search(results).group('text')
return translated_text
@log_calls('GoogleTranslationPlugin')
def prepare_text_for_url(self, text):
'''
Converts text so it can be used within URL as query to Google Translate.
'''
# There should be more replacements for plugin to work in any case:
char_replacements = { ' ' : '%20',
'+' : '%2B'}
for char, replacement in char_replacements.iteritems():
text = text.replace(char, replacement)
return text
@log_calls('GoogleTranslationPlugin')
def activate(self):
pass
@log_calls('GoogleTranslationPlugin')
def deactivate(self):
pass
class GoogleTranslateMessageReceivedEvent(nec.NetworkIncomingEvent):
name = 'google-translate-message-received'
base_network_events = ['raw-message-received']
def generate(self):
msg_type = self.base_event.xmpp_msg.attrs.get('type', None)
if msg_type == u'chat':
msg_text = "".join(self.base_event.xmpp_msg.kids[0].data)
if msg_text:
from_lang = self.plugin.config['from_lang']
to_lang = self.plugin.config['to_lang']
self.base_event.xmpp_msg.kids[0].setData(
self.plugin.translate_text(msg_text, from_lang, to_lang))
return False # We only want to modify old event, not emit another,
# so we return False here.

View File

@ -0,0 +1,2 @@
from length_notifier import LengthNotifierPlugin

View File

@ -0,0 +1,152 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="window1">
<child>
<object class="GtkTable" id="length_notifier_config_table">
<property name="visible">True</property>
<property name="border_width">9</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="column_spacing">7</property>
<property name="row_spacing">5</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="tooltip_text" translatable="yes">Message length at which notification is invoked.</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Message length:</property>
</object>
<packing>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="tooltip_text" translatable="yes">Background color of text entry field in chat window when notification is invoked.</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Notification color:</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="tooltip_text" translatable="yes">JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). Use comma (without space) as separator. If empty plugin is used with every JID. </property>
<property name="xalign">0</property>
<property name="label" translatable="yes">JabberIDs to include:</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="jids_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). Use comma (without space) as separator. If empty plugin is used with every JID. </property>
<signal name="editing_done" handler="on_jids_entry_editing_done"/>
<signal name="changed" handler="on_jids_entry_changed"/>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<object class="GtkColorButton" id="notification_colorbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes">Background color of text entry field in chat window when notification is invoked.</property>
<property name="xalign">0</property>
<property name="title" translatable="yes">Pick a color for notification</property>
<property name="color">#000000000000</property>
<signal name="color_set" handler="on_notification_colorbutton_color_set"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<child>
<object class="GtkSpinButton" id="message_length_spinbutton">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip_text" translatable="yes">Message length at which notification is invoked.</property>
<property name="width_chars">6</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
<signal name="value_changed" handler="on_message_length_spinbutton_value_changed"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="x_options">GTK_FILL</property>
<property name="y_options">GTK_FILL</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -0,0 +1,161 @@
# -*- coding: utf-8 -*-
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Message length notifier plugin.
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 1st June 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import sys
import gtk
from common import i18n
from plugins import GajimPlugin
from plugins.helpers import log, log_calls
from plugins.gui import GajimPluginConfigDialog
class LengthNotifierPlugin(GajimPlugin):
name = u'Message Length Notifier'
short_name = u'length_notifier'
version = u'0.1'
description = u'''Highlights message entry field in chat window when given length of message is exceeded.'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
@log_calls('LengthNotifierPlugin')
def init(self):
self.config_dialog = LengthNotifierPluginConfigDialog(self)
self.gui_extension_points = {
'chat_control' : (self.connect_with_chat_control,
self.disconnect_from_chat_control)
}
self.config_default_values = {'MESSAGE_WARNING_LENGTH' : (140, _('Message length at which notification is invoked.')),
'WARNING_COLOR' : ('#F0DB3E', _('Background color of text entry field in chat window when notification is invoked.')),
'JIDS' : ([], _('JabberIDs that plugin should be used with (eg. restrict only to one microblogging bot). If empty plugin is used with every JID. [not implemented]'))
}
@log_calls('LengthNotifierPlugin')
def textview_length_warning(self, tb, chat_control):
tv = chat_control.msg_textview
d = chat_control.length_notifier_plugin_data
t = tb.get_text(tb.get_start_iter(), tb.get_end_iter())
if t:
len_t = len(t)
#print("len_t: %d"%(len_t))
if len_t>self.config['MESSAGE_WARNING_LENGTH']:
if not d['prev_color']:
d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL]
tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR']))
elif d['prev_color']:
tv.modify_base(gtk.STATE_NORMAL, d['prev_color'])
d['prev_color'] = None
@log_calls('LengthNotifierPlugin')
def connect_with_chat_control(self, chat_control):
jid = chat_control.contact.jid
if self.jid_is_ok(jid):
d = {'prev_color' : None}
tv = chat_control.msg_textview
tb = tv.get_buffer()
h_id = tb.connect('changed', self.textview_length_warning, chat_control)
d['h_id'] = h_id
t = tb.get_text(tb.get_start_iter(), tb.get_end_iter())
if t:
len_t = len(t)
if len_t>self.config['MESSAGE_WARNING_LENGTH']:
d['prev_color'] = tv.style.copy().base[gtk.STATE_NORMAL]
tv.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.config['WARNING_COLOR']))
chat_control.length_notifier_plugin_data = d
return True
return False
@log_calls('LengthNotifierPlugin')
def disconnect_from_chat_control(self, chat_control):
try:
d = chat_control.length_notifier_plugin_data
tv = chat_control.msg_textview
tv.get_buffer().disconnect(d['h_id'])
if d['prev_color']:
tv.modify_base(gtk.STATE_NORMAL, d['prev_color'])
except AttributeError, error:
pass
#log.debug('Length Notifier Plugin was (probably) never connected with this chat window.\n Error: %s' % (error))
@log_calls('LengthNotifierPlugin')
def jid_is_ok(self, jid):
if jid in self.config['JIDS'] or not self.config['JIDS']:
return True
return False
class LengthNotifierPluginConfigDialog(GajimPluginConfigDialog):
def init(self):
self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path(
'config_dialog.ui')
self.xml = gtk.Builder()
self.xml.set_translation_domain(i18n.APP)
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['length_notifier_config_table'])
self.config_table = self.xml.get_object('length_notifier_config_table')
self.child.pack_start(self.config_table)
self.message_length_spinbutton = self.xml.get_object(
'message_length_spinbutton')
self.message_length_spinbutton.get_adjustment().set_all(140, 0, 500, 1,
10, 0)
self.notification_colorbutton = self.xml.get_object(
'notification_colorbutton')
self.jids_entry = self.xml.get_object('jids_entry')
self.xml.connect_signals(self)
def on_run(self):
self.message_length_spinbutton.set_value(self.plugin.config['MESSAGE_WARNING_LENGTH'])
self.notification_colorbutton.set_color(gtk.gdk.color_parse(self.plugin.config['WARNING_COLOR']))
#self.jids_entry.set_text(self.plugin.config['JIDS'])
self.jids_entry.set_text(','.join(self.plugin.config['JIDS']))
@log_calls('LengthNotifierPluginConfigDialog')
def on_message_length_spinbutton_value_changed(self, spinbutton):
self.plugin.config['MESSAGE_WARNING_LENGTH'] = spinbutton.get_value()
@log_calls('LengthNotifierPluginConfigDialog')
def on_notification_colorbutton_color_set(self, colorbutton):
self.plugin.config['WARNING_COLOR'] = colorbutton.get_color().to_string()
@log_calls('LengthNotifierPluginConfigDialog')
def on_jids_entry_changed(self, entry):
text = entry.get_text()
if len(text)>0:
self.plugin.config['JIDS'] = entry.get_text().split(',')
else:
self.plugin.config['JIDS'] = []
@log_calls('LengthNotifierPluginConfigDialog')
def on_jids_entry_editing_done(self, entry):
pass

View File

@ -0,0 +1 @@
from plugin import NewEventsExamplePlugin

View File

@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
##
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
New Events Example plugin.
Demonstrates how to use Network Events Controller to generate new events
based on existing one.
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 15th August 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import new
from pprint import pformat
from common import helpers
from common import gajim
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
from common import ged
from common import nec
class NewEventsExamplePlugin(GajimPlugin):
name = u'New Events Example'
short_name = u'new_events_example'
version = u'0.1'
description = u'''Shows how to generate new network events based on existing one using Network Events Controller.'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
@log_calls('NewEventsExamplePlugin')
def init(self):
self.config_dialog = None
#self.gui_extension_points = {}
#self.config_default_values = {}
self.events_handlers = {'raw-message-received' :
(ged.POSTCORE,
self.raw_message_received),
'customized-message-received' :
(ged.POSTCORE,
self.customized_message_received),
'enriched-chat-message-received' :
(ged.POSTCORE,
self.enriched_chat_message_received)}
self.events = [CustomizedMessageReceivedEvent,
MoreCustomizedMessageReceivedEvent,
ModifyOnlyMessageReceivedEvent,
EnrichedChatMessageReceivedEvent]
def enriched_chat_message_received(self, event_object):
pass
#print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name,
#event_object)
def raw_message_received(self, event_object):
pass
#print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name,
#event_object)
def customized_message_received(self, event_object):
pass
#print "Event '%s' occured. Event object: %s\n\n===\n"%(event_object.name,
#event_object
@log_calls('NewEventsExamplePlugin')
def activate(self):
pass
@log_calls('NewEventsExamplePlugin')
def deactivate(self):
pass
class CustomizedMessageReceivedEvent(nec.NetworkIncomingEvent):
name = 'customized-message-received'
base_network_events = ['raw-message-received']
def generate(self):
return True
class MoreCustomizedMessageReceivedEvent(nec.NetworkIncomingEvent):
'''
Shows chain of custom created events.
This one is based on custom 'customized-messsage-received'.
'''
name = 'more-customized-message-received'
base_network_events = ['customized-message-received']
def generate(self):
return True
class ModifyOnlyMessageReceivedEvent(nec.NetworkIncomingEvent):
name = 'modify-only-message-received'
base_network_events = ['raw-message-received']
def generate(self):
msg_type = self.base_event.xmpp_msg.attrs.get('type', None)
if msg_type == u'chat':
msg_text = "".join(self.base_event.xmpp_msg.kids[0].data)
self.base_event.xmpp_msg.kids[0].setData(
u'%s [MODIFIED BY CUSTOM NETWORK EVENT]'%(msg_text))
return False
class EnrichedChatMessageReceivedEvent(nec.NetworkIncomingEvent):
'''
Generates more friendly (in use by handlers) network event for
received chat message.
'''
name = 'enriched-chat-message-received'
base_network_events = ['raw-message-received']
def generate(self):
msg_type = self.base_event.xmpp_msg.attrs.get('type', None)
if msg_type == u'chat':
self.xmpp_msg = self.base_event.xmpp_msg
self.conn = self.base_event.conn
self.from_jid = helpers.get_full_jid_from_iq(self.xmpp_msg)
self.from_jid_without_resource = gajim.get_jid_without_resource(self.from_jid)
self.account = self.base_event.account
self.from_nickname = gajim.get_contact_name_from_jid(
self.account,
self.from_jid_without_resource)
self.msg_text = "".join(self.xmpp_msg.kids[0].data)
return True
return False

View File

@ -0,0 +1,4 @@
__all__ = ['RosterButtonsPlugin']
from plugin import RosterButtonsPlugin

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Roster buttons plug-in.
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 14th June 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import sys
import gtk
from common import i18n
from common import gajim
from plugins import GajimPlugin
from plugins.helpers import log, log_calls
class RosterButtonsPlugin(GajimPlugin):
name = u'Roster Buttons'
short_name = u'roster_buttons'
version = u'0.1'
description = u'''Adds quick action buttons to roster window.'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
@log_calls('RosterButtonsPlugin')
def init(self):
self.GTK_BUILDER_FILE_PATH = self.local_file_path('roster_buttons.ui')
self.roster_vbox = gajim.interface.roster.xml.get_object('roster_vbox2')
self.show_offline_contacts_menuitem = gajim.interface.roster.xml.get_object('show_offline_contacts_menuitem')
self.config_dialog = None
@log_calls('RosterButtonsPlugin')
def activate(self):
self.xml = gtk.Builder()
self.xml.set_translation_domain(i18n.APP)
self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH,
['roster_buttons_buttonbox'])
self.buttonbox = self.xml.get_object('roster_buttons_buttonbox')
self.roster_vbox.pack_start(self.buttonbox, expand=False)
self.roster_vbox.reorder_child(self.buttonbox, 0)
self.xml.connect_signals(self)
@log_calls('RosterButtonsPlugin')
def deactivate(self):
self.roster_vbox.remove(self.buttonbox)
self.buttonbox = None
self.xml = None
@log_calls('RosterButtonsPlugin')
def on_roster_button_1_clicked(self, button):
#gajim.interface.roster.on_show_offline_contacts_menuitem_activate(None)
self.show_offline_contacts_menuitem.set_active(not self.show_offline_contacts_menuitem.get_active())
@log_calls('RosterButtonsPlugin')
def on_roster_button_2_clicked(self, button):
pass
@log_calls('RosterButtonsPlugin')
def on_roster_button_3_clicked(self, button):
pass
@log_calls('RosterButtonsPlugin')
def on_roster_button_4_clicked(self, button):
pass

View File

@ -0,0 +1,70 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="window1">
<child>
<object class="GtkHButtonBox" id="roster_buttons_buttonbox">
<property name="visible">True</property>
<property name="homogeneous">True</property>
<property name="layout_style">spread</property>
<child>
<object class="GtkButton" id="roster_button_1">
<property name="label" translatable="yes">1</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_roster_button_1_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="roster_button_2">
<property name="label" translatable="yes">2</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_roster_button_2_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="roster_button_3">
<property name="label" translatable="yes">3</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_roster_button_3_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="roster_button_4">
<property name="label" translatable="yes">4</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_roster_button_4_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -0,0 +1,772 @@
"""
A python version of the main functions to use Snarl
(http://www.fullphat.net/snarl)
Version 1.0
This module can be used in two ways. One is the normal way
the other snarl interfaces work. This means you can call snShowMessage
and get an ID back for manipulations.
The other way is there is a class this module exposes called SnarlMessage.
This allows you to keep track of the message as a python object. If you
use the send without specifying False as the argument it will set the ID
to what the return of the last SendMessage was. This is of course only
useful for the SHOW message.
Requires one of:
pywin32 extensions from http://pywin32.sourceforge.net
ctypes (included in Python 2.5, downloadable for earlier versions)
Creator: Sam Listopad II (samlii@users.sourceforge.net)
Copyright 2006-2008 Samuel Listopad II
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
"""
import array, struct
def LOWORD(dword):
"""Return the low WORD of the passed in integer"""
return dword & 0x0000ffff
#get the hi word
def HIWORD(dword):
"""Return the high WORD of the passed in integer"""
return dword >> 16
class Win32FuncException(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
class Win32Funcs:
"""Just a little class to hide the details of finding and using the
correct win32 functions. The functions may throw a UnicodeEncodeError if
there is not a unicode version and it is sent a unicode string that cannot
be converted to ASCII."""
WM_USER = 0x400
WM_COPYDATA = 0x4a
#Type of String the functions are expecting.
#Used like function(myWin32Funcs.strType(param)).
__strType = str
#FindWindow function to use
__FindWindow = None
#FindWindow function to use
__FindWindowEx = None
#SendMessage function to use
__SendMessage = None
#SendMessageTimeout function to use
__SendMessageTimeout = None
#IsWindow function to use
__IsWindow = None
#RegisterWindowMessage to use
__RegisterWindowMessage = None
#GetWindowText to use
__GetWindowText = None
def FindWindow(self, lpClassName, lpWindowName):
"""Wraps the windows API call of FindWindow"""
if lpClassName is not None:
lpClassName = self.__strType(lpClassName)
if lpWindowName is not None:
lpWindowName = self.__strType(lpWindowName)
return self.__FindWindow(lpClassName, lpWindowName)
def FindWindowEx(self, hwndParent, hwndChildAfter, lpClassName, lpWindowName):
"""Wraps the windows API call of FindWindow"""
if lpClassName is not None:
lpClassName = self.__strType(lpClassName)
if lpWindowName is not None:
lpWindowName = self.__strType(lpWindowName)
return self.__FindWindowEx(hwndParent, hwndChildAfter, lpClassName, lpWindowName)
def SendMessage(self, hWnd, Msg, wParam, lParam):
"""Wraps the windows API call of SendMessage"""
return self.__SendMessage(hWnd, Msg, wParam, lParam)
def SendMessageTimeout(self, hWnd, Msg,
wParam, lParam, fuFlags,
uTimeout, lpdwResult = None):
"""Wraps the windows API call of SendMessageTimeout"""
idToRet = None
try:
idFromMsg = array.array('I', [0])
result = idFromMsg.buffer_info()[0]
response = self.__SendMessageTimeout(hWnd, Msg, wParam,
lParam, fuFlags,
uTimeout, result)
if response == 0:
raise Win32FuncException, "SendMessageTimeout TimedOut"
idToRet = idFromMsg[0]
except TypeError:
idToRet = self.__SendMessageTimeout(hWnd, Msg, wParam,
lParam, fuFlags,
uTimeout)
if lpdwResult is not None and lpdwResult.typecode == 'I':
lpdwResult[0] = idToRet
return idToRet
def IsWindow(self, hWnd):
"""Wraps the windows API call of IsWindow"""
return self.__IsWindow(hWnd)
def RegisterWindowMessage(self, lpString):
"""Wraps the windows API call of RegisterWindowMessage"""
return self.__RegisterWindowMessage(self.__strType(lpString))
def GetWindowText(self, hWnd, lpString = None, nMaxCount = None):
"""Wraps the windows API call of SendMessageTimeout"""
text = ''
if hWnd == 0:
return text
if nMaxCount is None:
nMaxCount = 1025
try:
arrayType = 'c'
if self.__strType == unicode:
arrayType = 'u'
path_string = array.array(arrayType, self.__strType('\x00') * nMaxCount)
path_buffer = path_string.buffer_info()[0]
result = self.__GetWindowText(hWnd,
path_buffer,
nMaxCount)
if result > 0:
if self.__strType == unicode:
text = path_string[0:result].tounicode()
else:
text = path_string[0:result].tostring()
except TypeError:
text = self.__GetWindowText(hWnd)
if lpString is not None and lpString.typecode == 'c':
lpdwResult[0:len(text)] = array.array('c', str(text));
if lpString is not None and lpString.typecode == 'u':
lpdwResult[0:len(text)] = array.array('u', unicode(text));
return text
def __init__(self):
"""Load up my needed functions"""
# First see if they already have win32gui imported. If so use it.
# This has to be checked first since the auto check looks for ctypes
# first.
try:
self.__FindWindow = win32gui.FindWindow
self.__FindWindowEx = win32gui.FindWindowEx
self.__GetWindowText = win32gui.GetWindowText
self.__IsWindow = win32gui.IsWindow
self.__SendMessage = win32gui.SendMessage
self.__SendMessageTimeout = win32gui.SendMessageTimeout
self.__RegisterWindowMessage = win32gui.RegisterWindowMessage
self.__strType = unicode
#Something threw a NameError, most likely the win32gui lines
#so do auto check
except NameError:
try:
from ctypes import windll
self.__FindWindow = windll.user32.FindWindowW
self.__FindWindowEx = windll.user32.FindWindowExW
self.__GetWindowText = windll.user32.GetWindowTextW
self.__IsWindow = windll.user32.IsWindow
self.__SendMessage = windll.user32.SendMessageW
self.__SendMessageTimeout = windll.user32.SendMessageTimeoutW
self.__RegisterWindowMessage = windll.user32.RegisterWindowMessageW
self.__strType = unicode
#FindWindowW wasn't found, look for FindWindowA
except AttributeError:
try:
self.__FindWindow = windll.user32.FindWindowA
self.__FindWindowEx = windll.user32.FindWindowExA
self.__GetWindowText = windll.user32.GetWindowTextA
self.__IsWindow = windll.user32.IsWindow
self.__SendMessage = windll.user32.SendMessageA
self.__SendMessageTimeout = windll.user32.SendMessageTimeoutA
self.__RegisterWindowMessage = windll.user32.RegisterWindowMessageA
# Couldn't find either so Die and tell user why.
except AttributeError:
import sys
sys.stderr.write("Your Windows TM setup seems to be corrupt."+
" No FindWindow found in user32.\n")
sys.stderr.flush()
sys.exit(3)
except ImportError:
try:
import win32gui
self.__FindWindow = win32gui.FindWindow
self.__FindWindowEx = win32gui.FindWindowEx
self.__GetWindowText = win32gui.GetWindowText
self.__IsWindow = win32gui.IsWindow
self.__SendMessage = win32gui.SendMessage
self.__SendMessageTimeout = win32gui.SendMessageTimeout
self.__RegisterWindowMessage = win32gui.RegisterWindowMessage
self.__strType = unicode
except ImportError:
import sys
sys.stderr.write("You need to have either"+
" ctypes or pywin32 installed.\n")
sys.stderr.flush()
#sys.exit(2)
myWin32Funcs = Win32Funcs()
SHOW = 1
HIDE = 2
UPDATE = 3
IS_VISIBLE = 4
GET_VERSION = 5
REGISTER_CONFIG_WINDOW = 6
REVOKE_CONFIG_WINDOW = 7
REGISTER_ALERT = 8
REVOKE_ALERT = 9
REGISTER_CONFIG_WINDOW_2 = 10
GET_VERSION_EX = 11
SET_TIMEOUT = 12
EX_SHOW = 32
GLOBAL_MESSAGE = "SnarlGlobalMessage"
GLOBAL_MSG = "SnarlGlobalEvent"
#Messages That may be received from Snarl
SNARL_LAUNCHED = 1
SNARL_QUIT = 2
SNARL_ASK_APPLET_VER = 3
SNARL_SHOW_APP_UI = 4
SNARL_NOTIFICATION_CLICKED = 32 #notification was right-clicked by user
SNARL_NOTIFICATION_CANCELLED = SNARL_NOTIFICATION_CLICKED #Name clarified
SNARL_NOTIFICATION_TIMED_OUT = 33
SNARL_NOTIFICATION_ACK = 34 #notification was left-clicked by user
#Snarl Test Message
WM_SNARLTEST = myWin32Funcs.WM_USER + 237
M_ABORTED = 0x80000007L
M_ACCESS_DENIED = 0x80000009L
M_ALREADY_EXISTS = 0x8000000CL
M_BAD_HANDLE = 0x80000006L
M_BAD_POINTER = 0x80000005L
M_FAILED = 0x80000008L
M_INVALID_ARGS = 0x80000003L
M_NO_INTERFACE = 0x80000004L
M_NOT_FOUND = 0x8000000BL
M_NOT_IMPLEMENTED = 0x80000001L
M_OK = 0x00000000L
M_OUT_OF_MEMORY = 0x80000002L
M_TIMED_OUT = 0x8000000AL
ErrorCodeRev = {
0x80000007L : "M_ABORTED",
0x80000009L : "M_ACCESS_DENIED",
0x8000000CL : "M_ALREADY_EXISTS",
0x80000006L : "M_BAD_HANDLE",
0x80000005L : "M_BAD_POINTER",
0x80000008L : "M_FAILED",
0x80000003L : "M_INVALID_ARGS",
0x80000004L : "M_NO_INTERFACE",
0x8000000BL : "M_NOT_FOUND",
0x80000001L : "M_NOT_IMPLEMENTED",
0x00000000L : "M_OK",
0x80000002L : "M_OUT_OF_MEMORY",
0x8000000AL : "M_TIMED_OUT"
}
class SnarlMessage(object):
"""The main Snarl interface object.
ID = Snarl Message ID for most operations. See SDK for more info
as to other values to put here.
type = Snarl Message Type. Valid values are : SHOW, HIDE, UPDATE,
IS_VISIBLE, GET_VERSION, REGISTER_CONFIG_WINDOW, REVOKE_CONFIG_WINDOW
all which are constants in the PySnarl module.
timeout = Timeout in seconds for the Snarl Message
data = Snarl Message data. This is dependant upon message type. See SDK
title = Snarl Message title.
text = Snarl Message text.
icon = Path to the icon to display in the Snarl Message.
"""
__msgType = 0
__msgID = 0
__msgTimeout = 0
__msgData = 0
__msgTitle = ""
__msgText = ""
__msgIcon = ""
__msgClass = ""
__msgExtra = ""
__msgExtra2 = ""
__msgRsvd1 = 0
__msgRsvd2 = 0
__msgHWnd = 0
lastKnownHWnd = 0
def getType(self):
"""Type Attribute getter."""
return self.__msgType
def setType(self, value):
"""Type Attribute setter."""
if( isinstance(value, (int, long)) ):
self.__msgType = value
type = property(getType, setType, doc="The Snarl Message Type")
def getID(self):
"""ID Attribute getter."""
return self.__msgID
def setID(self, value):
"""ID Attribute setter."""
if( isinstance(value, (int, long)) ):
self.__msgID = value
ID = property(getID, setID, doc="The Snarl Message ID")
def getTimeout(self):
"""Timeout Attribute getter."""
return self.__msgTimeout
def updateTimeout(self, value):
"""Timeout Attribute setter."""
if( isinstance(value, (int, long)) ):
self.__msgTimeout = value
timeout = property(getTimeout, updateTimeout,
doc="The Snarl Message Timeout")
def getData(self):
"""Data Attribute getter."""
return self.__msgData
def setData(self, value):
"""Data Attribute setter."""
if( isinstance(value, (int, long)) ):
self.__msgData = value
data = property(getData, setData, doc="The Snarl Message Data")
def getTitle(self):
"""Title Attribute getter."""
return self.__msgTitle
def setTitle(self, value):
"""Title Attribute setter."""
if( isinstance(value, basestring) ):
self.__msgTitle = value
title = property(getTitle, setTitle, doc="The Snarl Message Title")
def getText(self):
"""Text Attribute getter."""
return self.__msgText
def setText(self, value):
"""Text Attribute setter."""
if( isinstance(value, basestring) ):
self.__msgText = value
text = property(getText, setText, doc="The Snarl Message Text")
def getIcon(self):
"""Icon Attribute getter."""
return self.__msgIcon
def setIcon(self, value):
"""Icon Attribute setter."""
if( isinstance(value, basestring) ):
self.__msgIcon = value
icon = property(getIcon, setIcon, doc="The Snarl Message Icon")
def getClass(self):
"""Class Attribute getter."""
return self.__msgClass
def setClass(self, value):
"""Class Attribute setter."""
if( isinstance(value, basestring) ):
self.__msgClass = value
msgclass = property(getClass, setClass, doc="The Snarl Message Class")
def getExtra(self):
"""Extra Attribute getter."""
return self.__msgExtra
def setExtra(self, value):
"""Extra Attribute setter."""
if( isinstance(value, basestring) ):
self.__msgExtra = value
extra = property(getExtra, setExtra, doc="Extra Info for the Snarl Message")
def getExtra2(self):
"""Extra2 Attribute getter."""
return self.__msgExtra2
def setExtra2(self, value):
"""Extra2 Attribute setter."""
if( isinstance(value, basestring) ):
self.__msgExtra2 = value
extra2 = property(getExtra2, setExtra2,
doc="More Extra Info for the Snarl Message")
def getRsvd1(self):
"""Rsvd1 Attribute getter."""
return self.__msgRsvd1
def setRsvd1(self, value):
"""Rsvd1 Attribute setter."""
if( isinstance(value, (int, long)) ):
self.__msgRsvd1 = value
rsvd1 = property(getRsvd1, setRsvd1, doc="The Snarl Message Field Rsvd1")
def getRsvd2(self):
"""Rsvd2 Attribute getter."""
return self.__msgRsvd2
def setRsvd2(self, value):
"""Rsvd2 Attribute setter."""
if( isinstance(value, (int, long)) ):
self.__msgRsvd2 = value
rsvd2 = property(getRsvd2, setRsvd2, doc="The Snarl Message Field Rsvd2")
def getHwnd(self):
"""hWnd Attribute getter."""
return self.__msgHWnd
def setHwnd(self, value):
"""hWnd Attribute setter."""
if( isinstance(value, (int, long)) ):
self.__msgHWnd = value
hWnd = property(getHwnd, setHwnd, doc="The hWnd of the window this message is being sent from")
def __init__(self, title="", text="", icon="", msg_type=1, msg_id=0):
self.__msgTimeout = 0
self.__msgData = 0
self.__msgClass = ""
self.__msgExtra = ""
self.__msgExtra2 = ""
self.__msgRsvd1 = 0
self.__msgRsvd2 = 0
self.__msgType = msg_type
self.__msgText = text
self.__msgTitle = title
self.__msgIcon = icon
self.__msgID = msg_id
def createCopyStruct(self):
"""Creates the struct to send as the copyData in the message."""
return struct.pack("ILLL1024s1024s1024s1024s1024s1024sLL",
self.__msgType,
self.__msgID,
self.__msgTimeout,
self.__msgData,
self.__msgTitle.encode('utf-8'),
self.__msgText.encode('utf-8'),
self.__msgIcon.encode('utf-8'),
self.__msgClass.encode('utf-8'),
self.__msgExtra.encode('utf-8'),
self.__msgExtra2.encode('utf-8'),
self.__msgRsvd1,
self.__msgRsvd2
)
__lpData = None
__cds = None
def packData(self, dwData):
"""This packs the data in the necessary format for a
WM_COPYDATA message."""
self.__lpData = None
self.__cds = None
item = self.createCopyStruct()
self.__lpData = array.array('c', item)
lpData_ad = self.__lpData.buffer_info()[0]
cbData = self.__lpData.buffer_info()[1]
self.__cds = array.array('c',
struct.pack("IIP",
dwData,
cbData,
lpData_ad)
)
cds_ad = self.__cds.buffer_info()[0]
return cds_ad
def reset(self):
"""Reset this SnarlMessage to the default state."""
self.__msgType = 0
self.__msgID = 0
self.__msgTimeout = 0
self.__msgData = 0
self.__msgTitle = ""
self.__msgText = ""
self.__msgIcon = ""
self.__msgClass = ""
self.__msgExtra = ""
self.__msgExtra2 = ""
self.__msgRsvd1 = 0
self.__msgRsvd2 = 0
def send(self, setid=True):
"""Send this SnarlMessage to the Snarl window.
Args:
setid - Boolean defining whether or not to set the ID
of this SnarlMessage to the return value of
the SendMessage call. Default is True to
make simple case of SHOW easy.
"""
hwnd = myWin32Funcs.FindWindow(None, "Snarl")
if myWin32Funcs.IsWindow(hwnd):
if self.type == REGISTER_CONFIG_WINDOW or self.type == REGISTER_CONFIG_WINDOW_2:
self.hWnd = self.data
try:
response = myWin32Funcs.SendMessageTimeout(hwnd,
myWin32Funcs.WM_COPYDATA,
self.hWnd, self.packData(2),
2, 500)
except Win32FuncException:
return False
idFromMsg = response
if setid:
self.ID = idFromMsg
return True
else:
return idFromMsg
print "No snarl window found"
return False
def hide(self):
"""Hide this message. Type will revert to type before calling hide
to allow for better reuse of object."""
oldType = self.__msgType
self.__msgType = HIDE
retVal = bool(self.send(False))
self.__msgType = oldType
return retVal
def isVisible(self):
"""Is this message visible. Type will revert to type before calling
hide to allow for better reuse of object."""
oldType = self.__msgType
self.__msgType = IS_VISIBLE
retVal = bool(self.send(False))
self.__msgType = oldType
return retVal
def update(self, title=None, text=None, icon=None):
"""Update this message with given title and text. Type will revert
to type before calling hide to allow for better reuse of object."""
oldType = self.__msgType
self.__msgType = UPDATE
if text:
self.__msgText = text
if title:
self.__msgTitle = title
if icon:
self.__msgIcon = icon
retVal = self.send(False)
self.__msgType = oldType
return retVal
def setTimeout(self, timeout):
"""Set the timeout in seconds of the message"""
oldType = self.__msgType
oldData = self.__msgData
self.__msgType = SET_TIMEOUT
#self.timeout = timeout
#self.__msgData = self.__msgTimeout
self.__msgData = timeout
retVal = self.send(False)
self.__msgType = oldType
self.__msgData = oldData
return retVal
def show(self, timeout=None, title=None,
text=None, icon=None,
replyWindow=None, replyMsg=None, msgclass=None, soundPath=None):
"""Show a message"""
oldType = self.__msgType
oldTimeout = self.__msgTimeout
self.__msgType = SHOW
if text:
self.__msgText = text
if title:
self.__msgTitle = title
if timeout:
self.__msgTimeout = timeout
if icon:
self.__msgIcon = icon
if replyWindow:
self.__msgID = replyMsg
if replyMsg:
self.__msgData = replyWindow
if soundPath:
self.__msgExtra = soundPath
if msgclass:
self.__msgClass = msgclass
if ((self.__msgClass and self.__msgClass != "") or
(self.__msgExtra and self.__msgExtra != "")):
self.__msgType = EX_SHOW
retVal = bool(self.send())
self.__msgType = oldType
self.__msgTimeout = oldTimeout
return retVal
def snGetVersion():
""" Get the version of Snarl that is running as a tuple. (Major, Minor)
If Snarl is not running or there was an error it will
return False."""
msg = SnarlMessage(msg_type=GET_VERSION)
version = msg.send(False)
if not version:
return False
return (HIWORD(version), LOWORD(version))
def snGetVersionEx():
""" Get the internal version of Snarl that is running.
If Snarl is not running or there was an error it will
return False."""
sm = SnarlMessage(msg_type=GET_VERSION_EX)
verNum = sm.send(False)
if not verNum:
return False
return verNum
def snGetGlobalMessage():
"""Get the Snarl global message id from windows."""
return myWin32Funcs.RegisterWindowMessage(GLOBAL_MSG)
def snShowMessage(title, text, timeout=0, iconPath="",
replyWindow=0, replyMsg=0):
"""Show a message using Snarl and return its ID. See SDK for arguments."""
sm = SnarlMessage( title, text, iconPath, msg_id=replyMsg)
sm.data = replyWindow
if sm.show(timeout):
return sm.ID
else:
return False
def snShowMessageEx(msgClass, title, text, timeout=0, iconPath="",
replyWindow=0, replyMsg=0, soundFile=None, hWndFrom=None):
"""Show a message using Snarl and return its ID. See SDK for arguments.
One added argument is hWndFrom that allows one to make the messages appear
to come from a specific window. This window should be the one you registered
earlier with RegisterConfig"""
sm = SnarlMessage( title, text, iconPath, msg_id=replyMsg)
sm.data = replyWindow
if hWndFrom is not None:
sm.hWnd = hWndFrom
else:
sm.hWnd = SnarlMessage.lastKnownHWnd
if sm.show(timeout, msgclass=msgClass, soundPath=soundFile):
return sm.ID
else:
return False
def snUpdateMessage(msgId, msgTitle, msgText, icon=None):
"""Update a message"""
sm = SnarlMessage(msg_id=msgId)
if icon:
sm.icon = icon
return sm.update(msgTitle, msgText)
def snHideMessage(msgId):
"""Hide a message"""
return SnarlMessage(msg_id=msgId).hide()
def snSetTimeout(msgId, timeout):
"""Update the timeout of a message already shown."""
sm = SnarlMessage(msg_id=msgId)
return sm.setTimeout(timeout)
def snIsMessageVisible(msgId):
"""Returns True if the message is visible False otherwise."""
return SnarlMessage(msg_id=msgId).isVisible()
def snRegisterConfig(replyWnd, appName, replyMsg):
"""Register a config window. See SDK for more info."""
global lastRegisteredSnarlMsg
sm = SnarlMessage(msg_type=REGISTER_CONFIG_WINDOW,
title=appName,
msg_id=replyMsg)
sm.data = replyWnd
SnarlMessage.lastKnownHWnd = replyWnd
return sm.send(False)
def snRegisterConfig2(replyWnd, appName, replyMsg, icon):
"""Register a config window. See SDK for more info."""
global lastRegisteredSnarlMsg
sm = SnarlMessage(msg_type=REGISTER_CONFIG_WINDOW_2,
title=appName,
msg_id=replyMsg,
icon=icon)
sm.data = replyWnd
SnarlMessage.lastKnownHWnd = replyWnd
return sm.send(False)
def snRegisterAlert(appName, classStr) :
"""Register an alert for an already registered config. See SDK for more info."""
sm = SnarlMessage(msg_type=REGISTER_ALERT,
title=appName,
text=classStr)
return sm.send(False)
def snRevokeConfig(replyWnd):
"""Revoke a config window"""
sm = SnarlMessage(msg_type=REVOKE_CONFIG_WINDOW)
sm.data = replyWnd
if replyWnd == SnarlMessage.lastKnownHWnd:
SnarlMessage.lastKnownHWnd = 0
return sm.send(False)
def snGetSnarlWindow():
"""Returns the hWnd of the snarl window"""
return myWin32Funcs.FindWindow(None, "Snarl")
def snGetAppPath():
"""Returns the application path of the currently running snarl window"""
app_path = None
snarl_handle = snGetSnarlWindow()
if snarl_handle != 0:
pathwin_handle = myWin32Funcs.FindWindowEx(snarl_handle,
0,
"static",
None)
if pathwin_handle != 0:
try:
result = myWin32Funcs.GetWindowText(pathwin_handle)
app_path = result
except Win32FuncException:
pass
return app_path
def snGetIconsPath():
"""Returns the path to the icons of the program"""
s = snGetAppPath()
if s is None:
return ""
else:
return s + "etc\\icons\\"
def snSendTestMessage(data=None):
"""Sends a test message to Snarl. Used to make sure the
api is connecting"""
param = 0
command = 0
if data:
param = struct.pack("I", data)
command = 1
myWin32Funcs.SendMessage(snGetSnarlWindow(), WM_SNARLTEST, command, param)

View File

@ -0,0 +1 @@
from plugin import SnarlNotificationsPlugin

View File

@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
##
## This file is part of Gajim.
##
## Gajim 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 3 only.
##
## Gajim is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
##
'''
Events notifications using Snarl
Fancy events notifications under Windows using Snarl infrastructure.
:note: plugin is at proof-of-concept state.
:author: Mateusz Biliński <mateusz@bilinski.it>
:since: 15th August 2008
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
:license: GPL
'''
import new
from pprint import pformat
#import PySnarl
from common import gajim
from plugins import GajimPlugin
from plugins.helpers import log_calls, log
from common import ged
class SnarlNotificationsPlugin(GajimPlugin):
name = u'Snarl Notifications'
short_name = u'snarl_notifications'
version = u'0.1'
description = u'''Shows events notification using Snarl (http://www.fullphat.net/) under Windows. Snarl needs to be installed in system.
PySnarl bindings are used (http://code.google.com/p/pysnarl/).'''
authors = [u'Mateusz Biliński <mateusz@bilinski.it>']
homepage = u'http://blog.bilinski.it'
@log_calls('SnarlNotificationsPlugin')
def init(self):
self.config_dialog = None
#self.gui_extension_points = {}
#self.config_default_values = {}
self.events_handlers = {'NewMessage' : (ged.POSTCORE, self.newMessage)}
@log_calls('SnarlNotificationsPlugin')
def activate(self):
pass
@log_calls('SnarlNotificationsPlugin')
def deactivate(self):
pass
@log_calls('SnarlNotificationsPlugin')
def newMessage(self, args):
event_name = "NewMessage"
data = args
account = data[0]
jid = data[1][0]
jid_without_resource = gajim.get_jid_without_resource(jid)
msg = data[1][1]
msg_type = data[1][4]
if msg_type == 'chat':
nickname = gajim.get_contact_name_from_jid(account,
jid_without_resource)
elif msg_type == 'pm':
nickname = gajim.get_resource_from_jid(jid)
print "Event '%s' occured. Arguments: %s\n\n===\n"%(event_name, pformat(args))
print "Event '%s' occured. Arguments: \naccount = %s\njid = %s\nmsg = %s\nnickname = %s"%(
event_name, account, jid, msg, nickname)
#if PySnarl.snGetVersion() != False:
#(major, minor) = PySnarl.snGetVersion()
#print "Found Snarl version",str(major)+"."+str(minor),"running."
#PySnarl.snShowMessage(nickname, msg[:20]+'...')
#else:
#print "Sorry Snarl does not appear to be running"

2619
po/be.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2601
po/bg.po

File diff suppressed because it is too large Load Diff

2624
po/br.po

File diff suppressed because it is too large Load Diff

2601
po/cs.po

File diff suppressed because it is too large Load Diff

2593
po/da.po

File diff suppressed because it is too large Load Diff

2611
po/de.po

File diff suppressed because it is too large Load Diff

2616
po/el.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2615
po/eo.po

File diff suppressed because it is too large Load Diff

2616
po/es.po

File diff suppressed because it is too large Load Diff

2671
po/eu.po

File diff suppressed because it is too large Load Diff

4553
po/fr.po

File diff suppressed because it is too large Load Diff

2619
po/gl.po

File diff suppressed because it is too large Load Diff

2604
po/hr.po

File diff suppressed because it is too large Load Diff

2603
po/it.po

File diff suppressed because it is too large Load Diff

2612
po/ja.po

File diff suppressed because it is too large Load Diff

5428
po/kk.po

File diff suppressed because it is too large Load Diff

2700
po/lt.po

File diff suppressed because it is too large Load Diff

2596
po/nb.po

File diff suppressed because it is too large Load Diff

2637
po/nl.po

File diff suppressed because it is too large Load Diff

2596
po/no.po

File diff suppressed because it is too large Load Diff

2618
po/pl.po

File diff suppressed because it is too large Load Diff

2635
po/pt.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2241
po/ru.po

File diff suppressed because it is too large Load Diff

2608
po/sk.po

File diff suppressed because it is too large Load Diff

2589
po/sr.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

2594
po/sv.po

File diff suppressed because it is too large Load Diff

2600
po/uk.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,8 @@ nobase_dist_gajimsrc_PYTHON = \
$(srcdir)/common/xmpp/*.py \ $(srcdir)/common/xmpp/*.py \
$(srcdir)/common/zeroconf/*.py \ $(srcdir)/common/zeroconf/*.py \
$(srcdir)/command_system/*.py \ $(srcdir)/command_system/*.py \
$(srcdir)/command_system/implementation/*.py $(srcdir)/command_system/implementation/*.py \
$(srcdir)/plugins/*.py
dist-hook: dist-hook:
rm -f $(distdir)/ipython_view.py rm -f $(distdir)/ipython_view.py

View File

@ -64,13 +64,12 @@ class CommandWindow:
self.window.connect('delete-event', self.window.connect('delete-event',
self.on_adhoc_commands_window_delete_event) self.on_adhoc_commands_window_delete_event)
for name in ('restart_button', 'back_button', 'forward_button', for name in ('restart_button', 'back_button', 'forward_button',
'execute_button', 'close_button', 'stages_notebook', 'execute_button', 'finish_button', 'close_button', 'stages_notebook',
'retrieving_commands_stage_vbox', 'retrieving_commands_stage_vbox', 'command_list_stage_vbox',
'command_list_stage_vbox', 'command_list_vbox', 'command_list_vbox', 'sending_form_stage_vbox',
'sending_form_stage_vbox', 'sending_form_progressbar', 'sending_form_progressbar', 'notes_label', 'no_commands_stage_vbox',
'notes_label', 'no_commands_stage_vbox', 'error_stage_vbox', 'error_stage_vbox', 'error_description_label'):
'error_description_label'): setattr(self, name, self.xml.get_object(name))
self.__dict__[name] = self.xml.get_object(name)
self.initiate() self.initiate()
@ -100,6 +99,7 @@ class CommandWindow:
self.stage1() self.stage1()
# displaying the window # displaying the window
self.window.set_title('Ad-hoc Commands - Gajim')
self.xml.connect_signals(self) self.xml.connect_signals(self)
self.window.show_all() self.window.show_all()
@ -121,6 +121,9 @@ class CommandWindow:
def stage_close_button_clicked(self, *anything): def stage_close_button_clicked(self, *anything):
assert False assert False
def stage_restart_button_clicked(self, *anything):
assert False
def stage_adhoc_commands_window_delete_event(self, *anything): def stage_adhoc_commands_window_delete_event(self, *anything):
assert False assert False
@ -137,9 +140,15 @@ class CommandWindow:
def on_execute_button_clicked(self, *anything): def on_execute_button_clicked(self, *anything):
return self.stage_execute_button_clicked(*anything) return self.stage_execute_button_clicked(*anything)
def on_finish_button_clicked(self, *anything):
return self.stage_finish_button_clicked(*anything)
def on_close_button_clicked(self, *anything): def on_close_button_clicked(self, *anything):
return self.stage_close_button_clicked(*anything) return self.stage_close_button_clicked(*anything)
def on_restart_button_clicked(self, *anything):
return self.stage_restart_button_clicked(*anything)
def on_adhoc_commands_window_destroy(self, *anything): def on_adhoc_commands_window_destroy(self, *anything):
# TODO: do all actions that are needed to remove this object from memory # TODO: do all actions that are needed to remove this object from memory
self.remove_pulsing() self.remove_pulsing()
@ -169,15 +178,17 @@ class CommandWindow:
self.back_button.set_sensitive(False) self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False) self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(False) self.execute_button.set_sensitive(False)
self.finish_button.set_sensitive(False)
# request command list # request command list
self.request_command_list() self.request_command_list()
self.setup_pulsing( self.setup_pulsing(
self.xml.get_object('retrieving_commands_progressbar')) self.xml.get_object('retrieving_commands_progressbar'))
# setup the callbacks # setup the callbacks
self.stage_finish = self.stage1_finish self.stage_finish = self.stage1_finish
self.stage_close_button_clicked = self.stage1_close_button_clicked self.stage_close_button_clicked = self.stage1_close_button_clicked
self.stage_restart_button_clicked = self.stage1_restart_button_clicked
self.stage_adhoc_commands_window_delete_event = \ self.stage_adhoc_commands_window_delete_event = \
self.stage1_adhoc_commands_window_delete_event self.stage1_adhoc_commands_window_delete_event
@ -187,9 +198,13 @@ class CommandWindow:
def stage1_close_button_clicked(self, widget): def stage1_close_button_clicked(self, widget):
# cancelling in this stage is not critical, so we don't # cancelling in this stage is not critical, so we don't
# show any popups to user # show any popups to user
self.stage1_finish() self.stage_finish()
self.window.destroy() self.window.destroy()
def stage1_restart_button_clicked(self, widget):
self.stage_finish()
self.restart()
def stage1_adhoc_commands_window_delete_event(self, widget): def stage1_adhoc_commands_window_delete_event(self, widget):
self.stage1_finish() self.stage1_finish()
return True return True
@ -214,6 +229,7 @@ class CommandWindow:
self.back_button.set_sensitive(False) self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(True) self.forward_button.set_sensitive(True)
self.execute_button.set_sensitive(False) self.execute_button.set_sensitive(False)
self.finish_button.set_sensitive(False)
# build the commands list radiobuttons # build the commands list radiobuttons
first_radio = None first_radio = None
@ -229,6 +245,7 @@ class CommandWindow:
self.stage_finish = self.stage2_finish self.stage_finish = self.stage2_finish
self.stage_close_button_clicked = self.stage2_close_button_clicked self.stage_close_button_clicked = self.stage2_close_button_clicked
self.stage_restart_button_clicked = self.stage2_restart_button_clicked
self.stage_forward_button_clicked = self.stage2_forward_button_clicked self.stage_forward_button_clicked = self.stage2_forward_button_clicked
self.stage_adhoc_commands_window_delete_event = self.do_nothing self.stage_adhoc_commands_window_delete_event = self.do_nothing
@ -244,6 +261,10 @@ class CommandWindow:
self.stage_finish() self.stage_finish()
self.window.destroy() self.window.destroy()
def stage2_restart_button_clicked(self, widget):
self.stage_finish()
self.restart()
def stage2_forward_button_clicked(self, widget): def stage2_forward_button_clicked(self, widget):
self.stage3() self.stage3()
@ -266,10 +287,12 @@ class CommandWindow:
self.stages_notebook.page_num( self.stages_notebook.page_num(
self.sending_form_stage_vbox)) self.sending_form_stage_vbox))
self.restart_button.set_sensitive(True)
self.close_button.set_sensitive(True) self.close_button.set_sensitive(True)
self.back_button.set_sensitive(False) self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False) self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(False) self.execute_button.set_sensitive(False)
self.finish_button.set_sensitive(False)
self.stage3_submit_form() self.stage3_submit_form()
@ -277,13 +300,31 @@ class CommandWindow:
self.stage_back_button_clicked = self.stage3_back_button_clicked self.stage_back_button_clicked = self.stage3_back_button_clicked
self.stage_forward_button_clicked = self.stage3_forward_button_clicked self.stage_forward_button_clicked = self.stage3_forward_button_clicked
self.stage_execute_button_clicked = self.stage3_execute_button_clicked self.stage_execute_button_clicked = self.stage3_execute_button_clicked
self.stage_finish_button_clicked = self.stage3_finish_button_clicked
self.stage_close_button_clicked = self.stage3_close_button_clicked self.stage_close_button_clicked = self.stage3_close_button_clicked
self.stage_restart_button_clicked = self.stage3_restart_button_clicked
self.stage_adhoc_commands_window_delete_event = \ self.stage_adhoc_commands_window_delete_event = \
self.stage3_close_button_clicked self.stage3_close_button_clicked
def stage3_finish(self): def stage3_finish(self):
pass pass
def stage3_can_close(self, cb):
if self.form_status == 'completed':
cb()
return
def on_yes(button):
self.send_cancel()
dialog.destroy()
cb()
dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT \
| gtk.DIALOG_MODAL, gtk.BUTTONS_YES_NO, _('Cancel confirmation'),
_('You are in process of executing command. Do you really want to '
'cancel it?'), on_response_yes=on_yes)
dialog.popup()
def stage3_close_button_clicked(self, widget): def stage3_close_button_clicked(self, widget):
""" """
We are in the middle of executing command. Ask user if he really want to We are in the middle of executing command. Ask user if he really want to
@ -291,26 +332,23 @@ class CommandWindow:
""" """
# this works also as a handler for window_delete_event, so we have to # this works also as a handler for window_delete_event, so we have to
# return appropriate values # return appropriate values
if self.form_status == 'completed':
if widget != self.window:
self.window.destroy()
return False
if self.allow_stage3_close: if self.allow_stage3_close:
return False return False
def on_yes(button): def on_ok():
self.send_cancel()
self.allow_stage3_close = True self.allow_stage3_close = True
self.window.destroy() self.window.destroy()
dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT \ self.stage3_can_close(on_ok)
| gtk.DIALOG_MODAL, gtk.BUTTONS_YES_NO, _('Cancel confirmation'),
_('You are in process of executing command. Do you really want to '
'cancel it?'), on_response_yes=on_yes)
dialog.popup()
return True # Block event, don't close window return True # Block event, don't close window
def stage3_restart_button_clicked(self, widget):
def on_ok():
self.restart()
self.stage3_can_close(on_ok)
def stage3_back_button_clicked(self, widget): def stage3_back_button_clicked(self, widget):
self.stage3_submit_form('prev') self.stage3_submit_form('prev')
@ -320,9 +358,19 @@ class CommandWindow:
def stage3_execute_button_clicked(self, widget): def stage3_execute_button_clicked(self, widget):
self.stage3_submit_form('execute') self.stage3_submit_form('execute')
def stage3_finish_button_clicked(self, widget):
self.stage3_submit_form('complete')
def stage3_submit_form(self, action='execute'): def stage3_submit_form(self, action='execute'):
self.data_form_widget.set_sensitive(False) self.data_form_widget.set_sensitive(False)
if self.data_form_widget.get_data_form(): if self.data_form_widget.get_data_form():
df = self.data_form_widget.get_data_form()
if not df.is_valid():
dialogs.ErrorDialog(_('Invalid Form'),
_('The form is not filled correctly.'))
self.data_form_widget.set_sensitive(True)
return
self.data_form_widget.data_form.type = 'submit' self.data_form_widget.data_form.type = 'submit'
else: else:
self.data_form_widget.hide() self.data_form_widget.hide()
@ -331,6 +379,7 @@ class CommandWindow:
self.back_button.set_sensitive(False) self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False) self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(False) self.execute_button.set_sensitive(False)
self.finish_button.set_sensitive(False)
self.sending_form_progressbar.show() self.sending_form_progressbar.show()
self.setup_pulsing(self.sending_form_progressbar) self.setup_pulsing(self.sending_form_progressbar)
@ -379,18 +428,21 @@ class CommandWindow:
self.forward_button.set_sensitive( self.forward_button.set_sensitive(
actions.getTag('next') is not None) actions.getTag('next') is not None)
self.execute_button.set_sensitive(True) self.execute_button.set_sensitive(True)
self.finish_button.set_sensitive(actions.getTag('complete') is not \
None)
else: else:
self.close_button.set_sensitive(True) self.close_button.set_sensitive(True)
self.back_button.set_sensitive(False) self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False) self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(True) self.execute_button.set_sensitive(True)
self.finish_button.set_sensitive(False)
if self.form_status == 'completed': if self.form_status == 'completed':
self.close_button.set_sensitive(True) self.close_button.set_sensitive(True)
self.restart_button.set_sensitive(True)
self.back_button.hide() self.back_button.hide()
self.forward_button.hide() self.forward_button.hide()
self.execute_button.hide() self.execute_button.hide()
self.finish_button.hide()
self.close_button.show() self.close_button.show()
self.stage_adhoc_commands_window_delete_event = \ self.stage_adhoc_commands_window_delete_event = \
self.stage3_close_button_clicked self.stage3_close_button_clicked
@ -404,7 +456,7 @@ class CommandWindow:
self.notes_label.set_no_show_all(True) self.notes_label.set_no_show_all(True)
self.notes_label.hide() self.notes_label.hide()
def on_restart_button_clicked(self, widget): def restart(self):
self.commandnode = None self.commandnode = None
self.initiate() self.initiate()
@ -423,14 +475,19 @@ class CommandWindow:
self.back_button.set_sensitive(False) self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False) self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(False) self.execute_button.set_sensitive(False)
self.finish_button.set_sensitive(False)
self.stage_finish = self.do_nothing self.stage_finish = self.do_nothing
self.stage_close_button_clicked = self.stage4_close_button_clicked self.stage_close_button_clicked = self.stage4_close_button_clicked
self.stage_restart_button_clicked = self.stage4_restart_button_clicked
self.stage_adhoc_commands_window_delete_event = self.do_nothing self.stage_adhoc_commands_window_delete_event = self.do_nothing
def stage4_close_button_clicked(self, widget): def stage4_close_button_clicked(self, widget):
self.window.destroy() self.window.destroy()
def stage4_restart_button_clicked(self, widget):
self.restart()
def on_check_commands_2_button_clicked(self, widget): def on_check_commands_2_button_clicked(self, widget):
self.stage1() self.stage1()
@ -468,16 +525,21 @@ class CommandWindow:
self.back_button.hide() self.back_button.hide()
self.forward_button.hide() self.forward_button.hide()
self.execute_button.hide() self.execute_button.hide()
self.finish_button.hide()
self.error_description_label.set_text(error) self.error_description_label.set_text(error)
self.stage_finish = self.do_nothing self.stage_finish = self.do_nothing
self.stage_close_button_clicked = self.stage5_close_button_clicked self.stage_close_button_clicked = self.stage5_close_button_clicked
self.stage_restart_button_clicked = self.stage5_restart_button_clicked
self.stage_adhoc_commands_window_delete_event = self.do_nothing self.stage_adhoc_commands_window_delete_event = self.do_nothing
def stage5_close_button_clicked(self, widget): def stage5_close_button_clicked(self, widget):
self.window.destroy() self.window.destroy()
def stage5_restart_button_clicked(self, widget):
self.restart()
# helpers to handle pulsing in progressbar # helpers to handle pulsing in progressbar
def setup_pulsing(self, progressbar): def setup_pulsing(self, progressbar):
""" """
@ -554,7 +616,7 @@ class CommandWindow:
cmdnode.setAttr('sessionid', self.sessionid) cmdnode.setAttr('sessionid', self.sessionid)
if self.data_form_widget.data_form: if self.data_form_widget.data_form:
cmdnode.addChild(node=self.data_form_widget.data_form) cmdnode.addChild(node=self.data_form_widget.data_form.get_purged())
def callback(response): def callback(response):
# FIXME: move to connection_handlers.py # FIXME: move to connection_handlers.py

View File

@ -53,15 +53,16 @@ class AtomWindow:
""" """
Create new window... only if we have anything to show Create new window... only if we have anything to show
""" """
assert len(self.__class__.entries)>0 assert len(self.__class__.entries)
self.entry = None # the entry actually displayed self.entry = None # the entry actually displayed
self.xml = gtkgui_helpers.get_gtk_builder('atom_entry_window.ui') self.xml = gtkgui_helpers.get_gtk_builder('atom_entry_window.ui')
self.window = self.xml.get_object('atom_entry_window') self.window = self.xml.get_object('atom_entry_window')
for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox', for name in ('new_entry_label', 'feed_title_label',
'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox', 'feed_title_eventbox', 'feed_tagline_label', 'entry_title_label',
'last_modified_label', 'close_button', 'next_button'): 'entry_title_eventbox', 'last_modified_label', 'close_button',
'next_button'):
self.__dict__[name] = self.xml.get_object(name) self.__dict__[name] = self.xml.get_object(name)
self.displayNextEntry() self.displayNextEntry()
@ -83,23 +84,26 @@ class AtomWindow:
# fill the fields # fill the fields
if newentry.feed_link is not None: if newentry.feed_link is not None:
self.feed_title_label.set_markup( self.feed_title_label.set_markup(
u'<span foreground="blue" underline="single">%s</span>' % \ u'<span foreground="blue" underline="single">%s</span>' % \
gobject.markup_escape_text(newentry.feed_title)) gobject.markup_escape_text(newentry.feed_title))
else: else:
self.feed_title_label.set_markup( self.feed_title_label.set_markup(
gobject.markup_escape_text(newentry.feed_title)) gobject.markup_escape_text(newentry.feed_title))
self.feed_tagline_label.set_markup( self.feed_tagline_label.set_markup(
u'<small>%s</small>' % \ u'<small>%s</small>' % \
gobject.markup_escape_text(newentry.feed_tagline)) gobject.markup_escape_text(newentry.feed_tagline))
if newentry.uri is not None: if newentry.title:
self.entry_title_label.set_markup( if newentry.uri is not None:
self.entry_title_label.set_markup(
u'<span foreground="blue" underline="single">%s</span>' % \ u'<span foreground="blue" underline="single">%s</span>' % \
gobject.markup_escape_text(newentry.title)) gobject.markup_escape_text(newentry.title))
else: else:
self.entry_title_label.set_markup( self.entry_title_label.set_markup(
gobject.markup_escape_text(newentry.title)) gobject.markup_escape_text(newentry.title))
else:
self.entry_title_label.set_markup('')
self.last_modified_label.set_text(newentry.updated) self.last_modified_label.set_text(newentry.updated)
@ -114,11 +118,11 @@ class AtomWindow:
changed changed
""" """
count = len(self.__class__.entries) count = len(self.__class__.entries)
if count>0: if count:
self.new_entry_label.set_text(i18n.ngettext( self.new_entry_label.set_text(i18n.ngettext(
'You have received new entries (and %d not displayed):', 'You have received new entries (and %d not displayed):',
'You have received new entries (and %d not displayed):', count, 'You have received new entries (and %d not displayed):', count,
count, count)) count, count))
self.next_button.set_sensitive(True) self.next_button.set_sensitive(True)
else: else:
self.new_entry_label.set_text(_('You have received new entry:')) self.new_entry_label.set_text(_('You have received new entry:'))
@ -131,7 +135,7 @@ class AtomWindow:
def on_next_button_clicked(self, widget): def on_next_button_clicked(self, widget):
self.displayNextEntry() self.displayNextEntry()
def on_entry_title_button_press_event(self, widget, event): def on_entry_title_eventbox_button_press_event(self, widget, event):
#FIXME: make it using special gtk2.10 widget #FIXME: make it using special gtk2.10 widget
if event.button == 1: # left click if event.button == 1: # left click
uri = self.entry.uri uri = self.entry.uri
@ -139,7 +143,7 @@ class AtomWindow:
helpers.launch_browser_mailer('url', uri) helpers.launch_browser_mailer('url', uri)
return True return True
def on_feed_title_button_press_event(self, widget, event): def on_feed_title_eventbox_button_press_event(self, widget, event):
#FIXME: make it using special gtk2.10 widget #FIXME: make it using special gtk2.10 widget
if event.button == 1: # left click if event.button == 1: # left click
uri = self.entry.feed_uri uri = self.entry.feed_uri

View File

@ -46,6 +46,7 @@ from common import exceptions
from message_control import MessageControl from message_control import MessageControl
from conversation_textview import ConversationTextview from conversation_textview import ConversationTextview
from message_textview import MessageTextView from message_textview import MessageTextView
from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
from common.contacts import GC_Contact from common.contacts import GC_Contact
from common.logger import constants from common.logger import constants
from common.pep import MOODS, ACTIVITIES from common.pep import MOODS, ACTIVITIES
@ -95,8 +96,14 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
""" """
keymap = gtk.gdk.keymap_get_default() keymap = gtk.gdk.keymap_get_default()
keycode_c = keymap.get_entries_for_keyval(gtk.keysyms.c)[0][0] try:
keycode_ins = keymap.get_entries_for_keyval(gtk.keysyms.Insert)[0][0] keycode_c = keymap.get_entries_for_keyval(gtk.keysyms.c)[0][0]
except TypeError:
keycode_c = 54
try:
keycode_ins = keymap.get_entries_for_keyval(gtk.keysyms.Insert)[0][0]
except TypeError:
keycode_ins = 118
def make_href(self, match): def make_href(self, match):
url_color = gajim.config.get('urlmsgcolor') url_color = gajim.config.get('urlmsgcolor')
url = match.group() url = match.group()
@ -149,6 +156,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
""" """
self.draw_banner_text() self.draw_banner_text()
self._update_banner_state_image() self._update_banner_state_image()
gajim.plugin_manager.gui_extension_point('chat_control_base_draw_banner',
self)
def draw_banner_text(self): def draw_banner_text(self):
""" """
@ -232,6 +241,29 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
def status_url_clicked(self, widget, url): def status_url_clicked(self, widget, url):
helpers.launch_browser_mailer('url', url) helpers.launch_browser_mailer('url', url)
def setup_seclabel(self, combo):
self.seclabel_combo = combo
self.seclabel_combo.hide()
self.seclabel_combo.set_no_show_all(True)
lb = gtk.ListStore(str)
self.seclabel_combo.set_model(lb)
cell = gtk.CellRendererText()
cell.set_property('xpad', 5) # padding for status text
self.seclabel_combo.pack_start(cell, True)
# text to show is in in first column of liststore
self.seclabel_combo.add_attribute(cell, 'text', 0)
if gajim.connections[self.account].seclabel_supported:
gajim.connections[self.account].seclabel_catalogue(self.contact.jid, self.on_seclabels_ready)
def on_seclabels_ready(self):
lb = self.seclabel_combo.get_model()
lb.clear()
for label in gajim.connections[self.account].seclabel_catalogues[self.contact.jid][2]:
lb.append([label])
self.seclabel_combo.set_active(0)
self.seclabel_combo.set_no_show_all(False)
self.seclabel_combo.show_all()
def __init__(self, type_id, parent_win, widget_name, contact, acct, def __init__(self, type_id, parent_win, widget_name, contact, acct,
resource=None): resource=None):
# Undo needs this variable to know if space has been pressed. # Undo needs this variable to know if space has been pressed.
@ -380,6 +412,14 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.command_hits = [] self.command_hits = []
self.last_key_tabs = False self.last_key_tabs = False
# PluginSystem: adding GUI extension point for ChatControlBase
# instance object (also subclasses, eg. ChatControl or GroupchatControl)
gajim.plugin_manager.gui_extension_point('chat_control_base', self)
# This is bascially a very nasty hack to surpass the inability
# to properly use the super, because of the old code.
CommandTools.__init__(self)
def set_speller(self): def set_speller(self):
# now set the one the user selected # now set the one the user selected
per_type = 'contacts' per_type = 'contacts'
@ -415,6 +455,12 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
i += 1 i += 1
menu.show_all() menu.show_all()
def shutdown(self):
# PluginSystem: removing GUI extension points connected with ChatControlBase
# instance object
gajim.plugin_manager.remove_gui_extension_point('chat_control_base', self)
gajim.plugin_manager.remove_gui_extension_point('chat_control_base_draw_banner', self)
def on_msg_textview_populate_popup(self, textview, menu): def on_msg_textview_populate_popup(self, textview, menu):
""" """
Override the default context menu and we prepend an option to switch Override the default context menu and we prepend an option to switch
@ -721,6 +767,16 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.drag_entered_conv = True self.drag_entered_conv = True
self.conv_textview.tv.set_editable(True) self.conv_textview.tv.set_editable(True)
def get_seclabel(self):
label = None
if self.seclabel_combo is not None:
idx = self.seclabel_combo.get_active()
if idx != -1:
cat = gajim.connections[self.account].seclabel_catalogues[self.contact.jid]
lname = cat[2][idx]
label = cat[1][lname]
return label
def send_message(self, message, keyID='', type_='chat', chatstate=None, def send_message(self, message, keyID='', type_='chat', chatstate=None,
msg_id=None, composing_xep=None, resource=None, xhtml=None, msg_id=None, composing_xep=None, resource=None, xhtml=None,
callback=None, callback_args=[], process_commands=True): callback=None, callback_args=[], process_commands=True):
@ -733,9 +789,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if process_commands and self.process_as_command(message): if process_commands and self.process_as_command(message):
return return
label = self.get_seclabel()
MessageControl.send_message(self, message, keyID, type_=type_, MessageControl.send_message(self, message, keyID, type_=type_,
chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep,
resource=resource, user_nick=self.user_nick, xhtml=xhtml, resource=resource, user_nick=self.user_nick, xhtml=xhtml,
label=label,
callback=callback, callback_args=callback_args) callback=callback, callback_args=callback_args)
# Record message history # Record message history
@ -769,7 +827,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
other_tags_for_name=[], other_tags_for_time=[], other_tags_for_name=[], other_tags_for_time=[],
other_tags_for_text=[], count_as_new=True, subject=None, other_tags_for_text=[], count_as_new=True, subject=None,
old_kind=None, xhtml=None, simple=False, xep0184_id=None, old_kind=None, xhtml=None, simple=False, xep0184_id=None,
graphics=True): graphics=True, displaymarking=None):
""" """
Print 'chat' type messages Print 'chat' type messages
""" """
@ -781,7 +839,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
end = True end = True
textview.print_conversation_line(text, jid, kind, name, tim, textview.print_conversation_line(text, jid, kind, name, tim,
other_tags_for_name, other_tags_for_time, other_tags_for_text, other_tags_for_name, other_tags_for_time, other_tags_for_text,
subject, old_kind, xhtml, simple=simple, graphics=graphics) subject, old_kind, xhtml, simple=simple, graphics=graphics,
displaymarking=displaymarking)
if xep0184_id is not None: if xep0184_id is not None:
textview.show_xep0184_warning(xep0184_id) textview.show_xep0184_warning(xep0184_id)
@ -1413,6 +1472,15 @@ class ChatControl(ChatControlBase):
id_ = widget.connect('released', self.on_num_button_released) id_ = widget.connect('released', self.on_num_button_released)
self.handlers[id_] = widget self.handlers[id_] = widget
self.dtmf_window = self.xml.get_object('dtmf_window')
id_ = self.dtmf_window.connect('focus-out-event',
self.on_dtmf_window_focus_out_event)
self.handlers[id_] = self.dtmf_window
widget = self.xml.get_object('dtmf_button')
id_ = widget.connect('clicked', self.on_dtmf_button_clicked)
self.handlers[id_] = widget
widget = self.xml.get_object('mic_hscale') widget = self.xml.get_object('mic_hscale')
id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed) id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed)
self.handlers[id_] = widget self.handlers[id_] = widget
@ -1429,6 +1497,7 @@ class ChatControl(ChatControlBase):
session = gajim.connections[self.account].find_controlless_session( session = gajim.connections[self.account].find_controlless_session(
self.contact.jid, resource) self.contact.jid, resource)
self.setup_seclabel(self.xml.get_object('label_selector'))
if session: if session:
session.control = self session.control = self
self.session = session self.session = session
@ -1533,6 +1602,10 @@ class ChatControl(ChatControlBase):
else: else:
img.hide() img.hide()
# PluginSystem: adding GUI extension point for this ChatControl
# instance object
gajim.plugin_manager.gui_extension_point('chat_control', self)
def _update_jingle(self, jingle_type): def _update_jingle(self, jingle_type):
if jingle_type not in ('audio', 'video'): if jingle_type not in ('audio', 'video'):
return return
@ -1558,7 +1631,7 @@ class ChatControl(ChatControlBase):
def update_audio(self): def update_audio(self):
self._update_jingle('audio') self._update_jingle('audio')
vbox = self.xml.get_object('audio_vbox') hbox = self.xml.get_object('audio_buttons_hbox')
if self.audio_state == self.JINGLE_STATE_CONNECTED: if self.audio_state == self.JINGLE_STATE_CONNECTED:
# Set volume from config # Set volume from config
input_vol = gajim.config.get('audio_input_volume') input_vol = gajim.config.get('audio_input_volume')
@ -1568,11 +1641,11 @@ class ChatControl(ChatControlBase):
self.xml.get_object('mic_hscale').set_value(input_vol) self.xml.get_object('mic_hscale').set_value(input_vol)
self.xml.get_object('sound_hscale').set_value(output_vol) self.xml.get_object('sound_hscale').set_value(output_vol)
# Show vbox # Show vbox
vbox.set_no_show_all(False) hbox.set_no_show_all(False)
vbox.show_all() hbox.show_all()
elif not self.audio_sid: elif not self.audio_sid:
vbox.set_no_show_all(True) hbox.set_no_show_all(True)
vbox.hide() hbox.hide()
def update_video(self): def update_video(self):
self._update_jingle('video') self._update_jingle('video')
@ -1646,19 +1719,21 @@ class ChatControl(ChatControlBase):
def on_num_button_released(self, released): def on_num_button_released(self, released):
self._get_audio_content()._stop_dtmf() self._get_audio_content()._stop_dtmf()
def on_mic_hscale_value_changed(self, widget): def on_dtmf_button_clicked(self, widget):
value = widget.get_value() self.dtmf_window.show_all()
def on_dtmf_window_focus_out_event(self, widget, event):
self.dtmf_window.hide()
def on_mic_hscale_value_changed(self, widget, value):
self._get_audio_content().set_mic_volume(value / 100) self._get_audio_content().set_mic_volume(value / 100)
# Save volume to config # Save volume to config
# FIXME: Putting it here is maybe not the right thing to do?
gajim.config.set('audio_input_volume', value) gajim.config.set('audio_input_volume', value)
def on_sound_hscale_value_changed(self, widget): def on_sound_hscale_value_changed(self, widget, value):
value = widget.get_value()
self._get_audio_content().set_out_volume(value / 100) self._get_audio_content().set_out_volume(value / 100)
# Save volume to config # Save volume to config
# FIXME: Putting it here is maybe not the right thing to do?
gajim.config.set('audio_output_volume', value) gajim.config.set('audio_output_volume', value)
def on_avatar_eventbox_enter_notify_event(self, widget, event): def on_avatar_eventbox_enter_notify_event(self, widget, event):
@ -2048,20 +2123,23 @@ class ChatControl(ChatControlBase):
gobject.source_remove(self.possible_inactive_timeout_id) gobject.source_remove(self.possible_inactive_timeout_id)
self._schedule_activity_timers() self._schedule_activity_timers()
def _on_sent(id_, contact, message, encrypted, xhtml): def _on_sent(id_, contact, message, encrypted, xhtml, label):
if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts', if contact.supports(NS_RECEIPTS) and gajim.config.get_per('accounts',
self.account, 'request_receipt'): self.account, 'request_receipt'):
xep0184_id = id_ xep0184_id = id_
else: else:
xep0184_id = None xep0184_id = None
if label:
displaymarking = label.getTag('displaymarking')
else:
displaymarking = None
self.print_conversation(message, self.contact.jid, encrypted=encrypted, self.print_conversation(message, self.contact.jid, encrypted=encrypted,
xep0184_id=xep0184_id, xhtml=xhtml) xep0184_id=xep0184_id, xhtml=xhtml, displaymarking=displaymarking)
ChatControlBase.send_message(self, message, keyID, type_='chat', ChatControlBase.send_message(self, message, keyID, type_='chat',
chatstate=chatstate_to_send, composing_xep=composing_xep, chatstate=chatstate_to_send, composing_xep=composing_xep,
xhtml=xhtml, callback=_on_sent, xhtml=xhtml, callback=_on_sent,
callback_args=[contact, message, encrypted, xhtml], callback_args=[contact, message, encrypted, xhtml, self.get_seclabel()],
process_commands=process_commands) process_commands=process_commands)
def check_for_possible_paused_chatstate(self, arg): def check_for_possible_paused_chatstate(self, arg):
@ -2125,6 +2203,18 @@ class ChatControl(ChatControlBase):
msg = _('Session negotiation cancelled') msg = _('Session negotiation cancelled')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None) ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
def print_archiving_session_details(self):
"""
Print esession settings to textview
"""
archiving = bool(self.session) and isinstance(self.session,
ArchivingStanzaSession) and self.session.archiving
if archiving:
msg = _('This session WILL be archived on server')
else:
msg = _('This session WILL NOT be archived on server')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
def print_esession_details(self): def print_esession_details(self):
""" """
Print esession settings to textview Print esession settings to textview
@ -2149,8 +2239,15 @@ class ChatControl(ChatControlBase):
self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \ self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \
self.session.is_loggable(), self.session and self.session.verified_identity) self.session.is_loggable(), self.session and self.session.verified_identity)
def print_session_details(self):
if isinstance(self.session, EncryptedStanzaSession):
self.print_esession_details()
elif isinstance(self.session, ArchivingStanzaSession):
self.print_archiving_session_details()
def print_conversation(self, text, frm='', tim=None, encrypted=False, def print_conversation(self, text, frm='', tim=None, encrypted=False,
subject=None, xhtml=None, simple=False, xep0184_id=None): subject=None, xhtml=None, simple=False, xep0184_id=None,
displaymarking=None):
""" """
Print a line in the conversation Print a line in the conversation
@ -2213,7 +2310,7 @@ class ChatControl(ChatControlBase):
xhtml = '<body xmlns="%s">%s</body>' % (NS_XHTML, xhtml) xhtml = '<body xmlns="%s">%s</body>' % (NS_XHTML, xhtml)
ChatControlBase.print_conversation_line(self, text, kind, name, tim, ChatControlBase.print_conversation_line(self, text, kind, name, tim,
subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml, subject=subject, old_kind=self.old_msg_kind, xhtml=xhtml,
simple=simple, xep0184_id=xep0184_id) simple=simple, xep0184_id=xep0184_id, displaymarking=displaymarking)
if text.startswith('/me ') or text.startswith('/me\n'): if text.startswith('/me ') or text.startswith('/me\n'):
self.old_msg_kind = None self.old_msg_kind = None
else: else:
@ -2397,7 +2494,13 @@ class ChatControl(ChatControlBase):
self.reset_kbd_mouse_timeout_vars() self.reset_kbd_mouse_timeout_vars()
def shutdown(self): def shutdown(self):
# Send 'gone' chatstate # PluginSystem: calling shutdown of super class (ChatControlBase) to let it remove
# it's GUI extension points
super(ChatControl, self).shutdown()
# PluginSystem: removing GUI extension points connected with ChatControl
# instance object
gajim.plugin_manager.remove_gui_extension_point('chat_control', self) # Send 'gone' chatstate
self.send_chatstate('gone', self.contact) self.send_chatstate('gone', self.contact)
self.contact.chatstate = None self.contact.chatstate = None
self.contact.our_chatstate = None self.contact.our_chatstate = None
@ -2574,6 +2677,9 @@ class ChatControl(ChatControlBase):
if want_e2e and not self.no_autonegotiation \ if want_e2e and not self.no_autonegotiation \
and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION): and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION):
self.begin_e2e_negotiation() self.begin_e2e_negotiation()
elif (not self.session or not self.session.status) and \
gajim.connections[self.account].archiving_supported:
self.begin_archiving_negotiation()
else: else:
self.send_chatstate('active', self.contact) self.send_chatstate('active', self.contact)
@ -2660,8 +2766,12 @@ class ChatControl(ChatControlBase):
kind = 'info' kind = 'info'
else: else:
kind = 'print_queue' kind = 'print_queue'
dm = None
if len(data) > 10:
dm = data[10]
self.print_conversation(data[0], kind, tim = data[3], self.print_conversation(data[0], kind, tim = data[3],
encrypted = data[4], subject = data[1], xhtml = data[7]) encrypted = data[4], subject = data[1], xhtml = data[7],
displaymarking=dm)
if len(data) > 6 and isinstance(data[6], int): if len(data) > 6 and isinstance(data[6], int):
message_ids.append(data[6]) message_ids.append(data[6])
@ -2813,7 +2923,7 @@ class ChatControl(ChatControlBase):
else: else:
self.begin_e2e_negotiation() self.begin_e2e_negotiation()
def begin_e2e_negotiation(self): def begin_negotiation(self):
self.no_autonegotiation = True self.no_autonegotiation = True
if not self.session: if not self.session:
@ -2821,8 +2931,14 @@ class ChatControl(ChatControlBase):
new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id) new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id)
self.set_session(new_sess) self.set_session(new_sess)
def begin_e2e_negotiation(self):
self.begin_negotiation()
self.session.negotiate_e2e(False) self.session.negotiate_e2e(False)
def begin_archiving_negotiation(self):
self.begin_negotiation()
self.session.negotiate_archiving()
def got_connected(self): def got_connected(self):
ChatControlBase.got_connected(self) ChatControlBase.got_connected(self)
# Refreshing contact # Refreshing contact

View File

@ -0,0 +1,117 @@
# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Backbone of the command system. Provides smart and controllable
dispatching mechanism with an auto-discovery functionality. In addition
to automatic discovery and dispatching, also features manual control
over the process.
"""
from types import NoneType
from tools import remove
COMMANDS = {}
CONTAINERS = {}
def add_host(host):
CONTAINERS[host] = []
def remove_host(host):
remove(CONTAINERS, host)
def add_container(container):
for host in container.HOSTS:
CONTAINERS[host].append(container)
def remove_container(container):
for host in container.HOSTS:
remove(CONTAINERS[host], container)
def add_commands(container):
commands = COMMANDS.setdefault(container, {})
for command in traverse_commands(container):
for name in command.names:
commands[name] = command
def remove_commands(container):
remove(COMMANDS, container)
def traverse_commands(container):
for name in dir(container):
attribute = getattr(container, name)
if is_command(attribute):
yield attribute
def is_command(attribute):
from framework import Command
return isinstance(attribute, Command)
def is_root(namespace):
metaclass = namespace.get("__metaclass__", NoneType)
return issubclass(metaclass, Dispatchable)
def get_command(host, name):
for container in CONTAINERS[host]:
command = COMMANDS[container].get(name)
if command:
return command
def list_commands(host):
for container in CONTAINERS[host]:
commands = COMMANDS[container]
for name, command in commands.iteritems():
yield name, command
class Dispatchable(type):
def __init__(self, name, bases, namespace):
parents = super(Dispatchable, self)
parents.__init__(name, bases, namespace)
if not is_root(namespace):
self.dispatch()
def dispatch(self):
if self.AUTOMATIC:
self.enable()
class Host(Dispatchable):
def enable(self):
add_host(self)
def disable(self):
remove_host(self)
class Container(Dispatchable):
def enable(self):
add_container(self)
add_commands(self)
def disable(self):
remove_commands(self)
remove_container(self)

View File

@ -1,90 +0,0 @@
# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
The backbone of the command system. Provides automatic dispatching which
does not require explicit registering commands or containers and remains
active even after everything is done, so new commands can be added
during the runtime.
"""
from types import NoneType
class Dispatcher(type):
containers = {}
commands = {}
@classmethod
def register_host(klass, host):
klass.containers[host] = []
@classmethod
def register_container(klass, container):
for host in container.HOSTS:
klass.containers[host].append(container)
@classmethod
def register_commands(klass, container):
klass.commands[container] = {}
for command in klass.traverse_commands(container):
for name in command.names:
klass.commands[container][name] = command
@classmethod
def get_command(klass, host, name):
for container in klass.containers[host]:
command = klass.commands[container].get(name)
if command:
return command
@classmethod
def list_commands(klass, host):
for container in klass.containers[host]:
commands = klass.commands[container]
for name, command in commands.iteritems():
yield name, command
@classmethod
def traverse_commands(klass, container):
for name in dir(container):
attribute = getattr(container, name)
if klass.is_command(attribute):
yield attribute
@staticmethod
def is_root(ns):
meta = ns.get('__metaclass__', NoneType)
return issubclass(meta, Dispatcher)
@staticmethod
def is_command(attribute):
name = attribute.__class__.__name__
return name == 'Command'
class HostDispatcher(Dispatcher):
def __init__(klass, name, bases, ns):
if not Dispatcher.is_root(ns):
HostDispatcher.register_host(klass)
super(HostDispatcher, klass).__init__(name, bases, ns)
class ContainerDispatcher(Dispatcher):
def __init__(klass, name, bases, ns):
if not Dispatcher.is_root(ns):
ContainerDispatcher.register_container(klass)
ContainerDispatcher.register_commands(klass)
super(ContainerDispatcher, klass).__init__(name, bases, ns)

View File

@ -21,9 +21,10 @@ declarative way.
import re import re
from types import FunctionType from types import FunctionType
from inspect import getargspec from inspect import getargspec, getdoc
from dispatching import Dispatcher, HostDispatcher, ContainerDispatcher from dispatcher import Host, Container
from dispatcher import get_command, list_commands
from mapping import parse_arguments, adapt_arguments from mapping import parse_arguments, adapt_arguments
from errors import DefinitionError, CommandError, NoCommandError from errors import DefinitionError, CommandError, NoCommandError
@ -32,8 +33,12 @@ class CommandHost(object):
Command host is a hub between numerous command processors and Command host is a hub between numerous command processors and
command containers. Aimed to participate in a dispatching process in command containers. Aimed to participate in a dispatching process in
order to provide clean and transparent architecture. order to provide clean and transparent architecture.
The AUTOMATIC class variable, which must be defined by a command
host, specifies whether the command host should be automatically
dispatched and enabled by the dispatcher or not.
""" """
__metaclass__ = HostDispatcher __metaclass__ = Host
class CommandContainer(object): class CommandContainer(object):
""" """
@ -41,11 +46,15 @@ class CommandContainer(object):
allowing them to be dispatched and proccessed correctly. Each allowing them to be dispatched and proccessed correctly. Each
command container may be bound to a one or more command hosts. command container may be bound to a one or more command hosts.
Bounding is controlled by the HOSTS variable, which must be defined The AUTOMATIC class variable, which must be defined by a command
in the body of the command container. This variable should contain a processor, specifies whether the command processor should be
list of hosts to bound to, as a tuple or list. automatically dispatched and enabled by the dispatcher or not.
Bounding is controlled by the HOSTS class variable, which must be
defined by the command container. This variable should contain a
sequence of hosts to bound to, as a tuple or list.
""" """
__metaclass__ = ContainerDispatcher __metaclass__ = Container
class CommandProcessor(object): class CommandProcessor(object):
""" """
@ -126,24 +135,18 @@ class CommandProcessor(object):
pass pass
def get_command(self, name): def get_command(self, name):
command = Dispatcher.get_command(self.COMMAND_HOST, name) command = get_command(self.COMMAND_HOST, name)
if not command: if not command:
raise NoCommandError("Command does not exist", name=name) raise NoCommandError("Command does not exist", name=name)
return command return command
def list_commands(self): def list_commands(self):
commands = Dispatcher.list_commands(self.COMMAND_HOST) commands = list_commands(self.COMMAND_HOST)
commands = dict(commands) commands = dict(commands)
return sorted(set(commands.itervalues())) return sorted(set(commands.itervalues()))
class Command(object): class Command(object):
# These two regular expression patterns control how command
# documentation will be formatted to be transformed to a normal,
# readable state.
DOC_STRIP_PATTERN = re.compile(r'(?:^[ \t]+|\A\n)', re.MULTILINE)
DOC_FORMAT_PATTERN = re.compile(r'(?<!\n)\n(?!\n)', re.MULTILINE)
def __init__(self, handler, *names, **properties): def __init__(self, handler, *names, **properties):
self.handler = handler self.handler = handler
self.names = names self.names = names
@ -192,19 +195,8 @@ class Command(object):
""" """
Extract handler's documentation which is a doc-string and Extract handler's documentation which is a doc-string and
transform it to a usable format. transform it to a usable format.
Transformation is done based on the DOC_STRIP_PATTERN and
DOC_FORMAT_PATTERN regular expression patterns.
""" """
documentation = self.handler.__doc__ or None return getdoc(self.handler)
if not documentation:
return
documentation = re.sub(self.DOC_STRIP_PATTERN, str(), documentation)
documentation = re.sub(self.DOC_FORMAT_PATTERN, ' ', documentation)
return documentation
def extract_description(self): def extract_description(self):
""" """
@ -243,9 +235,9 @@ def command(*names, **properties):
can be reached. If no names are given - the the native name (the one can be reached. If no names are given - the the native name (the one
extracted from the command handler) will be used. extracted from the command handler) will be used.
If include_native=True is given (default) and names is non-empty - If native=True is given (default) and names is non-empty - then the
then the native name of the command will be prepended in addition to native name of the command will be prepended in addition to the
the given names. given names.
If usage=True is given (default) - then command help will be If usage=True is given (default) - then command help will be
appended with autogenerated usage info, based of the command handler appended with autogenerated usage info, based of the command handler
@ -273,14 +265,14 @@ def command(*names, **properties):
If overlap=True is given - then all the extra arguments will be If overlap=True is given - then all the extra arguments will be
mapped as if they were values for the keyword arguments. mapped as if they were values for the keyword arguments.
If expand_short=True is given (default) - then short, one-letter If expand=True is given (default) - then short, one-letter options
options will be expanded to a verbose ones, based of the comparison will be expanded to a verbose ones, based of the comparison of the
of the first letter. If more then one option with the same first first letter. If more then one option with the same first letter is
letter is given - then only first one will be used in the expansion. given - then only first one will be used in the expansion.
""" """
names = list(names) names = list(names)
include_native = properties.get('include_native', True) native = properties.get('native', True)
usage = properties.get('usage', True) usage = properties.get('usage', True)
source = properties.get('source', False) source = properties.get('source', False)
@ -288,7 +280,7 @@ def command(*names, **properties):
empty = properties.get('empty', False) empty = properties.get('empty', False)
extra = properties.get('extra', False) extra = properties.get('extra', False)
overlap = properties.get('overlap', False) overlap = properties.get('overlap', False)
expand_short = properties.get('expand_short', True) expand = properties.get('expand', True)
if empty and not raw: if empty and not raw:
raise DefinitionError("Empty option can be used only with raw commands") raise DefinitionError("Empty option can be used only with raw commands")
@ -303,7 +295,7 @@ def command(*names, **properties):
'extra': extra, 'extra': extra,
'overlap': overlap, 'overlap': overlap,
'empty': empty, 'empty': empty,
'expand_short': expand_short 'expand': expand
} }
def decorator(handler): def decorator(handler):
@ -314,9 +306,9 @@ def command(*names, **properties):
command = Command(handler, *names, **properties) command = Command(handler, *names, **properties)
# Extract and inject a native name if either no other names are # Extract and inject a native name if either no other names are
# specified or include_native property is enabled, while making # specified or native property is enabled, while making
# sure it is going to be the first one in the list. # sure it is going to be the first one in the list.
if not names or include_native: if not names or native:
names.insert(0, command.native_name) names.insert(0, command.native_name)
command.names = tuple(names) command.names = tuple(names)

View File

@ -1,38 +1,56 @@
# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # Copyright (c) 2009-2010, Alexander Cherniuk (ts33kr@gmail.com)
# All rights reserved.
# #
# This program is free software: you can redistribute it and/or modify # Redistribution and use in source and binary forms, with or without
# it under the terms of the GNU General Public License as published by # modification, are permitted provided that the following conditions
# the Free Software Foundation, either version 3 of the License, or # are met:
# (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # * Redistributions of source code must retain the above copyright
# but WITHOUT ANY WARRANTY; without even the implied warranty of # notice, this list of conditions and the following disclaimer.
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # * Redistributions in binary form must reproduce the above copyright
# along with this program. If not, see <http://www.gnu.org/licenses/>. # notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
""" """
The module contains examples of how to create your own commands, by This module contains examples of how to create your own commands, by
creating a new command container and definding a set of commands. creating a new command container, bounded to a specific command host,
and definding a set of commands inside of it.
Keep in mind that this module is not being loaded, so the code will not Keep in mind that this module is not being loaded from anywhere, so the
be executed and commands defined here will not be detected. code in here will not be executed and commands defined here will not be
detected.
""" """
from ..framework import CommandContainer, command, doc from ..framework import CommandContainer, command, doc
from hosts import ChatCommands, PrivateChatCommands, GroupChatCommands from hosts import *
class CustomCommonCommands(CommandContainer): class CustomCommonCommands(CommandContainer):
""" """
The AUTOMATIC class variable, set to a positive value, instructs the
command system to automatically discover the command container and
enable it.
This command container bounds to all three available in the default This command container bounds to all three available in the default
implementation command hosts. This means that commands defined in implementation command hosts. This means that commands defined in
this container will be available to all - chat, private chat and a this container will be available to all: chat, private chat and a
group chat. group chat.
""" """
HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands) AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
@command @command
def dance(self): def dance(self):
@ -46,45 +64,58 @@ class CustomCommonCommands(CommandContainer):
After all the documentation - there will be autogenerated (based After all the documentation - there will be autogenerated (based
on the method signature) usage information appended. You can on the method signature) usage information appended. You can
turn it off though, if you want. turn it off, if you want.
""" """
return "I can't dance, you stupid fuck, I'm just a command system! A cool one, though..." return "I don't dance."
class CustomChatCommands(CommandContainer): class CustomChatCommands(CommandContainer):
""" """
This command container bounds only to the ChatCommands command host. This command container bounds only to the ChatCommands command host.
Therefore command defined here will be available only to a chat. Therefore commands defined inside of the container will be available
only to a chat.
""" """
HOSTS = (ChatCommands,) AUTOMATIC = True
HOSTS = ChatCommands,
@doc(_("The same as using a doc-string, except it supports translation")) @command("squal", "bawl")
@command
def sing(self): def sing(self):
return "Are you phreaking kidding me? Buy yourself a damn stereo..." """
This command has an additional aliases. It means the command will
be available under three names: sing (the native name), squal
(the first alias), bawl (the second alias).
You can turn off the usage of the native name, if you want, and
specify a name or a set of names, as aliases, under which a
command will be available.
"""
return "Buy yourself a stereo."
class CustomPrivateChatCommands(CommandContainer): class CustomPrivateChatCommands(CommandContainer):
""" """
This command container bounds only to the PrivateChatCommands This command container bounds only to the PrivateChatCommands
command host. Therefore command defined here will be available only command host. Therefore commands defined inside of the container
to a private chat. will be available only to a private chat.
""" """
HOSTS = (PrivateChatCommands,) AUTOMATIC = True
HOSTS = PrivateChatCommands,
@command @command
@doc(_("The same as using a doc-string, except it supports translation"))
def make_coffee(self): def make_coffee(self):
return "What do I look like, you ass? A coffee machine!?" return "I'm not a coffee machine!"
class CustomGroupChatCommands(CommandContainer): class CustomGroupChatCommands(CommandContainer):
""" """
This command container bounds only to the GroupChatCommands command This command container bounds only to the GroupChatCommands command
host. Therefore command defined here will be available only to a host. Therefore commands defined inside of the container will be
group chat. available only to a group chat.
""" """
HOSTS = (GroupChatCommands,) AUTOMATIC = True
HOSTS = GroupChatCommands,
@command @command
def fetch(self): def fetch(self):
return "You should really buy yourself a dog and start torturing it instead of me..." return "Buy yourself a dog."

View File

@ -0,0 +1,120 @@
# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Provides facilities to safely execute expressions inside a shell process
and capture the resulting output, in an asynchronous fashion, avoiding
deadlocks. If the process execution time reaches the threshold - it is
forced to terminate. Consists of a tiny framework and a couple of
commands as a frontend.
"""
from subprocess import Popen, PIPE
from os.path import expanduser
from glib import timeout_add
from ..framework import CommandContainer, command, doc
from hosts import *
class Execute(CommandContainer):
AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
DIRECTORY = "~"
POLL_INTERVAL = 100
POLL_COUNT = 5
@command("exec", raw=True)
@doc(_("Execute expression inside a shell, show output"))
def execute(self, expression):
Execute.spawn(self, expression)
@classmethod
def spawn(cls, processor, expression):
pipes = dict(stdout=PIPE, stderr=PIPE)
directory = expanduser(cls.DIRECTORY)
popen = Popen(expression, shell=True, cwd=directory, **pipes)
cls.monitor(processor, popen)
@classmethod
def monitor(cls, processor, popen):
poller = cls.poller(processor, popen)
timeout_add(cls.POLL_INTERVAL, poller.next)
@classmethod
def poller(cls, processor, popen):
for x in xrange(cls.POLL_COUNT):
yield cls.brush(processor, popen)
cls.overdue(processor, popen)
yield False
@classmethod
def brush(cls, processor, popen):
if popen.poll() is not None:
cls.terminated(processor, popen)
return False
return True
@classmethod
def terminated(cls, processor, popen):
stdout, stderr = cls.fetch(popen)
success = popen.returncode == 0
if success and stdout:
processor.echo(stdout)
elif not success and stderr:
processor.echo_error(stderr)
@classmethod
def overdue(cls, processor, popen):
popen.terminate()
@classmethod
def fetch(cls, popen):
data = popen.communicate()
return map(cls.clean, data)
@staticmethod
def clean(text):
strip = chr(10) + chr(32)
return text.strip(strip)
class Show(Execute):
@command("sh", raw=True)
@doc(_("Execute expression inside a shell, send output"))
def show(self, expression):
Show.spawn(self, expression)
@classmethod
def terminated(cls, processor, popen):
stdout, stderr = cls.fetch(popen)
success = popen.returncode == 0
if success and stdout:
processor.send(stdout)
elif not success and stderr:
processor.echo_error(stderr)

View File

@ -25,18 +25,18 @@ class ChatCommands(CommandHost):
This command host is bound to the command processor which processes This command host is bound to the command processor which processes
commands from a chat. commands from a chat.
""" """
pass AUTOMATIC = True
class PrivateChatCommands(CommandHost): class PrivateChatCommands(CommandHost):
""" """
This command host is bound to the command processor which processes This command host is bound to the command processor which processes
commands from a private chat. commands from a private chat.
""" """
pass AUTOMATIC = True
class GroupChatCommands(CommandHost): class GroupChatCommands(CommandHost):
""" """
This command host is bound to the command processor which processes This command host is bound to the command processor which processes
commands from a group chat. commands from a group chat.
""" """
pass AUTOMATIC = True

View File

@ -1,42 +1,58 @@
# Copyright (C) 2009-2010 Alexander Cherniuk <ts33kr@gmail.com> # Copyright (c) 2009-2010, Alexander Cherniuk (ts33kr@gmail.com)
# All rights reserved.
# #
# This program is free software: you can redistribute it and/or modify # Redistribution and use in source and binary forms, with or without
# it under the terms of the GNU General Public License as published by # modification, are permitted provided that the following conditions
# the Free Software Foundation, either version 3 of the License, or # are met:
# (at your option) any later version.
# #
# This program is distributed in the hope that it will be useful, # * Redistributions of source code must retain the above copyright
# but WITHOUT ANY WARRANTY; without even the implied warranty of # notice, this list of conditions and the following disclaimer.
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # * Redistributions in binary form must reproduce the above copyright
# along with this program. If not, see <http://www.gnu.org/licenses/>. # notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
""" """
Provides a glue to tie command system framework and the actual code Provides a glue to tie command system framework and the actual code
where it would be dropped in. Defines a little bit of scaffolding to where it would be dropped in. Defines a little bit of scaffolding to
support interaction between the two and a few utility methods so you support interaction between the two and a few utility methods so you
don't need to dig up the code itself code to write basic commands. don't need to dig up the code itself to write basic commands.
""" """
from types import StringTypes from types import StringTypes
from traceback import print_exc from traceback import print_exc
from pango import FontDescription
from common import gajim from common import gajim
from ..framework import CommandProcessor from ..framework import CommandProcessor
from ..errors import CommandError, NoCommandError from ..errors import CommandError, NoCommandError
from ..tools import gconf
class ChatCommandProcessor(CommandProcessor): class ChatCommandProcessor(CommandProcessor):
""" """
A basic scaffolding to provide convenient interaction between the A basic scaffolding to provide convenient interaction between the
command system and chat controls. command system and chat controls. It will be merged directly into
the controls, by ChatCommandProcessor being among superclasses of
the controls.
""" """
def process_as_command(self, text): def process_as_command(self, text):
self.command_succeeded = False self.command_succeeded = False
flag = super(ChatCommandProcessor, self).process_as_command(text) parents = super(ChatCommandProcessor, self)
flag = parents.process_as_command(text)
if flag and self.command_succeeded: if flag and self.command_succeeded:
self.add_history(text) self.add_history(text)
self.clear_input() self.clear_input()
@ -44,17 +60,18 @@ class ChatCommandProcessor(CommandProcessor):
def execute_command(self, name, arguments): def execute_command(self, name, arguments):
try: try:
super(ChatCommandProcessor, self).execute_command(name, arguments) parents = super(ChatCommandProcessor, self)
parents.execute_command(name, arguments)
except NoCommandError, error: except NoCommandError, error:
details = dict(name=error.name, message=error.message) details = dict(name=error.name, message=error.message)
message = "%(name)s: %(message)s\n" % details message = "%(name)s: %(message)s\n" % details
message += "Try using the //%(name)s or /say /%(name)s " % details message += "Try using the //%(name)s or /say /%(name)s " % details
message += "construct if you intended to send it as a text." message += "construct if you intended to send it as a text."
self.echo(message, 'error') self.echo_error(message)
except CommandError, error: except CommandError, error:
self.echo("%s: %s" % (error.name, error.message), 'error') self.echo_error("%s: %s" % (error.name, error.message))
except Exception: except Exception:
self.echo("An error occured while trying to execute the command", 'error') self.echo_error("Error during command execution!")
print_exc() print_exc()
else: else:
self.command_succeeded = True self.command_succeeded = True
@ -87,14 +104,52 @@ class ChatCommandProcessor(CommandProcessor):
class CommandTools: class CommandTools:
""" """
Contains a set of basic tools and shortcuts you can use in your Contains a set of basic tools and shortcuts you can use in your
commands to performe some simple operations. commands to perform some simple operations. These will be merged
directly into the controls, by CommandTools being among superclasses
of the controls.
""" """
def echo(self, text, kind='info'): def __init__(self):
self.install_tags()
def install_tags(self):
buffer = self.conv_textview.tv.get_buffer()
name = gconf("/desktop/gnome/interface/monospace_font_name")
name = name if name else "Monospace"
font = FontDescription(name)
command_ok_tag = buffer.create_tag("command_ok")
command_ok_tag.set_property("font-desc", font)
command_ok_tag.set_property("foreground", "#3465A4")
command_error_tag = buffer.create_tag("command_error")
command_error_tag.set_property("font-desc", font)
command_error_tag.set_property("foreground", "#F57900")
def shift_line(self):
buffer = self.conv_textview.tv.get_buffer()
iter = buffer.get_end_iter()
if iter.ends_line() and not iter.is_start():
buffer.insert_with_tags_by_name(iter, "\n", "eol")
def append_with_tags(self, text, *tags):
buffer = self.conv_textview.tv.get_buffer()
iter = buffer.get_end_iter()
buffer.insert_with_tags_by_name(iter, text, *tags)
def echo(self, text, tag="command_ok"):
""" """
Print given text to the user. Print given text to the user, as a regular command output.
""" """
self.print_conversation(str(text), kind) self.shift_line()
self.append_with_tags(text, tag)
def echo_error(self, text):
"""
Print given text to the user, as an error command output.
"""
self.echo(text, "command_error")
def send(self, text): def send(self, text):
""" """
@ -128,3 +183,10 @@ class CommandTools:
Get the current connection object. Get the current connection object.
""" """
return gajim.connections[self.account] return gajim.connections[self.account]
@property
def full_jid(self):
"""
Get a full JID of the contact.
"""
return self.contact.get_full_jid()

View File

@ -30,7 +30,8 @@ from ..errors import CommandError
from ..framework import CommandContainer, command, doc from ..framework import CommandContainer, command, doc
from ..mapping import generate_usage from ..mapping import generate_usage
from hosts import ChatCommands, PrivateChatCommands, GroupChatCommands from hosts import *
import execute
# This holds constants fron the logger, which we'll be using in some of our # This holds constants fron the logger, which we'll be using in some of our
# commands. # commands.
@ -42,7 +43,8 @@ class StandardCommonCommands(CommandContainer):
to all - chat, private chat, group chat. to all - chat, private chat, group chat.
""" """
HOSTS = (ChatCommands, PrivateChatCommands, GroupChatCommands) AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands, GroupChatCommands
@command @command
@doc(_("Clear the text window")) @doc(_("Clear the text window"))
@ -56,7 +58,7 @@ class StandardCommonCommands(CommandContainer):
self.chat_buttons_set_visible(new_status) self.chat_buttons_set_visible(new_status)
@command(overlap=True) @command(overlap=True)
@doc(_("Show help on a given command or a list of available commands if -(-a)ll is given")) @doc(_("Show help on a given command or a list of available commands if -a is given"))
def help(self, command=None, all=False): def help(self, command=None, all=False):
if command: if command:
command = self.get_command(command) command = self.get_command(command)
@ -163,7 +165,8 @@ class StandardCommonChatCommands(CommandContainer):
to a chat and a private chat only. to a chat and a private chat only.
""" """
HOSTS = (ChatCommands, PrivateChatCommands) AUTOMATIC = True
HOSTS = ChatCommands, PrivateChatCommands
@command @command
@doc(_("Toggle the GPG encryption")) @doc(_("Toggle the GPG encryption"))
@ -178,41 +181,37 @@ class StandardCommonChatCommands(CommandContainer):
gajim.connections[self.account].sendPing(self.contact) gajim.connections[self.account].sendPing(self.contact)
@command @command
@doc(_("Send DTMF events through an open audio session")) @doc(_("Send DTMF sequence through an open audio session"))
def dtmf(self, events): def dtmf(self, sequence):
if not self.audio_sid: if not self.audio_sid:
raise CommandError(_("There is no open audio session with this contact")) raise CommandError(_("No open audio sessions with the contact"))
# Valid values for DTMF tones are *, # or a number. for tone in sequence:
events = [e for e in events if e in ('*', '#') or e.isdigit()] if not (tone in ("*", "#") or tone.isdigit()):
if events: raise CommandError(_("%s is not a valid tone") % tone)
session = gajim.connections[self.account].get_jingle_session( gjs = self.connection.get_jingle_session
self.contact.get_full_jid(), self.audio_sid) session = gjs(self.full_jid, self.audio_sid)
content = session.get_content('audio') content = session.get_content("audio")
content.batch_dtmf(events) content.batch_dtmf(sequence)
else:
raise CommandError(_("No valid DTMF event specified"))
@command @command
@doc(_("Toggle audio session")) @doc(_("Toggle audio session"))
def audio(self): def audio(self):
if not self.audio_available: if not self.audio_available:
raise CommandError(_("Audio sessions are not available")) raise CommandError(_("Audio sessions are not available"))
else: # An audio session is toggled by inverting the state of the
# A state of an audio session is toggled by inverting a state of the # appropriate button.
# appropriate button. state = self._audio_button.get_active()
state = self._audio_button.get_active() self._audio_button.set_active(not state)
self._audio_button.set_active(not state)
@command @command
@doc(_("Toggle video session")) @doc(_("Toggle video session"))
def video(self): def video(self):
if not self.video_available: if not self.video_available:
raise CommandError(_("Video sessions are not available")) raise CommandError(_("Video sessions are not available"))
else: # A video session is toggled by inverting the state of the
# A state of a video session is toggled by inverting a state of the # appropriate button.
# appropriate button. state = self._video_button.get_active()
state = self._video_button.get_active() self._video_button.set_active(not state)
self._video_button.set_active(not state)
class StandardChatCommands(CommandContainer): class StandardChatCommands(CommandContainer):
""" """
@ -220,7 +219,8 @@ class StandardChatCommands(CommandContainer):
to a chat. to a chat.
""" """
HOSTS = (ChatCommands,) AUTOMATIC = True
HOSTS = ChatCommands,
class StandardPrivateChatCommands(CommandContainer): class StandardPrivateChatCommands(CommandContainer):
""" """
@ -228,7 +228,8 @@ class StandardPrivateChatCommands(CommandContainer):
to a private chat. to a private chat.
""" """
HOSTS = (PrivateChatCommands,) AUTOMATIC = True
HOSTS = PrivateChatCommands,
class StandardGroupChatCommands(CommandContainer): class StandardGroupChatCommands(CommandContainer):
""" """
@ -236,7 +237,8 @@ class StandardGroupChatCommands(CommandContainer):
to a group chat. to a group chat.
""" """
HOSTS = (GroupChatCommands,) AUTOMATIC = True
HOSTS = GroupChatCommands,
@command(raw=True) @command(raw=True)
@doc(_("Change your nickname in a group chat")) @doc(_("Change your nickname in a group chat"))
@ -324,23 +326,24 @@ class StandardGroupChatCommands(CommandContainer):
@command @command
@doc(_("Display names of all group chat occupants")) @doc(_("Display names of all group chat occupants"))
def names(self, verbose=False): def names(self, verbose=False):
get_contact = lambda nick: gajim.contacts.get_gc_contact(self.account, self.room_jid, nick) ggc = gajim.contacts.get_gc_contact
nicks = gajim.contacts.get_nick_list(self.account, self.room_jid) gnl = gajim.contacts.get_nick_list
# First we do alpha-numeric sort and then role-based one. get_contact = lambda nick: ggc(self.account, self.room_jid, nick)
nicks.sort() get_role = lambda nick: get_contact(nick).role
nicks.sort(key=lambda nick: get_contact(nick).role) nicks = gnl(self.account, self.room_jid)
if verbose: nicks = sorted(nicks)
for nick in nicks: nicks = sorted(nicks, key=get_role)
contact = get_contact(nick)
role = helpers.get_uf_role(contact.role) if not verbose:
affiliation = helpers.get_uf_affiliation(contact.affiliation) return ", ".join(nicks)
self.echo("%s - %s - %s" % (nick, role, affiliation)) for nick in nicks:
else: contact = get_contact(nick)
return ', '.join(nicks) role = helpers.get_uf_role(contact.role)
affiliation = helpers.get_uf_affiliation(contact.affiliation)
self.echo("%s - %s - %s" % (nick, role, affiliation))
@command('ignore', raw=True) @command('ignore', raw=True)
@doc(_("Forbid an occupant to send you public or private messages")) @doc(_("Forbid an occupant to send you public or private messages"))
@ -350,4 +353,4 @@ class StandardGroupChatCommands(CommandContainer):
@command('unignore', raw=True) @command('unignore', raw=True)
@doc(_("Allow an occupant to send you public or private messages")) @doc(_("Allow an occupant to send you public or private messages"))
def unblock(self, who): def unblock(self, who):
self.on_unblock(None, who) self.on_unblock(None, who)

View File

@ -205,7 +205,7 @@ def adapt_arguments(command, arguments, args, opts):
# The second stage of transforming options to an associatable state. # The second stage of transforming options to an associatable state.
# Expanding short, one-letter options to a verbose ones, if # Expanding short, one-letter options to a verbose ones, if
# corresponding optin has been given. # corresponding optin has been given.
if command.expand_short: if command.expand:
expanded = [] expanded = []
for spec_key, spec_value in norm_kwargs.iteritems(): for spec_key, spec_value in norm_kwargs.iteritems():
letter = spec_key[0] if len(spec_key) > 1 else None letter = spec_key[0] if len(spec_key) > 1 else None

View File

@ -0,0 +1,44 @@
# Copyright (c) 2010, Alexander Cherniuk (ts33kr@gmail.com)
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from types import *
from glib import GError
def remove(sequence, target):
if isinstance(sequence, ListType):
if target in sequence:
sequence.remove(target)
elif isinstance(sequence, DictType):
if target in sequence:
del sequence[target]
def gconf(path):
try:
from gconf import client_get_default
client = client_get_default()
return client.get_string(path)
except ImportError, GError:
pass

View File

@ -271,6 +271,8 @@ def check_and_possibly_create_paths():
XTLS_CERTS = configpaths.gajimpaths['MY_PEER_CERTS'] XTLS_CERTS = configpaths.gajimpaths['MY_PEER_CERTS']
LOCAL_XTLS_CERTS = configpaths.gajimpaths['MY_CERT'] LOCAL_XTLS_CERTS = configpaths.gajimpaths['MY_CERT']
PLUGINS_CONFIG_PATH = gajim.PLUGINS_CONFIG_DIR
if not os.path.exists(MY_DATA): if not os.path.exists(MY_DATA):
create_path(MY_DATA) create_path(MY_DATA)
elif os.path.isfile(MY_DATA): elif os.path.isfile(MY_DATA):
@ -347,6 +349,18 @@ def check_and_possibly_create_paths():
jingle_xtls.make_certs(cert_name, 'gajim') jingle_xtls.make_certs(cert_name, 'gajim')
if not os.path.exists(PLUGINS_CONFIG_PATH):
create_path(PLUGINS_CONFIG_PATH)
elif os.path.isfile(PLUGINS_CONFIG_PATH):
print _('%s is a file but it should be a directory') % PLUGINS_CONFIG_PATH
print _('Gajim will now exit')
sys.exit()
def create_path(directory): def create_path(directory):
head, tail = os.path.split(directory)
if not os.path.exists(head):
create_path(head)
if os.path.exists(directory):
return
print _('creating %s directory') % directory print _('creating %s directory') % directory
os.mkdir(directory, 0700) os.mkdir(directory, 0700)

Some files were not shown because too many files have changed in this diff Show More