merge ad_hoc branch. so we now have ad hoc commands and pubsub (atom). A great thanks to Tomasz Melcer (liorithiel) and Google Summer Of Code. fixes #189

This commit is contained in:
Yann Leboulanger 2006-11-18 22:15:07 +00:00
commit 0a2cd86fce
24 changed files with 4568 additions and 139 deletions

View File

@ -113,6 +113,26 @@
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="execute_command_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes">_Execute Command...</property>
<property name="use_underline">True</property>
<child internal-child="image">
<widget class="GtkImage" id="image1246">
<property name="visible">True</property>
<property name="stock">gtk-execute</property>
<property name="icon_size">1</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="edit_account_menuitem">
<property name="visible">True</property>

View File

@ -0,0 +1,764 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkWindow" id="adhoc_commands_window">
<property name="visible">True</property>
<property name="title" translatable="yes">Ad-hoc Commands - Gajim</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<signal name="destroy" handler="on_adhoc_commands_window_destroy" last_modification_time="Thu, 22 Jun 2006 22:50:45 GMT"/>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="border_width">6</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkNotebook" id="stages_notebook">
<property name="width_request">400</property>
<property name="height_request">400</property>
<property name="visible">True</property>
<property name="show_tabs">False</property>
<property name="show_border">False</property>
<property name="tab_pos">GTK_POS_TOP</property>
<property name="scrollable">False</property>
<property name="enable_popup">False</property>
<child>
<widget class="GtkVBox" id="retrieving_commands_stage_vbox">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkLabel" id="label247">
<property name="visible">True</property>
<property name="label" translatable="yes">Please wait while retrieving command list...</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkProgressBar" id="retrieving_commands_progressbar">
<property name="visible">True</property>
<property name="orientation">GTK_PROGRESS_LEFT_TO_RIGHT</property>
<property name="fraction">0</property>
<property name="pulse_step">0.0500000007451</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label264">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkLabel" id="label265">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="command_list_stage_vbox">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkLabel" id="label253">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;Choose command to execute:&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.20000000298</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">6</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="command_list_vbox">
<property name="border_width">12</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox2957">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkButton" id="check_commands_1_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Check once more</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_check_commands_1_button_clicked" last_modification_time="Fri, 23 Jun 2006 18:05:14 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label266">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkLabel" id="label267">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="sending_form_stage_vbox">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkLabel" id="notes_label">
<property name="visible">True</property>
<property name="label" translatable="yes">Please wait while the command is sending...</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkProgressBar" id="sending_form_progressbar">
<property name="visible">True</property>
<property name="orientation">GTK_PROGRESS_LEFT_TO_RIGHT</property>
<property name="fraction">0</property>
<property name="pulse_step">0.0500000007451</property>
<property name="text" translatable="yes">Please wait...</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label268">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkLabel" id="label269">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="no_commands_stage_vbox">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkLabel" id="label263">
<property name="visible">True</property>
<property name="label" translatable="yes">This jabber entity does not expose any commands.</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkHBox" id="hbox2956">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkButton" id="check_commands_2_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label" translatable="yes">Check once more</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_check_commands_2_button_clicked" last_modification_time="Fri, 23 Jun 2006 18:05:31 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label270">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkLabel" id="label271">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="error_stage_vbox">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkLabel" id="label257">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;b&gt;An error has occured:&lt;/b&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="error_description_label">
<property name="visible">True</property>
<property name="label" translatable="yes">Error description...</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">True</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label272">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkLabel" id="label273">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</widget>
<packing>
<property name="padding">3</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox2953">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkHBox" id="hbox2958">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkButton" id="cancel_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_cancel_button_clicked" last_modification_time="Mon, 10 Jul 2006 16:24:32 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="padding">3</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="back_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-go-back</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_back_button_clicked" last_modification_time="Mon, 10 Jul 2006 16:24:50 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="forward_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-go-forward</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_forward_button_clicked" last_modification_time="Mon, 10 Jul 2006 16:24:47 GMT"/>
</widget>
<packing>
<property name="padding">3</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="execute_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-execute</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_execute_button_clicked" last_modification_time="Mon, 10 Jul 2006 16:24:42 GMT"/>
</widget>
<packing>
<property name="padding">3</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="close_button">
<property name="can_focus">True</property>
<property name="label">gtk-close</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_close_button_clicked" last_modification_time="Mon, 10 Jul 2006 16:24:38 GMT"/>
</widget>
<packing>
<property name="padding">3</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View File

@ -0,0 +1,371 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkWindow" id="atom_entry_window">
<property name="visible">True</property>
<property name="title" translatable="yes">New entry received</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<child>
<widget class="GtkVBox" id="vbox112">
<property name="border_width">6</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkLabel" id="new_entry_label">
<property name="visible">True</property>
<property name="label" translatable="yes">You have received new entry:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="hseparator14">
<property name="visible">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkTable" id="table4">
<property name="visible">True</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<property name="homogeneous">False</property>
<property name="row_spacing">0</property>
<property name="column_spacing">6</property>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">Feed name:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_RIGHT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">Entry:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="label" translatable="yes">Last modified:</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="feed_tagline_label">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;small&gt;Romeo and Juliet&lt;/small&gt;</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<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="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="last_modified_label">
<property name="visible">True</property>
<property name="label" translatable="yes">2003-12-13T18:30:02Z</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<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>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkEventBox" id="feed_title_eventbox">
<property name="visible">True</property>
<property name="visible_window">True</property>
<property name="above_child">False</property>
<signal name="button_press_event" handler="on_feed_title_eventbox_button_press_event" last_modification_time="Thu, 20 Jul 2006 21:53:07 GMT"/>
<child>
<widget class="GtkLabel" id="feed_title_label">
<property name="visible">True</property>
<property name="label" translatable="yes">Old stories</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
</child>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options">fill</property>
</packing>
</child>
<child>
<widget class="GtkEventBox" id="entry_title_eventbox">
<property name="visible">True</property>
<property name="visible_window">True</property>
<property name="above_child">False</property>
<signal name="button_press_event" handler="on_entry_title_eventbox_button_press_event" last_modification_time="Thu, 20 Jul 2006 21:53:12 GMT"/>
<child>
<widget class="GtkLabel" id="entry_title_label">
<property name="visible">True</property>
<property name="label" translatable="yes">Soliloquy</property>
<property name="use_underline">False</property>
<property name="use_markup">True</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
</child>
</widget>
<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="x_options">fill</property>
<property name="y_options">fill</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="hseparator15">
<property name="visible">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkButton" id="close_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-close</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_close_button_clicked" last_modification_time="Thu, 20 Jul 2006 21:29:17 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="next_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">Next entry</property>
<property name="use_underline">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_next_button_clicked" last_modification_time="Thu, 20 Jul 2006 21:29:21 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View File

@ -183,4 +183,626 @@
</child>
</widget>
<widget class="GtkWindow" id="data_form_old_fake_window">
<property name="visible">True</property>
<property name="title" translatable="yes">window1</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<child>
<widget class="GtkVBox" id="vbox111">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">10</property>
<child>
<widget class="GtkScrolledWindow" id="data_form_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkViewport" id="viewport2">
<property name="visible">True</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<child>
<widget class="GtkVBox" id="container_vbox">
<property name="border_width">5</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">5</property>
<child>
<widget class="GtkLabel" id="form_instructions_label">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">True</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">5</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="form_instructions_hseparator">
<property name="visible">True</property>
</widget>
<packing>
<property name="padding">5</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</widget>
</child>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkTable" id="item_list_table">
<property name="visible">True</property>
<property name="n_rows">1</property>
<property name="n_columns">2</property>
<property name="homogeneous">False</property>
<property name="row_spacing">0</property>
<property name="column_spacing">0</property>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow36">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
<property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTreeView" id="item_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">False</property>
<property name="rules_hint">False</property>
<property name="reorderable">False</property>
<property name="enable_search">True</property>
<property name="fixed_height_mode">False</property>
<property name="hover_selection">False</property>
<property name="hover_expand">False</property>
</widget>
</child>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="vbox112">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkButton" id="clear_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-clear</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="remove_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-remove</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">GTK_PACK_END</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="add_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-add</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="edit_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-edit</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options">fill</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
<widget class="GtkWindow" id="data_form_fake_window">
<property name="visible">True</property>
<property name="title" translatable="yes">window1</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<child>
<widget class="GtkVBox" id="data_form_vbox">
<property name="border_width">3</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">3</property>
<child>
<widget class="GtkLabel" id="instructions_label">
<property name="visible">True</property>
<property name="label" translatable="yes">Fill in the form.</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="instructions_hseparator">
<property name="visible">True</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkNotebook" id="data_form_types_notebook">
<property name="visible">True</property>
<property name="show_tabs">False</property>
<property name="show_border">True</property>
<property name="tab_pos">GTK_POS_TOP</property>
<property name="scrollable">False</property>
<property name="enable_popup">False</property>
<child>
<widget class="GtkScrolledWindow" id="single_form_scrolledwindow">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_NONE</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkViewport" id="single_form_viewport">
<property name="visible">True</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<child>
<placeholder/>
</child>
</widget>
</child>
</widget>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="multiple_form_hbox">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow38">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTreeView" id="records_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="headers_visible">True</property>
<property name="rules_hint">False</property>
<property name="reorderable">False</property>
<property name="enable_search">True</property>
<property name="fixed_height_mode">False</property>
<property name="hover_selection">False</property>
<property name="hover_expand">False</property>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkVBox" id="vbox114">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkButton" id="add_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-add</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_add_button_clicked" last_modification_time="Mon, 21 Aug 2006 22:32:45 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="remove_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-remove</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_remove_button_clicked" last_modification_time="Mon, 21 Aug 2006 22:32:50 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="hseparator17">
<property name="visible">True</property>
</widget>
<packing>
<property name="padding">3</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="edit_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-edit</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_edit_button_clicked" last_modification_time="Mon, 21 Aug 2006 22:32:54 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="hseparator18">
<property name="visible">True</property>
</widget>
<packing>
<property name="padding">3</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="up_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-go-up</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_up_button_clicked" last_modification_time="Mon, 21 Aug 2006 22:32:59 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="down_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-go-down</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_down_button_clicked" last_modification_time="Mon, 21 Aug 2006 22:33:03 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<widget class="GtkHSeparator" id="hseparator19">
<property name="visible">True</property>
</widget>
<packing>
<property name="padding">3</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkButton" id="clear_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-clear</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_clear_button_clicked" last_modification_time="Mon, 21 Aug 2006 22:33:07 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
<packing>
<property name="tab_expand">False</property>
<property name="tab_fill">True</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="label" translatable="yes"></property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="type">tab</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View File

@ -0,0 +1,232 @@
<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkWindow" id="groups_post_window">
<property name="visible">True</property>
<property name="title" translatable="yes">Create new post</property>
<property name="type">GTK_WINDOW_TOPLEVEL</property>
<property name="window_position">GTK_WIN_POS_NONE</property>
<property name="modal">False</property>
<property name="resizable">True</property>
<property name="destroy_with_parent">False</property>
<property name="decorated">True</property>
<property name="skip_taskbar_hint">False</property>
<property name="skip_pager_hint">False</property>
<property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<child>
<widget class="GtkVBox" id="vbox1">
<property name="border_width">6</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">4</property>
<child>
<widget class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="n_rows">2</property>
<property name="n_columns">2</property>
<property name="homogeneous">False</property>
<property name="row_spacing">0</property>
<property name="column_spacing">0</property>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">From</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">Subject</property>
<property name="use_underline">False</property>
<property name="use_markup">False</property>
<property name="justify">GTK_JUSTIFY_LEFT</property>
<property name="wrap">False</property>
<property name="selectable">False</property>
<property name="xalign">0</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
<property name="ellipsize">PANGO_ELLIPSIZE_NONE</property>
<property name="width_chars">-1</property>
<property name="single_line_mode">False</property>
<property name="angle">0</property>
</widget>
<packing>
<property name="left_attach">0</property>
<property name="right_attach">1</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="x_options">fill</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="from_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">0</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property>
</widget>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">0</property>
<property name="bottom_attach">1</property>
<property name="y_options"></property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="subject_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">0</property>
<property name="text" translatable="yes"></property>
<property name="has_frame">True</property>
<property name="invisible_char">●</property>
<property name="activates_default">False</property>
</widget>
<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="y_options"></property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkTextView" id="contents_textview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="editable">True</property>
<property name="overwrite">False</property>
<property name="accepts_tab">True</property>
<property name="justification">GTK_JUSTIFY_LEFT</property>
<property name="wrap_mode">GTK_WRAP_NONE</property>
<property name="cursor_visible">True</property>
<property name="pixels_above_lines">0</property>
<property name="pixels_below_lines">0</property>
<property name="pixels_inside_wrap">0</property>
<property name="left_margin">0</property>
<property name="right_margin">0</property>
<property name="indent">0</property>
<property name="text" translatable="yes"></property>
</widget>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkButton" id="cancel_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-cancel</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_cancel_button_clicked" last_modification_time="Fri, 18 Aug 2006 16:27:42 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<widget class="GtkButton" id="send_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="label">gtk-go-forward</property>
<property name="use_stock">True</property>
<property name="relief">GTK_RELIEF_NORMAL</property>
<property name="focus_on_click">True</property>
<signal name="clicked" handler="on_send_button_clicked" last_modification_time="Fri, 18 Aug 2006 16:27:48 GMT"/>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">False</property>
</packing>
</child>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">False</property>
<property name="fill">True</property>
</packing>
</child>
</widget>
</child>
</widget>
</glade-interface>

View File

@ -17,9 +17,7 @@
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
<property name="gravity">GDK_GRAVITY_NORTH_WEST</property>
<property name="focus_on_map">True</property>
<property name="urgency_hint">False</property>
<property name="has_separator">False</property>
<signal name="destroy" handler="on_input_dialog_destroy" last_modification_time="Sat, 18 Nov 2006 20:23:21 GMT"/>
<child internal-child="vbox">
<widget class="GtkVBox" id="dialog-vbox10">

View File

@ -157,6 +157,26 @@
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="execute_command_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes">Execute Command...</property>
<property name="use_underline">True</property>
<child internal-child="image">
<widget class="GtkImage" id="image1467">
<property name="visible">True</property>
<property name="stock">gtk-execute</property>
<property name="icon_size">1</property>
<property name="xalign">0.5</property>
<property name="yalign">0.5</property>
<property name="xpad">0</property>
<property name="ypad">0</property>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkSeparatorMenuItem" id="above_subscription_separator">
<property name="visible">True</property>

View File

@ -26,7 +26,6 @@
<signal name="focus_in_event" handler="on_roster_window_focus_in_event" last_modification_time="Sun, 04 Sep 2005 16:33:35 GMT"/>
<signal name="key_press_event" handler="on_roster_window_key_press_event" last_modification_time="Tue, 20 Sep 2005 19:26:27 GMT"/>
<signal name="focus_out_event" handler="on_roster_window_focus_out_event" last_modification_time="Tue, 08 Nov 2005 14:01:01 GMT"/>
<signal name="popup_menu" handler="on_roster_window_popup_menu" last_modification_time="Sat, 18 Nov 2006 21:12:06 GMT"/>
<child>
<widget class="GtkVBox" id="roster_vbox">

514
src/adhoc_commands.py Normal file
View File

@ -0,0 +1,514 @@
# -*- coding: utf-8 -*-
## config.py
##
## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
## Vincent Hanquez <tab@snarc.org>
## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
## Vincent Hanquez <tab@snarc.org>
## Nikos Kouremenos <nkour@jabber.org>
## Dimitur Kirov <dkirov@gmail.com>
## Travis Shirk <travis@pobox.com>
## Norman Rasmussen <norman@rasmussen.co.za>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 2 only.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
# TODO: think if we need caching command list. it may be wrong if there will
# TODO: be entities that often change the list, it may be slow to fetch it
# TODO: every time
import gobject
import gtk
from common import xmpp, gajim, dataforms
import gtkgui_helpers
import dialogs
import dataforms_widget
class CommandWindow:
'''Class for a window for single ad-hoc commands session. Note, that
there might be more than one for one account/jid pair in one moment.
TODO: maybe put this window into MessageWindow? consider this when
TODO: it will be possible to manage more than one window of one
TODO: account/jid pair in MessageWindowMgr.
TODO: gtk 2.10 has a special wizard-widget, consider using it...'''
def __init__(self, account, jid):
'''Create new window.'''
# an account object
self.account = gajim.connections[account]
self.jid = jid
self.pulse_id=None # to satisfy self.setup_pulsing()
self.commandlist=None # a list of (commandname, commanddescription)
# command's data
self.commandnode = None
self.sessionid = None
self.dataform = None
# retrieving widgets from xml
self.xml = gtkgui_helpers.get_glade('adhoc_commands_window.glade')
self.window = self.xml.get_widget('adhoc_commands_window')
for name in ('cancel_button', 'back_button', 'forward_button',
'execute_button','close_button','stages_notebook',
'retrieving_commands_stage_vbox',
'command_list_stage_vbox','command_list_vbox',
'sending_form_stage_vbox','sending_form_progressbar',
'notes_label','no_commands_stage_vbox','error_stage_vbox',
'error_description_label'):
self.__dict__[name] = self.xml.get_widget(name)
# creating data forms widget
self.data_form_widget = dataforms_widget.DataFormWidget()
self.data_form_widget.show()
self.sending_form_stage_vbox.pack_start(self.data_form_widget)
# setting initial stage
self.close_button.set_no_show_all(True)
self.stage1()
# displaying the window
self.xml.signal_autoconnect(self)
self.window.show_all()
# these functions are set up by appropriate stageX methods
def stage_finish(self, *anything): pass
def stage_cancel_button_clicked(self, *anything): assert False
def stage_back_button_clicked(self, *anything): assert False
def stage_forward_button_clicked(self, *anything): assert False
def stage_execute_button_clicked(self, *anything): assert False
def stage_close_button_clicked(self, *anything): assert False
def stage_adhoc_commands_window_delete_event(self, *anything): assert False
def do_nothing(self, *anything): return False
# widget callbacks
def on_cancel_button_clicked(self, *anything):
return self.stage_cancel_button_clicked(*anything)
def on_back_button_clicked(self, *anything):
return self.stage_back_button_clicked(*anything)
def on_forward_button_clicked(self, *anything):
return self.stage_forward_button_clicked(*anything)
def on_execute_button_clicked(self, *anything):
return self.stage_execute_button_clicked(*anything)
def on_close_button_clicked(self, *anything):
return self.stage_close_button_clicked(*anything)
def on_adhoc_commands_window_destroy(self, *anything):
# TODO: do all actions that are needed to remove this object from memory...
self.remove_pulsing()
def on_adhoc_commands_window_delete_event(self, *anything):
return self.stage_adhoc_commands_window_delete_event(self, *anything)
def __del__(self):
print "Object has been deleted."
# stage 1: waiting for command list
def stage1(self):
'''Prepare the first stage. Request command list,
set appropriate state of widgets.'''
# close old stage...
self.stage_finish()
# show the stage
self.stages_notebook.set_current_page(
self.stages_notebook.page_num(
self.retrieving_commands_stage_vbox))
# set widgets' state
self.cancel_button.set_sensitive(True)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(False)
# request command list
self.request_command_list()
self.setup_pulsing(
self.xml.get_widget('retrieving_commands_progressbar'))
# setup the callbacks
self.stage_finish = self.stage1_finish
self.stage_cancel_button_clicked = self.stage1_cancel_button_clicked
self.stage_adhoc_commands_window_delete_event = self.stage1_adhoc_commands_window_delete_event
def stage1_finish(self):
self.remove_pulsing()
def stage1_cancel_button_clicked(self, widget):
# cancelling in this stage is not critical, so we don't
# show any popups to user
self.stage1_finish()
self.window.destroy()
def stage1_adhoc_commands_window_delete_event(self, widget):
self.stage1_finish()
return True
# stage 2: choosing the command to execute
def stage2(self):
'''Populate the command list vbox with radiobuttons
(TODO: if there is more commands, maybe some kind of list?),
set widgets' state.'''
# close old stage
self.stage_finish()
assert len(self.commandlist)>0
self.stages_notebook.set_current_page(
self.stages_notebook.page_num(
self.command_list_stage_vbox))
self.cancel_button.set_sensitive(True)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(True)
self.execute_button.set_sensitive(False)
# build the commands list radiobuttons
first_radio = None
for (commandnode, commandname) in self.commandlist:
radio = gtk.RadioButton(first_radio, label=commandname)
radio.connect("toggled", self.on_command_radiobutton_toggled, commandnode)
if first_radio is None:
first_radio = radio
self.commandnode = commandnode
self.command_list_vbox.pack_start(radio, expand=False)
self.command_list_vbox.show_all()
self.stage_finish = self.stage2_finish
self.stage_cancel_button_clicked = self.stage2_cancel_button_clicked
self.stage_forward_button_clicked = self.stage2_forward_button_clicked
self.stage_adhoc_commands_window_delete_event = self.do_nothing
def stage2_finish(self):
'''Remove widgets we created. Not needed when the window is destroyed.'''
def remove_widget(widget):
self.command_list_vbox.remove(widget)
self.command_list_vbox.foreach(remove_widget)
def stage2_cancel_button_clicked(self, widget):
self.stage_finish()
self.window.destroy()
def stage2_forward_button_clicked(self, widget):
self.stage3()
def on_command_radiobutton_toggled(self, widget, commandnode):
self.commandnode = commandnode
def on_check_commands_1_button_clicked(self, widget):
self.stage1()
# stage 3: command invocation
def stage3(self):
# close old stage
self.stage_finish()
assert isinstance(self.commandnode, unicode)
self.form_status = None
self.stages_notebook.set_current_page(
self.stages_notebook.page_num(
self.sending_form_stage_vbox))
self.cancel_button.set_sensitive(True)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(False)
self.stage3_submit_form()
self.stage_finish = self.stage3_finish
self.stage_cancel_button_clicked = self.stage3_cancel_button_clicked
self.stage_back_button_clicked = self.stage3_back_button_clicked
self.stage_forward_button_clicked = self.stage3_forward_button_clicked
self.stage_execute_button_clicked = self.stage3_execute_button_clicked
self.stage_close_button_clicked = self.stage3_close_button_clicked
self.stage_adhoc_commands_window_delete_event = self.stage3_cancel_button_clicked
def stage3_finish(self):
pass
def stage3_cancel_button_clicked(self, widget, *anything):
''' We are in the middle of executing command. Ask user if he really want to cancel
the process, then... cancel it. '''
# this works also as a handler for window_delete_event, so we have to return appropriate
# values
# TODO: translate it
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?')
dialog.popup()
if dialog.get_response()==gtk.RESPONSE_YES:
self.send_cancel()
if widget==self.window:
return False
else:
self.window.destroy()
return False
return True
def stage3_close_button_clicked(self, widget):
# this works also as a handler for window_delete_event, so we have to return appropriate
# values
if widget==self.window:
return False
else:
self.window.destroy()
def stage3_back_button_clicked(self, widget):
self.stage3_submit_form('prev')
def stage3_forward_button_clicked(self, widget):
self.stage3_submit_form('next')
def stage3_execute_button_clicked(self, widget):
self.stage3_submit_form('execute')
def stage3_submit_form(self, action='execute'):
self.data_form_widget.set_sensitive(False)
if self.data_form_widget.get_data_form() is None:
self.data_form_widget.hide()
self.cancel_button.set_sensitive(True)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(False)
self.sending_form_progressbar.show()
self.setup_pulsing(self.sending_form_progressbar)
self.send_command(action)
def stage3_next_form(self, command):
assert isinstance(command, xmpp.Node)
self.remove_pulsing()
self.sending_form_progressbar.hide()
if self.sessionid is None:
self.sessionid = command.getAttr('sessionid')
self.form_status = command.getAttr('status')
if command.getTag('x') is not None:
self.dataform = dataforms.ExtendForm(node=command.getTag('x'))
self.data_form_widget.set_sensitive(True)
try:
self.data_form_widget.data_form=self.dataform
except dataforms.Error:
# TODO: translate
self.stage5(error='Service sent malformed data', senderror=True)
self.data_form_widget.show()
else:
self.data_form_widget.hide()
action = command.getTag('action')
if action is None:
self.cancel_button.set_sensitive(True)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(True)
else:
# actions, actions, actions...
self.cancel_button.set_sensitive(True)
self.back_button.set_sensitive(action.getTag('prev') is not None)
self.forward_button.set_sensitive(action.getTag('next') is not None)
self.execute_button.set_sensitive(True)
if self.form_status == 'completed':
self.cancel_button.set_sensitive(False)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False)
self.execute_button.set_no_show_all(True)
self.execute_button.hide()
self.close_button.set_no_show_all(False)
self.close_button.show()
self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked
note = command.getTag('note')
if note is not None:
self.notes_label.set_text(note.getData().decode('utf-8'))
self.notes_label.set_no_show_all(False)
self.notes_label.show()
else:
self.notes_label.set_no_show_all(True)
self.notes_label.hide()
# stage 4: no commands are exposed
def stage4(self):
'''Display the message. Wait for user to close the window'''
# close old stage
self.stage_finish()
self.stages_notebook.set_current_page(
self.stages_notebook.page_num(
self.no_commands_stage_vbox))
self.cancel_button.set_sensitive(True)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(False)
self.stage_finish = self.do_nothing
self.stage_cancel_button_clicked = self.stage4_cancel_button_clicked
self.stage_adhoc_commands_window_delete_event = self.do_nothing
def stage4_cancel_button_clicked(self, widget):
self.window.destroy()
def on_check_commands_2_button_clicked(self, widget):
self.stage1()
# stage 5: an error has occured
def stage5(self, error=None, errorid=None, senderror=False):
'''Display the error message. Wait for user to close the window'''
# TODO: sending error to responder
# close old stage
self.stage_finish()
assert errorid is not None or error is not None
if errorid is not None:
# we've got error code, display appropriate message
errorname = xmpp.NS_STANZAS + ' ' + str(errorid)
errordesc = xmpp.ERRORS[errorname][2]
error = errordesc.decode('utf-8')
del errorname, errordesc
elif error is not None:
# we've got error message
pass
else:
# we don't know what's that, bailing out
assert False
self.stages_notebook.set_current_page(
self.stages_notebook.page_num(
self.error_stage_vbox))
self.cancel_button.set_sensitive(True)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(False)
self.error_description_label.set_text(error)
self.stage_finish = self.do_nothing
self.stage_cancel_button_clicked = self.stage5_cancel_button_clicked
self.stage_adhoc_commands_window_delete_event = self.do_nothing
def stage5_cancel_button_clicked(self, widget):
self.window.destroy()
# helpers to handle pulsing in progressbar
def setup_pulsing(self, progressbar):
'''Set the progressbar to pulse. Makes a custom
function to repeatedly call progressbar.pulse() method.'''
assert self.pulse_id is None
assert isinstance(progressbar, gtk.ProgressBar)
def callback():
progressbar.pulse()
return True # important to keep callback be called back!
# 12 times per second (80 miliseconds)
self.pulse_id = gobject.timeout_add(80, callback)
def remove_pulsing(self):
'''Stop pulsing, useful when especially when removing widget.'''
if self.pulse_id is not None:
gobject.source_remove(self.pulse_id)
self.pulse_id=None
# handling xml stanzas
def request_command_list(self):
'''Request the command list. Change stage on delivery.'''
query = xmpp.Iq(typ='get', to=xmpp.JID(self.jid), xmlns=xmpp.NS_DISCO_ITEMS)
query.setQuerynode(xmpp.NS_COMMANDS)
def callback(response):
'''Called on response to query.'''
# TODO: move to connection_handlers.py
# is error => error stage
error = response.getError()
if error is not None:
# extracting error description from xmpp/protocol.py
self.stage5(errorid = error)
return
# no commands => no commands stage
# commands => command selection stage
items = response.getTag('query').getTags('item')
if len(items)==0:
self.commandlist = []
self.stage4()
else:
self.commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items]
self.stage2()
self.account.connection.SendAndCallForResponse(query, callback)
def send_command(self, action='execute'):
'''Send the command with data form. Wait for reply.'''
# create the stanza
assert isinstance(self.commandnode, unicode)
assert action in ('execute', 'prev', 'next', 'complete')
stanza = xmpp.Iq(typ='set', to=self.jid)
cmdnode = stanza.addChild('command', attrs={
'xmlns':xmpp.NS_COMMANDS,
'node':self.commandnode,
'action':action
})
if self.sessionid is not None:
cmdnode.setAttr('sessionid', self.sessionid)
if self.data_form_widget.data_form is not None:
# cmdnode.addChild(node=dataforms.DataForm(tofill=self.data_form_widget.data_form))
# TODO: simplified form to send
cmdnode.addChild(node=self.data_form_widget.data_form)
def callback(response):
# TODO: move to connection_handlers.py
err = response.getError()
if err is not None:
self.stage5(errorid = err)
else:
self.stage3_next_form(response.getTag('command'))
self.account.connection.SendAndCallForResponse(stanza, callback)
def send_cancel(self):
'''Send the command with action='cancel'. '''
assert self.commandnode is not None
if self.sessionid is not None:
# we already have sessionid, so the service sent at least one reply.
stanza = xmpp.Iq(typ='set', to=self.jid)
stanza.addChild('command', attrs={
'xmlns':xmpp.NS_COMMANDS,
'node':self.commandnode,
'sessionid':self.sessionid,
'action':'cancel'
})
self.account.connection.send(stanza)
else:
# we did not received any reply from service; TODO: we should wait and
# then send cancel; for now we do nothing
pass

113
src/atom_window.py Normal file
View File

@ -0,0 +1,113 @@
'''atom_window.py - a window to display atom entries from pubsub. For now greatly simplified,
supports only simple feeds like the one from pubsub.com. '''
import gtk
import gtk.gdk
import gtkgui_helpers
from common import helpers
class AtomWindow:
window = None
entries = []
@classmethod # python2.4 decorator
def newAtomEntry(cls, entry):
''' Queue new entry, open window if there's no one opened. '''
cls.entries.append(entry)
if cls.window is None:
cls.window = AtomWindow()
else:
cls.window.updateCounter()
def __init__(self):
''' Create new window... only if we have anything to show. '''
assert len(self.__class__.entries)>0
self.entry = None # the entry actually displayed
self.xml = gtkgui_helpers.get_glade('atom_entry_window.glade')
self.window = self.xml.get_widget('atom_entry_window')
for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox',
'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox',
'last_modified_label', 'close_button', 'next_button'):
self.__dict__[name] = self.xml.get_widget(name)
self.displayNextEntry()
self.xml.signal_autoconnect(self)
self.window.show_all()
self.entry_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self.feed_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK)
def displayNextEntry(self):
''' Get next entry from the queue and display it in the window. '''
assert len(self.__class__.entries)>0
newentry = self.__class__.entries.pop(0)
# fill the fields
if newentry.feed_link is not None:
self.feed_title_label.set_markup(
u'<span foreground="blue" underline="single">%s</span>' % \
gtkgui_helpers.escape_for_pango_markup(newentry.feed_title))
else:
self.feed_title_label.set_markup(
gtkgui_helpers.escape_for_pango_markup(newentry.feed_title))
self.feed_tagline_label.set_markup(
u'<small>%s</small>' % \
gtkgui_helpers.escape_for_pango_markup(newentry.feed_tagline))
if newentry.uri is not None:
self.entry_title_label.set_markup(
u'<span foreground="blue" underline="single">%s</span>' % \
gtkgui_helpers.escape_for_pango_markup(newentry.title))
else:
self.entry_title_label.set_markup(
gtkgui_helpers.escape_for_pango_markup(newentry.title))
self.last_modified_label.set_text(newentry.updated)
# update the counters
self.updateCounter()
self.entry = newentry
def updateCounter(self):
''' We display number of events on the top of window, sometimes it needs to be
changed...'''
count = len(self.__class__.entries)
# TODO: translate
if count>0:
self.new_entry_label.set_text( \
'You have received new entries (and %(count)d not displayed):' % \
{'count': count})
self.next_button.set_sensitive(True)
else:
self.new_entry_label.set_text('You have received new entry:')
self.next_button.set_sensitive(False)
def on_close_button_clicked(self, widget):
self.window.destroy()
def on_next_button_clicked(self, widget):
self.displayNextEntry()
def on_entry_title_button_press_event(self, widget, event):
# TODO: make it using special gtk2.10 widget
if event.button == 1: # left click
uri = self.entry.uri
if uri is not None:
helpers.launch_browser_mailer('url', uri)
return True
def on_feed_title_button_press_event(self, widget, event):
# TODO: make it using special gtk2.10 widget
if event.button == 1: # left click
uri = self.entry.feed_uri
if uri is not None:
helpers.launch_browser_mailer('url', uri)
return True

138
src/common/atom.py Normal file
View File

@ -0,0 +1,138 @@
# -*- coding: utf-8 -*-
''' Atom (rfc 4287) feed parser, used to read data from atom-over-pubsub transports
and services. Very simple. Actually implements only atom:entry. Implement more features
if you need. '''
# suggestion: rewrite functions that return dates to return standard python time tuples,
# exteneded to contain timezone
import xmpp
import time
class PersonConstruct(xmpp.Node, object):
''' Not used for now, as we don't need authors/contributors in pubsub.com feeds.
They rarely exist there. '''
def __init__(self, node):
''' Create person construct from node. '''
xmpp.Node.__init__(self, node=node)
def get_name(self):
return self.getTagData('name')
name = property(get_name, None, None,
'''Conveys a human-readable name for the person. Should not be None,
although some badly generated atom feeds don't put anything here
(this is non-standard behavior, still pubsub.com sometimes does that.)''')
def get_uri(self):
return self.getTagData('uri')
uri = property(get_uri, None, None,
'''Conveys an IRI associated with the person. Might be None when not set.''')
def get_email(self):
return self.getTagData('email')
email = property(get_email, None, None,
'''Conveys an e-mail address associated with the person. Might be None when
not set.''')
class Entry(xmpp.Node, object):
def __init__(self, node=None):
''' Create new atom entry object. '''
xmpp.Node.__init__(self, 'entry', node=node)
def __repr__(self):
return '<Atom:Entry object of id="%r">' % self.id
class OldEntry(xmpp.Node, object):
''' Parser for feeds from pubsub.com. They use old Atom 0.3 format with
their extensions. '''
def __init__(self, node=None):
''' Create new Atom 0.3 entry object. '''
xmpp.Node.__init__(self, 'entry', node=node)
def __repr__(self):
return '<Atom0.3:Entry object of id="%r">' % self.id
def get_feed_title(self):
''' Returns title of feed, where the entry was created. The result is the feed name
concatenated with source-feed title. '''
title = u''
if self.parent is not None:
main_feed = self.parent.getTagData('title')
else:
main_feed = None
if self.getTag('source-feed') is not None:
source_feed = self.getTag('source-feed').getTagData('title')
else:
source_feed = None
if main_feed is not None and source_feed is not None:
return u'%s: %s' % (main_feed, source_feed)
elif main_feed is not None:
return main_feed
elif source_feed is not None:
return source_feed
else:
return u''
feed_title = property(get_feed_title, None, None,
''' Title of feed. It is built from entry's original feed title and title of feed
which delivered this entry. ''')
def get_feed_link(self):
''' Get a link to main page of feed (in pubsub.com: second link of rel='alternate',
first contains raw xml data). '''
try:
return self.getTag('source-feed').getTags('link', {'rel':'alternate'})[1].getData()
except:
return None
feed_link = property(get_feed_link, None, None,
''' Link to main webpage of the feed. ''')
def get_title(self):
''' Get an entry's title. '''
return self.getTagData('title')
title = property(get_title, None, None,
''' Entry's title. ''')
def get_uri(self):
''' Get the uri the entry points to (entry's first link element with rel='alternate'
or without rel attribute). '''
for element in self.getTags('link'):
if element.attrs.has_key('rel') and element.attrs['rel']<>'alternate': continue
try:
return element.attrs['href']
except AttributeError:
pass
return None
uri = property(get_uri, None, None,
''' URI that is pointed by the entry. ''')
def get_updated(self):
''' Get the time the entry was updated last time. This should be standarized,
but pubsub.com sends it in human-readable format. We won't try to parse it.
(Atom 0.3 uses the word «modified» for that).
If there's no time given in the entry, we try with <published>
and <issued> elements. '''
for name in ('updated', 'modified', 'published', 'issued'):
date = self.getTagData(name)
if date is not None: break
if date is None:
# it is not in the standard format
return time.asctime()
return date
updated = property(get_updated, None, None,
''' Last significant modification time. ''')
feed_tagline = u''

252
src/common/commands.py Normal file
View File

@ -0,0 +1,252 @@
##
## Copyright (C) 2006 Gajim Team
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 2 only.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
import xmpp
import helpers
import dataforms
import gajim
class AdHocCommand:
commandnode = 'command'
commandname = 'The Command'
commandfeatures = (xmpp.NS_DATA,)
@staticmethod
def isVisibleFor(samejid):
''' This returns True if that command should be visible and invokable
for others.
samejid - True when command is invoked by an entity with the same bare jid.
'''
return True
def __init__(self, conn, jid, sessionid):
self.connection = conn
self.jid = jid
self.sessionid = sessionid
def buildResponse(self, request, status='executing', defaultaction=None, actions=None):
assert status in ('executing', 'completed', 'canceled')
response = request.buildReply('result')
cmd = response.addChild('command', {
'xmlns': xmpp.NS_COMMANDS,
'sessionid': self.sessionid,
'node': self.commandnode,
'status': status})
if defaultaction is not None or actions is not None:
if defaultaction is not None:
assert defaultaction in ('cancel', 'execute', 'prev', 'next', 'complete')
attrs = {'action': defaultaction}
else:
attrs = {}
cmd.addChild('actions', attrs, actions)
return response, cmd
def badRequest(self, stanza):
self.connection.connection.send(xmpp.Error(stanza, xmpp.NS_STANZAS+' bad-request'))
def cancel(self, request):
response, cmd = self.buildResponse(request, status='canceled')
self.connection.connection.send(response)
return False # finish the session
class ChangeStatusCommand(AdHocCommand):
commandnode = 'change-status'
commandname = 'Change status information'
@staticmethod
def isVisibleFor(samejid):
''' Change status is visible only if the entity has the same bare jid. '''
return True # TODO: Remove that!
return samejid
def execute(self, request):
# first query...
response, cmd = self.buildResponse(request, defaultaction='execute', actions=['execute'])
cmd.addChild(node=dataforms.SimpleDataForm(
title='Change status',
instructions='Set the presence type and description',
fields=[
dataforms.Field('list-single',
var='presence-type',
label='Type of presence:',
options=[
(u'free-for-chat', u'Free for chat'),
(u'online', u'Online'),
(u'away', u'Away'),
(u'xa', u'Extended away'),
(u'dnd', u'Do not disturb'),
(u'offline', u'Offline - disconnect')],
value='online',
required=True),
dataforms.Field('text-multi',
var='presence-desc',
label='Presence description:')]))
self.connection.connection.send(response)
# for next invocation
self.execute = self.changestatus
return True # keep the session
def changestatus(self, request):
# check if the data is correct
try:
form=dataforms.SimpleDataForm(extend=request.getTag('command').getTag('x'))
except:
self.badRequest(request)
return False
try:
presencetype = form['presence-type'].value
if not presencetype in \
('free-for-chat', 'online', 'away', 'xa', 'dnd', 'offline'):
self.badRequest(request)
return False
except: # KeyError if there's no presence-type field in form or
# AttributeError if that field is of wrong type
self.badRequest(request)
return False
try:
presencedesc = form['presence-desc'].value
except: # same exceptions as in last comment
presencedesc = u''
response, cmd = self.buildResponse(request, status='completed')
cmd.addChild('note', {}, 'The status has been changed.')
self.connection.connection.send(response)
# send new status
gajim.interface.roster.send_status(self.connection.name, presencetype, presencedesc)
return False # finish the session
class ConnectionCommands:
''' This class depends on that it is a part of Connection() class. '''
def __init__(self):
# a list of all commands exposed: node -> command class
self.__commands = {}
for cmdobj in (ChangeStatusCommand,):
self.__commands[cmdobj.commandnode] = cmdobj
# a list of sessions; keys are tuples (jid, sessionid, node)
self.__sessions = {}
def getOurBareJID(self):
return gajim.get_jid_from_account(self.name)
def isSameJID(self, jid):
''' Tests if the bare jid given is the same as our bare jid. '''
return xmpp.JID(jid).getStripped() == self.getOurBareJID()
def commandListQuery(self, con, iq_obj):
iq = iq_obj.buildReply('result')
jid = helpers.get_full_jid_from_iq(iq_obj)
q = iq.getTag('query')
for node, cmd in self.__commands.iteritems():
if cmd.isVisibleFor(self.isSameJID(jid)):
q.addChild('item', {
# TODO: find the jid
'jid': self.getOurBareJID()+u'/'+self.server_resource,
'node': node,
'name': cmd.commandname})
self.connection.send(iq)
def commandQuery(self, con, iq_obj):
''' Send disco result for query for command (JEP-0050, example 6.).
Return True if the result was sent, False if not. '''
jid = helpers.get_full_jid_from_iq(iq_obj)
node = iq_obj.getTagAttr('query', 'node')
if node not in self.__commands: return False
cmd = self.__commands[node]
if cmd.isVisibleFor(self.isSameJID(jid)):
iq = iq_obj.buildReply('result')
q = iq.getTag('query')
q.addChild('identity', attrs = {'type': 'command-node',
'category': 'automation',
'name': cmd.commandname})
q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS})
for feature in cmd.commandfeatures:
q.addChild('feature', attrs = {'var': feature})
self.connection.send(iq)
return True
return False
def _CommandExecuteCB(self, con, iq_obj):
jid = helpers.get_full_jid_from_iq(iq_obj)
cmd = iq_obj.getTag('command')
if cmd is None: return
node = cmd.getAttr('node')
if node is None: return
sessionid = cmd.getAttr('sessionid')
if sessionid is None:
# we start a new command session... only if we are visible for the jid
newcmd = self.__commands[node]
if not newcmd.isVisibleFor(self.isSameJID(jid)):
return
# generate new sessionid
sessionid = self.connection.getAnID()
# create new instance and run it
obj = newcmd(conn=self, jid=jid, sessionid=sessionid)
rc = obj.execute(iq_obj)
if rc:
self.__sessions[(jid, sessionid, node)] = obj
raise xmpp.NodeProcessed
else:
# the command is already running, check for it
magictuple = (jid, sessionid, node)
if magictuple not in self.__sessions:
# we don't have this session... ha!
return
action = cmd.getAttr('action')
obj = self.__sessions[magictuple]
try:
if action == 'cancel': rc = obj.cancel(iq_obj)
elif action == 'prev': rc = obj.prev(iq_obj)
elif action == 'next': rc = obj.next(iq_obj)
elif action == 'execute' or action is None:
rc = obj.execute(iq_obj)
elif action == 'complete': rc = obj.complete(iq_obj)
else:
# action is wrong. stop the session, send error
raise AttributeError
except AttributeError:
# the command probably doesn't handle invoked action...
# stop the session, return error
del self.__sessions[magictuple]
return
# delete the session if rc is False
if not rc:
del self.__sessions[magictuple]
raise xmpp.NodeProcessed

View File

@ -33,6 +33,10 @@ import common.xmpp
from common import GnuPG
from common import helpers
from common import gajim
from common import dataforms
from common import atom
from common.commands import ConnectionCommands
from common.pubsub import ConnectionPubSub
STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd',
'invisible']
@ -661,6 +665,10 @@ class ConnectionDisco:
feature = common.xmpp.Node('feature')
feature.setAttr('var', common.xmpp.NS_FILE)
query.addChild(node=feature)
# exposing adhoc commands
feature = common.xmpp.Node('feature')
feature.setAttr('var', common.xmpp.NS_COMMANDS)
query.addChild(node=feature)
self.connection.send(iq)
raise common.xmpp.NodeProcessed
@ -705,20 +713,34 @@ class ConnectionDisco:
else:
self.dispatch('AGENT_INFO_ITEMS', (jid, node, items))
def _DiscoverItemsGetCB(self, con, iq_obj):
gajim.log.debug('DiscoverItemsGetCB')
node = iq_obj.getTagAttr('query', 'node')
if node==common.xmpp.NS_COMMANDS:
self.commandListQuery(con, iq_obj)
raise common.xmpp.NodeProcessed
def _DiscoverInfoGetCB(self, con, iq_obj):
gajim.log.debug('DiscoverInfoGetCB')
iq = iq_obj.buildReply('result')
q = iq.getTag('query')
q.addChild('identity', attrs = {'type': 'pc',
'category': 'client',
'name': 'Gajim'})
q.addChild('feature', attrs = {'var': common.xmpp.NS_BYTESTREAM})
q.addChild('feature', attrs = {'var': common.xmpp.NS_SI})
q.addChild('feature', attrs = {'var': common.xmpp.NS_FILE})
q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC})
q.addChild('feature', attrs = {'var': common.xmpp.NS_XHTML_IM})
self.connection.send(iq)
raise common.xmpp.NodeProcessed
q = iq_obj.getTag('query')
node = q.getAttr('node')
if self.commandQuery(con, iq_obj):
raise NodeProcessed
elif node is None:
iq = iq_obj.buildReply('result')
q = iq.getTag('query')
q.addChild('identity', attrs = {'type': 'pc',
'category': 'client',
'name': 'Gajim'})
q.addChild('feature', attrs = {'var': common.xmpp.NS_BYTESTREAM})
q.addChild('feature', attrs = {'var': common.xmpp.NS_SI})
q.addChild('feature', attrs = {'var': common.xmpp.NS_FILE})
q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC})
q.addChild('feature', attrs = {'var': common.xmpp.NS_XHTML_IM})
self.connection.send(iq)
raise common.xmpp.NodeProcessed
def _DiscoverInfoErrorCB(self, con, iq_obj):
gajim.log.debug('DiscoverInfoErrorCB')
@ -1103,11 +1125,12 @@ class ConnectionVcard:
else:
self.dispatch('VCARD', vcard)
class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco):
class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub):
def __init__(self):
ConnectionVcard.__init__(self)
ConnectionBytestream.__init__(self)
ConnectionCommands.__init__(self)
ConnectionPubSub.__init__(self)
# List of IDs we are waiting answers for {id: (type_of_request, data), }
self.awaiting_answers = {}
# List of IDs that will produce a timeout is answer doesn't arrive
@ -1333,6 +1356,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
def _messageCB(self, con, msg):
'''Called when we receive a message'''
# check if the message is pubsub#event
if msg.getTag('event') is not None:
self._pubsubEventCB(con, msg)
return
msgtxt = msg.getBody()
msghtml = msg.getXHTML()
mtype = msg.getType()
@ -1449,6 +1476,32 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
subject, chatstate, msg_id, composing_jep, user_nick, msghtml))
# END messageCB
def _pubsubEventCB(self, con, msg):
''' Called when we receive <message/> with pubsub event. '''
# TODO: Logging? (actually services where logging would be useful, should
# TODO: allow to access archives remotely...)
event = msg.getTag('event')
items = event.getTag('items')
if items is None: return
for item in items.getTags('item'):
# check for event type (for now only one type supported: pubsub.com events)
child = item.getTag('pubsub-message')
if child is not None:
# we have pubsub.com notification
child = child.getTag('feed')
if child is None: continue
for entry in child.getTags('entry'):
# for each entry in feed (there shouldn't be more than one,
# but to be sure...
self.dispatch('ATOM_ENTRY', (atom.OldEntry(node=entry),))
continue
# unknown type... probably user has another client who understands that event
raise common.xmpp.NodeProcessed
def _presenceCB(self, con, prs):
'''Called when we receive a presence'''
ptype = prs.getType()
@ -1918,12 +1971,17 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco)
common.xmpp.NS_PRIVATE)
con.RegisterHandler('iq', self._HttpAuthCB, 'get',
common.xmpp.NS_HTTP_AUTH)
con.RegisterHandler('iq', self._CommandExecuteCB, 'set',
common.xmpp.NS_COMMANDS)
con.RegisterHandler('iq', self._gMailNewMailCB, 'set',
common.xmpp.NS_GMAILNOTIFY)
con.RegisterHandler('iq', self._gMailQueryCB, 'result',
common.xmpp.NS_GMAILNOTIFY)
con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get',
common.xmpp.NS_DISCO_INFO)
con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
common.xmpp.NS_DISCO_ITEMS)
con.RegisterHandler('iq', self._PubSubCB, 'result')
con.RegisterHandler('iq', self._ErrorCB, 'error')
con.RegisterHandler('iq', self._IqCB)
con.RegisterHandler('iq', self._StanzaArrivedCB)

424
src/common/dataforms.py Normal file
View File

@ -0,0 +1,424 @@
# this will go to src/common/xmpp later, for now it is in src/common
""" This module contains wrappers for different parts of data forms (JEP 0004). For information
how to use them, read documentation. """
import xmpp
# exceptions used in this module
class Error(Exception): pass # base class
class UnknownDataForm(Error): pass # when we get xmpp.Node which we do not understand
class WrongFieldValue(Error): pass # when we get xmpp.Node which contains bad fields
# helper class to change class of already existing object
class ExtendedNode(xmpp.Node, object):
@classmethod
def __new__(cls, *a, **b):
if 'extend' not in b.keys():
return object.__new__(cls)
extend = b['extend']
assert issubclass(cls, extend.__class__)
extend.__class__ = cls
return extend
# helper decorator to create properties in cleaner way
def nested_property(f):
ret = f()
p = {'doc': f.__doc__}
for v in ('fget', 'fset', 'fdel', 'doc'):
if v in ret.keys(): p[v]=ret[v]
return property(**p)
# helper to create fields from scratch
def Field(typ, **attrs):
''' Helper function to create a field of given type. '''
f = {
'boolean': BooleanField,
'fixed': StringField,
'hidden': StringField,
'text-private': StringField,
'text-single': StringField,
'jid-multi': ListMultiField,
'jid-single': ListSingleField,
'list-multi': ListMultiField,
'list-single': ListSingleField,
'text-multi': TextMultiField,
}[typ](typ=typ, **attrs)
return f
def ExtendField(node):
''' Helper function to extend a node to field of appropriate type. '''
# TODO: move the dict out
typ=node.getAttr('type')
f = {
'boolean': BooleanField,
'fixed': StringField,
'hidden': StringField,
'text-private': StringField,
'text-single': StringField,
'jid-multi': ListMultiField,
'jid-single': ListSingleField,
'list-multi': ListMultiField,
'list-single': ListSingleField,
'text-multi': TextMultiField,
}[typ](extend=node)
return f
def ExtendForm(node):
''' Helper function to extend a node to form of appropriate type. '''
if node.getTag('recorded') is not None:
return MultipleDataForm(extend=node)
else:
return SimpleDataForm(extend=node)
class DataField(ExtendedNode):
""" Keeps data about one field - var, field type, labels, instructions... """
def __init__(self, typ=None, var=None, value=None, label=None, desc=None, required=False,
options=None, extend=None):
if extend is None:
ExtendedNode.__init__(self, 'field')
self.type = typ
self.var = var
if value is not None: self.value = value
if label is not None: self.label = label
if desc is not None: self.desc = desc
self.required = required
self.options = options
@nested_property
def type():
'''Type of field. Recognized values are: 'boolean', 'fixed', 'hidden', 'jid-multi',
'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private',
'text-single'. If you set this to something different, DataField will store
given name, but treat all data as text-single.'''
def fget(self):
t = self.getAttr('type')
if t is None: return 'text-single'
return t
def fset(self, value):
assert isinstance(value, basestring)
self.setAttr('type', value)
return locals()
@nested_property
def var():
'''Field identifier.'''
def fget(self):
return self.getAttr('var')
def fset(self, value):
assert isinstance(value, basestring)
self.setAttr('var', value)
def fdel(self):
self.delAttr('var')
return locals()
@nested_property
def label():
'''Human-readable field name.'''
def fget(self):
return self.getAttr('label')
def fset(self, value):
assert isinstance(value, basestring)
self.setAttr('label', value)
def fdel(self):
self.delAttr('label')
return locals()
@nested_property
def description():
'''Human-readable description of field meaning.'''
def fget(self):
return self.getTagData('desc') or u''
def fset(self, value):
assert isinstance(value, basestring)
if value == '':
fdel(self)
else:
self.setTagData('desc', value)
def fdel(self):
t = self.getTag('desc')
if t is not None:
self.delChild(t)
return locals()
@nested_property
def required():
'''Controls whether this field required to fill. Boolean.'''
def fget(self):
return boolean(self.getTag('required'))
def fset(self, value):
t = self.getTag('required')
if t and not value:
self.delChild(t)
elif not t and value:
self.addChild('required')
return locals()
class BooleanField(DataField):
@nested_property
def value():
'''Value of field. May contain True, False or None.'''
def fget(self):
v = self.getTagData('value')
if v in ('0', 'false'): return False
if v in ('1', 'true'): return True
if v is None: return None
raise WrongFieldValue
def fset(self, value):
self.setTagData('value', value and '1' or '0')
def fdel(self, value):
t = self.getTag('value')
if t is not None:
self.delChild(t)
return locals()
class StringField(DataField):
''' Covers fields of types: fixed, hidden, text-private, text-single. '''
@nested_property
def value():
'''Value of field. May be any unicode string.'''
def fget(self):
return self.getTagData('value') or u''
def fset(self, value):
assert isinstance(value, basestring)
if value == '':
return fdel(self)
self.setTagData('value', value)
def fdel(self):
try:
self.delChild(self.getTag('value'))
except ValueError: # if there already were no value tag
pass
return locals()
class ListField(DataField):
''' Covers fields of types: jid-multi, jid-single, list-multi, list-single. '''
@nested_property
def options():
'''Options.'''
def fget(self):
options = []
for element in self.getTags('option'):
v = element.getTagData('value')
if v is None: raise WrongFieldValue
options.append((element.getAttr('label'), v))
return options
def fset(self, values):
fdel(self)
for value, label in values:
self.addChild('option', {'label': label}).setTagData('value', value)
def fdel(self):
for element in self.getTags('option'):
self.delChild(element)
return locals()
def iter_options(self):
for element in self.getTags('option'): # TODO: iter!
v = element.getTagData('value')
if v is None: raise WrongFieldValue
yield (v, element.getAttr('label'))
class ListSingleField(ListField, StringField):
'''Covers list-single and jid-single fields.'''
pass
class ListMultiField(ListField):
'''Covers list-multi and jid-multi fields.'''
@nested_property
def values():
'''Values held in field.'''
def fget(self):
values = []
for element in self.getTags('value'):
values.append(element.getData())
return values
def fset(self, values):
fdel(self)
for value in values:
self.addChild('value').setData(value)
def fdel(self):
for element in self.getTags('value'):
self.delChild(element)
return locals()
def iter_values():
for element in self.getTags('value'):
yield element.getData()
class TextMultiField(DataField):
@nested_property
def value():
'''Value held in field.'''
def fget(self):
value = u''
for element in self.getTags('value'): # TODO: iter!
value += '\n' + element.getData()
return value[1:]
def fset(self, value):
fdel(self)
if value == '': return
for line in value.split('\n'):
self.addChild('value').setData(line)
def fdel(self):
for element in self.getTags('value'):
self.delChild(element)
return locals()
class DataRecord(ExtendedNode):
'''The container for data fields - an xml element which has DataField
elements as children.'''
def __init__(self, fields=None, associated=None, extend=None):
self.associated = associated
self.vars = {}
if extend is None:
# we have to build this object from scratch
xmpp.Node.__init__(self)
if fields is not None: self.fields = fields
else:
# we already have xmpp.Node inside - try to convert all
# fields into DataField objects
if fields is None:
for field in self.getTags('field'): # TODO: iter!
if not isinstance(field, DataField):
ExtendField(field)
self.vars[field.var] = field
else:
for field in self.getTags('field'):
self.delChild(field)
self.fields = fields
@nested_property
def fields():
'''List of fields in this record.'''
def fget(self):
return self.getTags('field')
def fset(self, fields):
fdel(self)
for field in fields:
if not isinstance(field, DataField):
ExtendField(extend=field)
self.addChild(node=field)
def fdel(self):
for element in self.getTags('field'):
self.delChild(element)
return locals()
def iter_fields(self):
''' Iterate over fields in this record. Do not take associated
into account. '''
for field in self.getTags('field'): # TODO: iter!
yield field
def iter_with_associated(self):
''' Iterate over associated, yielding both our field and
associated one together. '''
for field in self.associated.iter_fields():
yield self[field.var], field
def __getitem__(self, item):
return self.vars[item]
class DataForm(ExtendedNode):
def __init__(self, type=None, title=None, instructions=None, extend=None):
if extend is None:
# we have to build form from scratch
xmpp.Node.__init__(self, 'x', attrs={'xmlns': xmpp.NS_DATA})
if type is not None: self.type=type
if title is not None: self.title=title
if instructions is not None: self.instructions=instructions
@nested_property
def type():
''' Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'.
'form' - this form is to be filled in; you will be able soon to do:
filledform = DataForm(replyto=thisform)...'''
def fget(self):
return self.getAttr('type')
def fset(self):
assert type in ('form', 'submit', 'cancel', 'result')
self.setAttr('type', type)
return locals()
@nested_property
def title():
''' Title of the form. Human-readable, should not contain any \\r\\n.'''
def fget(self):
return self.getTagData('title')
def fset(self, title):
self.setTagData('title', title)
def fdel(self):
try:
self.delChild('title')
except ValueError:
pass
return locals()
@nested_property
def instructions():
''' Instructions for this form. Human-readable, may contain \\r\\n. '''
# TODO: the same code is in TextMultiField. join them
def fget(self):
value = u''
for value in self.getTags('value'):
value += '\n' + value.getData()
return value[1:]
def fset(self, value):
fdel(self)
if value == '': return
for line in value.split('\n'):
self.addChild('value').setData(line)
def fdel(self):
for value in self.getTags('value'):
self.delChild(value)
return locals()
class SimpleDataForm(DataForm, DataRecord):
def __init__(self, type=None, title=None, instructions=None, fields=None, extend=None):
DataForm.__init__(self, type=type, title=title, instructions=instructions, extend=extend)
DataRecord.__init__(self, fields=fields, extend=self, associated=self)
class MultipleDataForm(DataForm):
def __init__(self):
# all records, recorded into DataRecords
pass
@nested_property
def items():
''' A list of all records. '''
def fget(self):
return list(self.iter_records())
def fset(self, records):
fdel(self)
for record in records:
if not isinstance(record, DataRecord):
DataRecord(extend=record)
self.addChild(node=record)
def fdel(self):
for record in self.getTags('record'):
self.delChild(record)
return locals()
def iter_records():
for record in self.getTags('item'):
yield item
@nested_property
def recorded():
''' DataRecord that contains descriptions of fields in records.'''
def fget(self):
return self.getTag('recorded')
def fset(self, record):
try:
self.delChild('recorded')
except:
pass
record.setName('recorded')
self.addChild(node=record)
return locals()

51
src/common/pubsub.py Normal file
View File

@ -0,0 +1,51 @@
import xmpp
import gajim
class ConnectionPubSub:
def __init__(self):
self.__callbacks={}
def send_pb_subscription_query(self, jid, cb, *args, **kwargs):
query = xmpp.Iq('get', to=jid)
pb = query.addChild('pubsub', {'xmlns': xmpp.NS_PUBSUB})
pb.addChild('subscriptions')
id = self.connection.send(query)
self.__callbacks[id]=(cb, args, kwargs)
def send_pb_subscribe(self, jid, node, cb, *args, **kwargs):
our_jid = gajim.get_jid_from_account(self.name)
query = xmpp.Iq('set', to=jid)
pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
pb.addChild('subscribe', {'node': node, 'jid': our_jid})
id = self.connection.send(query)
self.__callbacks[id]=(cb, args, kwargs)
def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs):
our_jid = gajim.get_jid_from_account(self.name)
query = xmpp.Iq('set', to=jid)
pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
pb.addChild('unsubscribe', {'node': node, 'jid': our_jid})
id = self.connection.send(query)
self.__callbacks[id]=(cb, args, kwargs)
def send_pb_publish(self, jid, node, item, id):
'''Publish item to a node.'''
query = xmpp.Iq('set', to=jid)
e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB)
e = e.addChild('publish', {'node': node})
e = e.addChild('item', {'id': id}, [item]) # TODO: we should generate id... or we shouldn't?
self.connection.send(query)
def _PubSubCB(self, conn, stanza):
try:
cb, args, kwargs = self.__callbacks.pop(stanza.getID())
cb(conn, stanza, *args, **kwargs)
except KeyError:
pass

View File

@ -33,7 +33,7 @@ def ustr(what):
if type(r)<>type(u''): return unicode(r,ENCODING)
return r
class Node:
class Node(object):
""" Node class describes syntax of separate XML Node. It have a constructor that permits node creation
from set of "namespace name", attributes and payload of text strings and other nodes.
It does not natively support building node from text string and uses NodeBuilder class for that purpose.
@ -58,9 +58,9 @@ class Node:
"node" and other arguments is provided then the node initially created as replica of "node"
provided and then modified to be compliant with other arguments."""
if node:
if self.FORCE_NODE_RECREATION and type(node)==type(self):
if self.FORCE_NODE_RECREATION and isinstance(node, Node):
node=str(node)
if type(node)<>type(self):
if not isinstance(node, Node):
node=NodeBuilder(node,self)
else:
self.name,self.namespace,self.attrs,self.data,self.kids,self.parent = node.name,node.namespace,{},[],[],node.parent
@ -74,9 +74,9 @@ class Node:
if self.parent and not self.namespace: self.namespace=self.parent.namespace
for attr in attrs.keys():
self.attrs[attr]=attrs[attr]
if type(payload) in (type(''),type(u'')): payload=[payload]
if isinstance(payload, basestring): payload=[payload]
for i in payload:
if type(i)==type(self): self.addChild(node=i)
if isinstance(i, Node): self.addChild(node=i)
else: self.data.append(ustr(i))
def __str__(self,fancy=0):
@ -130,7 +130,7 @@ class Node:
def delChild(self, node, attrs={}):
""" Deletes the "node" from the node's childs list, if "node" is an instance.
Else deletes the first node that have specified name and (optionally) attributes. """
if type(node)<>type(self): node=self.getTag(node,attrs)
if not isinstance(node, Node): node=self.getTag(node,attrs)
self.kids.remove(node)
return node
def getAttrs(self):
@ -191,6 +191,18 @@ class Node:
else: nodes.append(node)
if one and nodes: return nodes[0]
if not one: return nodes
def iterTags(self, name, attrs={}, namespace=None):
""" Iterate over all children using specified arguments as filter. """
for node in self.kids:
if namespace is not None and namespace!=node.getNamespace(): continue
if node.getName() == name:
for key in attrs.keys():
if not node.attrs.has_key(key) or \
node.attrs[key]!=attrs[key]: break
else:
yield node
def setAttr(self, key, val):
""" Sets attribute "key" with the value "val". """
self.attrs[key]=val

View File

@ -2068,7 +2068,7 @@ class DataFormWindow:
def __init__(self, account, config):
self.account = account
self.config = config
self.xml = gtkgui_helpers.get_glade('data_form_window.glade')
self.xml = gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_window')
self.window = self.xml.get_widget('data_form_window')
self.window.set_transient_for(gajim.interface.roster.window)
self.config_vbox = self.xml.get_widget('config_vbox')

491
src/dataforms_widget.py Normal file
View File

@ -0,0 +1,491 @@
## dataforms.py
##
## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
## Copyright (C) 2005-2006 Nikos Kouremenos <nkour@jabber.org>
## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
## Copyright (C) 2003-2005 Vincent Hanquez <tab@snarc.org>
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 2 only.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
""" This module contains widget that can display data form (JEP-0004).
Words single and multiple refers here to types of data forms:
single means these with one record of data (without <recorded/> element),
multiple - these which may contain more data (with <recorded/> element)."""
# TODO: forms of type='result' should be read-only
# TODO: remove tabs from dialog
import gtk
import pango
import gtkgui_helpers
import common.xmpp as xmpp
import common.dataforms as dataforms
import itertools
class DataFormWidget(gtk.Alignment, object):
# "public" interface
""" Data Form widget. Use like any other widget. """
def __init__(self, dataformnode=None):
""" Create a widget. """
gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0)
self._data_form = None
self.xml=gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_vbox')
self.xml.signal_autoconnect(self)
for name in ('instructions_label', 'instructions_hseparator',
'single_form_viewport', 'data_form_types_notebook',
'single_form_scrolledwindow', 'multiple_form_hbox',
'records_treeview', 'add_button', 'remove_button',
'edit_button', 'up_button', 'down_button', 'clear_button'):
self.__dict__[name] = self.xml.get_widget(name)
self.add(self.xml.get_widget('data_form_vbox'))
if dataformnode is not None:
self.set_data_form(dataformnode)
selection = self.records_treeview.get_selection()
selection.connect('changed', self.on_records_selection_changed)
selection.set_mode(gtk.SELECTION_MULTIPLE)
def set_data_form(self, dataform):
""" Set the data form (xmpp.DataForm) displayed in widget. """
assert isinstance(dataform, dataforms.DataForm)
self.del_data_form()
self._data_form = dataform
if isinstance(dataform, dataforms.SimpleDataForm):
self.build_single_data_form()
else:
self.build_multiple_data_form()
# create appropriate description for instructions field if there isn't any
if dataform.instructions=='':
if dataform.type=='result':
# form is single
instructions = _('This is result of query.')
else:
# form is writable (TODO: move that to build_*_data_form()?
if isinstance(dataform, dataforms.SimpleDataForm):
instructions = _('Fill in the form.')
else:
instructions = _('Edit items on the list')
else:
instructions = dataform.instructions
self.instructions_label.set_text(instructions)
def get_data_form(self):
""" Data form displayed in the widget or None if no form. """
return self._data_form
def del_data_form(self):
self.clean_data_form()
self._data_form = None
data_form = property(get_data_form, set_data_form, del_data_form,
"Data form presented in a widget")
def get_title(self):
""" Get the title of data form, as a unicode object. If no
title or no form, returns u''. Useful for setting window title. """
if self._data_form is not None:
if self._data_form.title is not None:
return self._data_form.title
return u''
title = property(get_title, None, None, "Data form title")
def show(self):
""" Treat 'us' as one widget. """
self.show_all()
# "private" methods
# we have actually two different kinds of data forms: one is a simple form to fill,
# second is a table with several records;
def clean_data_form(self):
'''Remove data about existing form. This metod is empty, because
it is rewritten by build_*_data_form, according to type of form
which is actually displayed.'''
pass
def build_single_data_form(self):
'''Invoked when new single form is to be created.'''
assert isinstance(self._data_form, dataforms.SimpleDataForm)
self.clean_data_form()
self.singleform = SingleForm(self._data_form)
self.singleform.show()
self.single_form_viewport.add(self.singleform)
self.data_form_types_notebook.set_current_page(
self.data_form_types_notebook.page_num(
self.single_form_scrolledwindow))
self.clean_data_form = self.clean_single_data_form
def clean_single_data_form(self):
'''(Called as clean_data_form, read the docs of clean_data_form()).
Remove form from widget.'''
self.singleform.destroy()
del self.singleform
def build_multiple_data_form(self):
'''Invoked when new multiple form is to be created.'''
assert isinstance(self._data_form, dataforms.MultipleDataForm)
self.clean_data_form()
# creating model for form...
fieldtypes = []
for field in self._data_form.recorded.iter_fields():
# note: we store also text-private and hidden fields,
# we just do not display them.
# TODO: boolean fields
#elif field.type=='boolean': fieldtypes.append(bool)
fieldtypes.append(str)
self.multiplemodel = gtk.ListStore(*fieldtypes)
# moving all data to model
for item in self._data_form.iter_records():
# TODO: probably wrong... (.value[s]?, fields not in the same order?)
# not checking multiple-item forms...
self.multiplemodel.append([field.value for field in item.iter_fields()])
# constructing columns...
for field, counter in zip(self._data_form.iter_fields(), itertools.count()):
print repr(field), repr(counter)
self.records_treeview.append_column(
gtk.TreeViewColumn(field.label, gtk.CellRendererText(),
text=counter))
self.records_treeview.set_model(self.multiplemodel)
self.records_treeview.show_all()
self.data_form_types_notebook.set_current_page(
self.data_form_types_notebook.page_num(
self.multiple_form_hbox))
self.clean_data_form = self.clean_multiple_data_form
# refresh list look
self.refresh_multiple_buttons()
def clean_multiple_data_form(self):
'''(Called as clean_data_form, read the docs of clean_data_form()).
Remove form from widget.'''
del self.multiplemodel
def refresh_multiple_buttons(self):
''' Checks for treeview state and makes control buttons sensitive.'''
selection = self.records_treeview.get_selection()
model = self.records_treeview.get_model()
count = selection.count_selected_rows()
if count==0:
self.remove_button.set_sensitive(False)
self.edit_button.set_sensitive(False)
self.up_button.set_sensitive(False)
self.down_button.set_sensitive(False)
elif count==1:
self.remove_button.set_sensitive(True)
self.edit_button.set_sensitive(True)
_, (path,) = selection.get_selected_rows()
iter = model.get_iter(path)
if model.iter_next(iter) is None:
self.up_button.set_sensitive(True)
self.down_button.set_sensitive(False)
elif path==(0,):
self.up_button.set_sensitive(False)
self.down_button.set_sensitive(True)
else:
self.up_button.set_sensitive(True)
self.down_button.set_sensitive(True)
else:
self.remove_button.set_sensitive(True)
self.edit_button.set_sensitive(True)
self.up_button.set_sensitive(False)
self.down_button.set_sensitive(False)
if len(model)==0:
self.clear_button.set_sensitive(False)
else:
self.clear_button.set_sensitive(True)
def on_clear_button_clicked(self, widget):
self.records_treeview.get_model().clear()
def on_remove_button_clicked(self, widget):
selection = self.records_treeview.get_selection()
model, rowrefs = selection.get_selected_rows() # rowref is a list of paths
for i in xrange(len(rowrefs)):
rowrefs[i] = gtk.TreeRowReference(model, rowrefs[i])
# rowref is a list of row references; need to convert because we will modify the model,
# paths would change
for rowref in rowrefs:
del model[rowref.get_path()]
def on_up_button_clicked(self, widget):
selection = self.records_treeview.get_selection()
model, (path,) = selection.get_selected_rows()
iter = model.get_iter(path)
previter = model.get_iter((path[0]-1,)) # constructing path for previous iter
model.swap(iter, previter)
self.refresh_multiple_buttons()
def on_down_button_clicked(self, widget):
selection = self.records_treeview.get_selection()
model, (path,) = selection.get_selected_rows()
iter = model.get_iter(path)
nextiter = model.iter_next(iter)
model.swap(iter, nextiter)
self.refresh_multiple_buttons()
def on_records_selection_changed(self, widget):
self.refresh_multiple_buttons()
class SingleForm(gtk.Table, object):
""" Widget that represent DATAFORM_SINGLE mode form. Because this is used
not only to display single forms, but to form input windows of multiple-type
forms, it is in another class."""
def __init__(self, dataform):
assert isinstance(dataform, dataforms.SimpleDataForm)
gtk.Table.__init__(self)
self.set_col_spacings(6)
self.set_row_spacings(6)
self._data_form = dataform
# building widget
linecounter = 0
# for each field...
for field in self._data_form.iter_fields():
if field.type=='hidden': continue
commonlabel = True
commondesc = True
commonwidget = True
widget = None
if field.type=='boolean':
widget = gtk.CheckButton()
widget.connect('toggled', self.on_boolean_checkbutton_toggled, field)
widget.set_active(field.value)
elif field.type=='fixed':
leftattach = 1
rightattach = 2
if field.label is None:
commonlabel = False
leftattach = 0
if field.description is None:
commondesc = False
rightattach = 3
commonwidget=False
widget = gtk.Label(field.value)
widget.set_line_wrap(True)
self.attach(widget, leftattach, rightattach, linecounter, linecounter+1,
xoptions=gtk.FILL, yoptions=gtk.FILL)
elif field.type == 'list-single':
# TODO: When more than few choices, make a list
# TODO: Think of moving that to another function (it could be used
# TODO: in stage2 of adhoc commands too).
# TODO: What if we have radio buttons and non-required field?
# TODO: We cannot deactivate them all...
widget = gtk.VBox()
first_radio = None
for value, label in field.iter_options():
radio = gtk.RadioButton(first_radio, label=label)
radio.connect('toggled', self.on_list_single_radiobutton_toggled,
field, value)
if first_radio is None:
first_radio = radio
if field.value=='': # TODO: is None when done
field.value = value
if value == field.value:
radio.set_active(True)
widget.pack_start(radio, expand=False)
elif field.type == 'list-multi':
# TODO: When more than few choices, make a list
widget = gtk.VBox()
for value, label in field.iter_options():
check = gtk.CheckButton(label, use_underline=False)
check.set_active(value in field.values)
check.connect('toggled', self.on_list_multi_checkbutton_toggled,
field, value)
widget.pack_start(check, expand=False)
elif field.type == 'jid-single':
widget = gtk.Entry()
widget.connect('changed', self.on_text_single_entry_changed, field)
widget.set_text(field.value)
elif field.type == 'jid-multi':
commonwidget = False
xml = gtkgui_helpers.get_glade('data_form_window.glade', 'item_list_table')
widget = xml.get_widget('item_list_table')
treeview = xml.get_widget('item_treeview')
listmodel = gtk.ListStore(str)
for value in field.iter_values():
# nobody will create several megabytes long stanza
listmodel.insert(999999, (value,))
treeview.set_model(listmodel)
renderer = gtk.CellRendererText()
renderer.set_property('editable', True)
renderer.connect('edited',
self.on_jid_multi_cellrenderertext_edited, listmodel, field)
treeview.append_column(gtk.TreeViewColumn(None, renderer,
text=0))
xml.get_widget('add_button').connect('clicked',
self.on_jid_multi_add_button_clicked, treeview, listmodel, field)
xml.get_widget('edit_button').connect('clicked',
self.on_jid_multi_edit_button_clicked, treeview)
xml.get_widget('remove_button').connect('clicked',
self.on_jid_multi_remove_button_clicked, treeview, field)
xml.get_widget('clear_button').connect('clicked',
self.on_jid_multi_clean_button_clicked, listmodel, field)
self.attach(widget, 1, 2, linecounter, linecounter+1)
del xml
elif field.type == 'text-private':
widget = gtk.Entry()
widget.connect('changed', self.on_text_single_entry_changed, field)
widget.set_visibility(False)
widget.set_text(field.value)
elif field.type == 'text-multi':
# TODO: bigger text view
commonwidget = False
textwidget = gtk.TextView()
textwidget.set_wrap_mode(gtk.WRAP_WORD)
textwidget.get_buffer().connect('changed', self.on_text_multi_textbuffer_changed,
field)
textwidget.get_buffer().set_text(field.value)
widget = gtk.ScrolledWindow()
widget.add(textwidget)
self.attach(widget, 1, 2, linecounter, linecounter+1)
else:# field.type == 'text-single' or field.type is nonstandard:
# JEP says that if we don't understand some type, we
# should handle it as text-single
widget = gtk.Entry()
widget.connect('changed', self.on_text_single_entry_changed, field)
if field.value is None:
field.value = u''
widget.set_text(field.value)
if commonlabel and field.label is not None:
label = gtk.Label(field.label)
label.set_alignment(1.0, 0.5)
self.attach(label, 0, 1, linecounter, linecounter+1,
xoptions=gtk.FILL, yoptions=gtk.FILL)
if commonwidget:
assert widget is not None
self.attach(widget, 1, 2, linecounter, linecounter+1,
yoptions=gtk.FILL)
widget.show_all()
if commondesc and field.description!='':
label = gtk.Label()
label.set_markup('<small>'+\
gtkgui_helpers.escape_for_pango_markup(field.description)+\
'</small>')
label.set_line_wrap(True)
self.attach(label, 2, 3, linecounter, linecounter+1,
xoptions=gtk.FILL|gtk.SHRINK, yoptions=gtk.FILL|gtk.SHRINK)
linecounter+=1
if self.get_property('visible'):
self.show_all()
def show(self):
# simulate that we are one widget
self.show_all()
def on_boolean_checkbutton_toggled(self, widget, field):
field.value = widget.get_active()
def on_list_single_radiobutton_toggled(self, widget, field, value):
field.value = value
def on_list_multi_checkbutton_toggled(self, widget, field, value):
# TODO: make some methods like add_value and remove_value
if widget.get_active() and value not in field.values:
field.values += [value]
elif not widget.get_active() and value in field.values:
field.values = [v for v in field.values if v!=value]
def on_text_single_entry_changed(self, widget, field):
field.value = widget.get_text()
def on_text_multi_textbuffer_changed(self, widget, field):
field.value = widget.get_text(
widget.get_start_iter(),
widget.get_end_iter())
def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, model, field):
old=model[path][0]
model[path][0]=newtext
values = field.values
values[values.index(old)]=newtext
field.values = values
def on_jid_multi_add_button_clicked(self, widget, treeview, model, field):
iter = model.insert(999999, ("new@jabber.id",))
treeview.set_cursor(model.get_path(iter), treeview.get_column(0), True)
field.values = field.values + ["new@jabber.id"]
def on_jid_multi_edit_button_clicked(self, widget, treeview):
model, iter = treeview.get_selection().get_selected()
assert iter is not None
treeview.set_cursor(model.get_path(iter), treeview.get_column(0), True)
def on_jid_multi_remove_button_clicked(self, widget, treeview, field):
selection = treeview.get_selection()
model = treeview.get_model()
deleted = []
def remove(model, path, iter, deleted):
deleted+=model[iter]
model.remove(iter)
selection.selected_foreach(remove, deleted)
field.values = (v for v in field.values if v not in deleted)
def on_jid_multi_clean_button_clicked(self, widget, model, field):
model.clear()
del field.values

View File

@ -1002,7 +1002,7 @@ class FTOverwriteConfirmationDialog(ConfirmationDialog):
class InputDialog:
'''Class for Input dialog'''
def __init__(self, title, label_str, input_str = None, is_modal = True,
ok_handler = None, cancel_handler = None):
ok_handler = None):
# if modal is True you also need to call get_response()
# and ok_handler won't be used
self.xml = gtkgui_helpers.get_glade('input_dialog.glade')
@ -1011,7 +1011,6 @@ class InputDialog:
self.input_entry = self.xml.get_widget('input_entry')
self.dialog.set_title(title)
label.set_markup(label_str)
self.cancel_handler = cancel_handler
if input_str:
self.input_entry.set_text(input_str)
self.input_entry.select_region(0, -1) # select all
@ -1023,22 +1022,14 @@ class InputDialog:
okbutton.connect('clicked', self.on_okbutton_clicked)
cancelbutton = self.xml.get_widget('cancelbutton')
cancelbutton.connect('clicked', self.on_cancelbutton_clicked)
self.xml.signal_autoconnect(self)
self.dialog.show_all()
def on_input_dialog_destroy(self, widget):
if self.cancel_handler:
self.cancel_handler()
def on_okbutton_clicked(self, widget):
def on_okbutton_clicked(self, widget):
user_input = self.input_entry.get_text().decode('utf-8')
self.dialog.destroy()
if isinstance(self.ok_handler, tuple):
self.ok_handler[0](user_input, *self.ok_handler[1:])
else:
self.ok_handler(user_input)
self.ok_handler(user_input)
def on_cancelbutton_clicked(self, widget):
def on_cancelbutton_clicked(self, widget):
self.dialog.destroy()
def get_response(self):

View File

@ -46,6 +46,7 @@ import pango
import dialogs
import tooltips
import gtkgui_helpers
import groups
from common import gajim
from common import xmpp
@ -73,6 +74,7 @@ def _gen_agent_type_info():
('gateway', 'sip'): (False, 'sip.png'),
('directory', 'user'): (None, 'jud.png'),
('pubsub', 'generic'): (None, 'pubsub.png'),
('pubsub', 'service'): (PubSubBrowser, 'pubsub.png'),
('proxy', 'bytestreams'): (None, 'bytestreams.png'), # Socks5 FT proxy
# Transports
@ -216,6 +218,7 @@ class ServicesCache:
self.account = account
self._items = CacheDictionary(0, getrefresh = False)
self._info = CacheDictionary(0, getrefresh = False)
self._subscriptions = CacheDictionary(5, getrefresh=False)
self._cbs = {}
def cleanup(self):
@ -580,7 +583,7 @@ _('Without a connection, you can not browse available services'))
self.connect_style_event(opts[0], opts[1])
def destroy(self, chain = False):
'''Close the browser. This can optionally close it's children and
'''Close the browser. This can optionally close its children and
propagate to the parent. This should happen on actions like register,
or join to kill off the entire browser chain.'''
if self.dying:
@ -604,7 +607,8 @@ _('Without a connection, you can not browse available services'))
child.destroy(chain = chain)
self.children.remove(child)
if self.parent:
self.parent.children.remove(self)
if self in self.parent.children:
self.parent.children.remove(self)
if chain and not self.parent.children:
self.parent.destroy(chain = chain)
self.parent = None
@ -1662,6 +1666,214 @@ class MucBrowser(AgentBrowser):
# switch to alternate query mode
self.cache.get_items(jid, node, self._channel_altinfo)
def PubSubBrowser(account, jid, node):
''' Returns an AgentBrowser subclass that will display service discovery
for particular pubsub service. Different pubsub services may need to
present different data during browsing. '''
# for now, only discussion groups are supported...
# TODO: check if it has appropriate features to be such kind of service
return DiscussionGroupsBrowser(account, jid, node)
class DiscussionGroupsBrowser(AgentBrowser):
''' For browsing pubsub-based discussion groups service. '''
def __init__(self, account, jid, node):
AgentBrowser.__init__(self, account, jid, node)
# this will become set object when we get subscriptions; None means
# we don't know yet which groups are subscribed
self.subscriptions = None
# this will become our action widgets when we create them; None means
# we don't have them yet (needed for check in callback)
self.subscribe_button = None
self.unsubscribe_button = None
gajim.connections[account].send_pb_subscription_query(jid, self._subscriptionsCB)
def _create_treemodel(self):
''' Create treemodel for the window. '''
# JID, node, name (with description) - pango markup, dont have info?, subscribed?
model = gtk.ListStore(str, str, str, bool, bool)
model.set_sort_column_id(3, gtk.SORT_ASCENDING)
self.window.services_treeview.set_model(model)
# Name column
# Pango markup for name and description, description printed with
# <small/> font
renderer = gtk.CellRendererText()
col = gtk.TreeViewColumn(_('Name'))
col.pack_start(renderer)
col.set_attributes(renderer, markup=2)
col.set_resizable(True)
self.window.services_treeview.insert_column(col, -1)
# Subscription state
renderer = gtk.CellRendererToggle()
col = gtk.TreeViewColumn(_('Subscribed'))
col.pack_start(renderer)
col.set_attributes(renderer, inconsistent=3, active=4)
col.set_resizable(False)
self.window.services_treeview.insert_column(col, -1)
self.window.services_treeview.set_headers_visible(True)
def _add_item(self, model, jid, node, item, force):
''' Called when we got basic information about new node from query.
Show the item. '''
name = item.get('name', '')
if self.subscriptions is not None:
dunno = False
subscribed = name in self.subscriptions
else:
dunno = True
subscribed = False
name = gtkgui_helpers.escape_for_pango_markup(name)
name = '<b>%s</b>' % name
model.append((jid, node, name, dunno, subscribed))
def _add_actions(self):
self.post_button = gtk.Button(label=_('New post'), use_underline=True)
self.post_button.set_sensitive(False)
self.post_button.connect('clicked', self.on_post_button_clicked)
self.window.action_buttonbox.add(self.post_button)
self.post_button.show_all()
self.subscribe_button = gtk.Button(label=_('_Subscribe'), use_underline=True)
self.subscribe_button.set_sensitive(False)
self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked)
self.window.action_buttonbox.add(self.subscribe_button)
self.subscribe_button.show_all()
self.unsubscribe_button = gtk.Button(label=_('_Unsubscribe'), use_underline=True)
self.unsubscribe_button.set_sensitive(False)
self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked)
self.window.action_buttonbox.add(self.unsubscribe_button)
self.unsubscribe_button.show_all()
def _clean_actions(self):
if self.post_button is not None:
self.post_button.destroy()
self.post_button = None
if self.subscribe_button is not None:
self.subscribe_button.destroy()
self.subscribe_button = None
if self.unsubscribe_button is not None:
self.unsubscribe_button.destroy()
self.unsubscribe_button = None
def update_actions(self):
'''Called when user selected a row. Make subscribe/unsubscribe buttons
sensitive appropriatelly.'''
# we have nothing to do if we don't have buttons...
if self.subscribe_button is None: return
model, iter = self.window.services_treeview.get_selection().get_selected()
if not iter or self.subscriptions is None:
# no item selected or no subscriptions info, all buttons are insensitive
self.post_button.set_sensitive(False)
self.subscribe_button.set_sensitive(False)
self.unsubscribe_button.set_sensitive(False)
else:
subscribed = model.get_value(iter, 4) # 4 = subscribed?
self.post_button.set_sensitive(subscribed)
self.subscribe_button.set_sensitive(not subscribed)
self.unsubscribe_button.set_sensitive(subscribed)
def on_post_button_clicked(self, widget):
'''Called when 'post' button is pressed. Open window to create post'''
model, iter = self.window.services_treeview.get_selection().get_selected()
if iter is None: return
groupnode = model.get_value(iter, 1) # 1 = groupnode
groups.GroupsPostWindow(self.account, self.jid, groupnode)
def on_subscribe_button_clicked(self, widget):
'''Called when 'subscribe' button is pressed. Send subscribtion request.'''
model, iter = self.window.services_treeview.get_selection().get_selected()
if iter is None: return
groupnode = model.get_value(iter, 1) # 1 = groupnode
gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._subscribeCB, groupnode)
def on_unsubscribe_button_clicked(self, widget):
'''Called when 'unsubscribe' button is pressed. Send unsubscription request.'''
model, iter = self.window.services_treeview.get_selection().get_selected()
if iter is None: return
groupnode = model.get_value(iter, 1) # 1 = groupnode
gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._unsubscribeCB, groupnode)
def _subscriptionsCB(self, conn, request):
''' We got the subscribed groups list stanza. Now, if we already
have items on the list, we should actualize them. '''
print 0
try:
subscriptions = request.getTag('pubsub').getTag('subscriptions')
except:
return
print 1
groups = set()
for child in subscriptions.getTags('subscription'):
print 2, repr(child), str(child)
groups.add(child['node'])
print 3, groups
self.subscriptions = groups
# try to setup existing items in model
model = self.window.services_treeview.get_model()
print 4
for row in model:
print 5
# 1 = group node
# 3 = insensitive checkbox for subscribed
# 4 = subscribed?
groupnode = row[1]
row[3]=False
row[4]=groupnode in groups
print 6
# we now know subscriptions, update button states
self.update_actions()
raise xmpp.NodeProcessed
def _subscribeCB(self, conn, request, groupnode):
'''We have just subscribed to a node. Update UI'''
self.subscriptions.add(groupnode)
model = self.window.services_treeview.get_model()
for row in model:
if row[1] == groupnode: # 1 = groupnode
row[4]=True
break
self.update_actions()
raise xmpp.NodeProcessed
def _unsubscribeCB(self, conn, request, groupnode):
'''We have just unsubscribed from a node. Update UI'''
self.subscriptions.remove(groupnode)
model = self.window.services_treeview.get_model()
for row in model:
if row[1] == groupnode: # 1 = groupnode
row[4]=False
break
self.update_actions()
raise xmpp.NodeProcessed
# Fill the global agent type info dictionary
_agent_type_info = _gen_agent_type_info()

View File

@ -26,6 +26,7 @@ from common import i18n
import message_control
from chat_control import ChatControlBase
from atom_window import AtomWindow
from common import exceptions
from common.zeroconf import connection_zeroconf
@ -1405,6 +1406,10 @@ class Interface:
def handle_event_metacontacts(self, account, tags_list):
gajim.contacts.define_metacontacts(account, tags_list)
def handle_atom_entry(self, account, data):
atom_entry, = data
AtomWindow.newAtomEntry(atom_entry)
def handle_event_privacy_lists_received(self, account, data):
# ('PRIVACY_LISTS_RECEIVED', account, list)
if not self.instances.has_key(account):
@ -1749,6 +1754,7 @@ class Interface:
'ASK_NEW_NICK': self.handle_event_ask_new_nick,
'SIGNED_IN': self.handle_event_signed_in,
'METACONTACTS': self.handle_event_metacontacts,
'ATOM_ENTRY': self.handle_atom_entry,
'PRIVACY_LISTS_RECEIVED': self.handle_event_privacy_lists_received,
'PRIVACY_LIST_RECEIVED': self.handle_event_privacy_list_received,
'PRIVACY_LISTS_ACTIVE_DEFAULT': \
@ -1963,10 +1969,9 @@ class Interface:
self.show_vcard_when_connect = []
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'gajim.png')
path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps/gajim.png')
pix = gtk.gdk.pixbuf_new_from_file(path_to_file)
# set the icon to all newly opened windows
gtk.window_set_default_icon(pix)
gtk.window_set_default_icon(pix) # set the icon to all newly opened windows
self.roster.window.set_icon_from_file(path_to_file) # and to roster window
self.sleeper = common.sleepy.Sleepy(
gajim.config.get('autoawaytime') * 60, # make minutes to seconds

47
src/groups.py Normal file
View File

@ -0,0 +1,47 @@
'''Window to create new post for discussion groups service.'''
import gtk
from common import gajim, xmpp
import gtkgui_helpers
class GroupsPostWindow:
def __init__(self, account, servicejid, groupid):
'''Open new 'create post' window to create message for groupid on servicejid service.'''
assert isinstance(servicejid, basestring)
assert isinstance(groupid, basestring)
self.account = account
self.servicejid = servicejid
self.groupid = groupid
self.xml = gtkgui_helpers.get_glade('groups_post_window.glade')
self.window = self.xml.get_widget('groups_post_window')
for name in ('from_entry', 'subject_entry', 'contents_textview'):
self.__dict__[name] = self.xml.get_widget(name)
self.xml.signal_autoconnect(self)
self.window.show_all()
def on_cancel_button_clicked(self, w):
'''Close window.'''
self.window.destroy()
def on_send_button_clicked(self, w):
'''Gather info from widgets and send it as a message.'''
# constructing item to publish... that's atom:entry element
item = xmpp.Node('entry', {'xmlns':'http://www.w3.org/2005/Atom'})
author = item.addChild('author')
author.addChild('name', {}, [self.from_entry.get_text()])
item.addChild('generator', {}, ['Gajim'])
item.addChild('title', {}, [self.subject_entry.get_text()])
item.addChild('id', {}, ['0'])
buffer = self.contents_textview.get_buffer()
item.addChild('content', {}, [buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())])
# publish it to node
gajim.connections[self.account].send_pb_publish(self.servicejid, self.groupid, item, '0')
# close the window
self.window.destroy()

View File

@ -658,9 +658,11 @@ def get_state_image_from_file_path_show(file_path, show):
def get_possible_button_event(event):
'''mouse or keyboard caused the event?'''
if event.type == gtk.gdk.KEY_PRESS:
return 0 # no event.button so pass 0
# BUTTON_PRESS event, so pass event.button
return event.button
event_button = 0 # no event.button so pass 0
else: # BUTTON_PRESS event, so pass event.button
event_button = event.button
return event_button
def destroy_widget(widget):
widget.destroy()

View File

@ -31,6 +31,7 @@ import gtkgui_helpers
import cell_renderer_image
import tooltips
import message_control
import adhoc_commands
import notify
from common import gajim
@ -54,8 +55,9 @@ C_NAME, # cellrenderer text that holds contact nickame
C_TYPE, # account, group or contact?
C_JID, # the jid of the row
C_ACCOUNT, # cellrenderer text that holds account name
C_EDITABLE, # cellrenderer text that holds name editable or not?
C_SECPIXBUF, # secondary_pixbuf (holds avatar or padlock)
) = range(6)
) = range(7)
class RosterWindow:
'''Class for main window of the GTK+ interface'''
@ -164,7 +166,7 @@ class RosterWindow:
if self.regroup:
show = helpers.get_global_show()
model.append(None, [self.jabber_state_images['16'][show],
_('Merged accounts'), 'account', '', 'all', None])
_('Merged accounts'), 'account', '', 'all', False, None])
self.draw_account(account)
return
@ -179,7 +181,7 @@ class RosterWindow:
model.append(None, [self.jabber_state_images['16'][show],
gtkgui_helpers.escape_for_pango_markup(account),
'account', our_jid, account, tls_pixbuf])
'account', our_jid, account, False, tls_pixbuf])
def draw_account(self, account):
model = self.tree.get_model()
@ -312,7 +314,8 @@ class RosterWindow:
name = contact.get_shown_name()
for i in parent_iters:
# we add some values here. see draw_contact for more
model.append(i, (None, name, 'contact', jid, account, None))
model.append(i, (None, name, 'contact', jid, account,
False, None))
self.draw_contact(jid, account)
self.draw_avatar(jid, account)
# Redraw parent to change icon
@ -345,7 +348,7 @@ class RosterWindow:
iterG = model.append(IterAcct, [
self.jabber_state_images['16']['closed'],
gtkgui_helpers.escape_for_pango_markup(group), 'group',
group, account, None])
group, account, False, None])
self.draw_group(group, account)
if model.iter_n_children(IterAcct) == 1: # We added the first one
self.draw_account(account)
@ -364,7 +367,8 @@ class RosterWindow:
name = contact.get_shown_name()
# we add some values here. see draw_contact for more
model.append(iterG, (None, name, typestr, contact.jid, account, None))
model.append(iterG, (None, name, typestr, contact.jid, account,
False, None))
if gajim.groups[account][group]['expand']:
self.tree.expand_row(model.get_path(iterG), False)
@ -434,7 +438,7 @@ class RosterWindow:
model = self.tree.get_model()
iterAcct = self.get_account_iter(account)
model.append(iterAcct, (None, gajim.nicks[account], 'self_contact', jid,
account, None))
account, False, None))
self.draw_contact(jid, account)
self.draw_avatar(jid, account)
@ -1394,9 +1398,12 @@ class RosterWindow:
def on_rename(self, widget, iter, path):
# this function is called either by F2 or by Rename menuitem
if gajim.interface.instances.has_key('rename'):
gajim.interface.instances['rename'].dialog.window.present()
return
# to display that menuitem we show a menu, that does focus-out
# we then select Rename and focus-in
# focus-in callback checks on this var and if is NOT None
# it redraws the selected contact resulting in stopping our rename
# procedure. So set this to None to stop that
self._last_selected_contact = []
model = self.tree.get_model()
row_type = model[iter][C_TYPE]
@ -1407,66 +1414,16 @@ class RosterWindow:
return
if row_type in ('contact', 'agent'):
# it's jid
title = _('Rename Contact')
message = _('Enter a new nickname for contact %s') % jid
old_text = gajim.contacts.get_contact_with_highest_priority(account,
jid).name
# Remove possible resource indicator (Name (2))
contact = gajim.contacts.get_first_contact_from_jid(account, jid)
name = contact.name
model[iter][C_NAME] = gtkgui_helpers.escape_for_pango_markup(name)
elif row_type == 'group':
if jid in helpers.special_groups + (_('General'),):
return
old_text = model[iter][C_JID].decode('utf-8')
title = _('Rename Group')
message = _('Enter a new name for group %s') % old_text
def on_renamed(new_text, account, row_type, jid, old_text):
if gajim.interface.instances.has_key('rename'):
del gajim.interface.instances['rename']
if row_type in ('contact', 'agent'):
if old_text == new_text:
return
for u in gajim.contacts.get_contact(account, jid):
u.name = new_text
gajim.connections[account].update_contact(jid, new_text, u.groups)
self.draw_contact(jid, account)
# Update opened chat
ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
if ctrl:
ctrl.update_ui()
win = gajim.interface.msg_win_mgr.get_window(jid, account)
win.redraw_tab(ctrl)
win.show_title()
elif row_type == 'group':
# in C_JID column, we hold the group name (which is not escaped)
if old_text == new_text:
return
# Groups may not change name from or to a special groups
for g in helpers.special_groups:
if g in (new_text, old_text):
return
# get all contacts in that group
for jid in gajim.contacts.get_jid_list(account):
contact = gajim.contacts.get_contact_with_highest_priority(
account, jid)
if old_text in contact.groups:
# set them in the new one and remove it from the old
contact.groups.remove(old_text)
self.remove_contact(contact, account)
if new_text not in contact.groups:
contact.groups.append(new_text)
self.add_contact_to_roster(contact.jid, account)
gajim.connections[account].update_contact(contact.jid,
contact.name, contact.groups)
# If last removed iter was not visible, gajim.groups is not cleaned
if gajim.groups[account].has_key(old_text):
del gajim.groups[account][old_text]
def on_canceled():
if gajim.interface.instances.has_key('rename'):
del gajim.interface.instances['rename']
gajim.interface.instances['rename'] = dialogs.InputDialog(title, message,
old_text, False, (on_renamed, account, row_type, jid, old_text),
on_canceled)
model[iter][C_EDITABLE] = True # set 'editable' to True
self.tree.set_cursor(path, self.tree.get_column(0), True)
def on_remove_group_item_activated(self, widget, group, account):
dlg = dialogs.ConfirmationDialogCheck(_('Remove Group'),
@ -1690,6 +1647,8 @@ class RosterWindow:
'assign_openpgp_key_menuitem')
add_special_notification_menuitem = xml.get_widget(
'add_special_notification_menuitem')
execute_command_menuitem = xml.get_widget(
'execute_command_menuitem')
add_special_notification_menuitem.hide()
add_special_notification_menuitem.set_no_show_all(True)
@ -1721,25 +1680,41 @@ class RosterWindow:
contacts = gajim.contacts.get_contact(account, jid)
if len(contacts) > 1: # sevral resources
sub_menu = gtk.Menu()
start_chat_menuitem.set_submenu(sub_menu)
def resources_submenu(action):
""" Build a submenu with contact's resources. """
sub_menu = gtk.Menu()
iconset = gajim.config.get('iconset')
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
for c in contacts:
# icon MUST be different instance for every item
state_images = self.load_iconset(path)
item = gtk.ImageMenuItem(c.resource + ' (' + str(c.priority) + ')')
icon_name = helpers.get_icon_name_to_show(c, account)
icon = state_images[icon_name]
item.set_image(icon)
sub_menu.append(item)
item.connect('activate', self.on_open_chat_window, c, account,
c.resource)
iconset = gajim.config.get('iconset')
if not iconset:
iconset = DEFAULT_ICONSET
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
for c in contacts:
# icon MUST be different instance for every item
state_images = self.load_iconset(path)
item = gtk.ImageMenuItem(c.resource + ' (' + str(c.priority) + ')')
icon_name = helpers.get_icon_name_to_show(c, account)
icon = state_images[icon_name]
item.set_image(icon)
sub_menu.append(item)
item.connect('activate', action, c, account,
c.resource)
return sub_menu
start_chat_menuitem.set_submenu(resources_submenu(self.on_open_chat_window))
execute_command_menuitem.set_submenu(resources_submenu(self.on_execute_command))
else: # one resource
start_chat_menuitem.connect('activate',
self.on_roster_treeview_row_activated, tree_path)
self.on_open_chat_window, contact, account)
# we cannot execute commands when the resource is unknown
# TODO: that's true only if the entity is a contact,
# TODO: we need to show this also for transports
if contact.resource:
execute_command_menuitem.connect('activate',
self.on_execute_command, contact, account, contact.resource)
else:
execute_command_menuitem.hide()
execute_command_menuitem.set_no_show_all(True)
if contact.resource:
send_file_menuitem.connect('activate',
@ -1846,7 +1821,7 @@ class RosterWindow:
for widget in [start_chat_menuitem, send_single_message_menuitem,
rename_menuitem, edit_groups_menuitem, send_file_menuitem,
subscription_menuitem, add_to_roster_menuitem,
remove_from_roster_menuitem]:
remove_from_roster_menuitem, execute_command_menuitem]:
widget.set_sensitive(False)
event_button = gtkgui_helpers.get_possible_button_event(event)
@ -2095,6 +2070,15 @@ class RosterWindow:
if gajim.account_is_disconnected(account):
item.set_sensitive(False)
item = gtk.ImageMenuItem(_('Execute Command...'))
icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU)
item.set_image(icon)
menu.append(item)
item.connect('activate', self.on_execute_command, contact, account,
contact.resource)
if not is_connected:
item.set_sensitive(False)
item = gtk.ImageMenuItem(_('_Rename'))
# add a special img for rename menuitem
path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps',
@ -2167,6 +2151,7 @@ class RosterWindow:
new_message_menuitem = xml.get_widget('new_message_menuitem')
add_contact_menuitem = xml.get_widget('add_contact_menuitem')
service_discovery_menuitem = xml.get_widget('service_discovery_menuitem')
execute_command_menuitem = xml.get_widget('execute_command_menuitem')
edit_account_menuitem = xml.get_widget('edit_account_menuitem')
sub_menu = gtk.Menu()
status_menuitem.set_submenu(sub_menu)
@ -2210,6 +2195,10 @@ class RosterWindow:
add_contact_menuitem.connect('activate', self.on_add_new_contact, account)
service_discovery_menuitem.connect('activate',
self.on_service_disco_menuitem_activate, account)
hostname = gajim.config.get_per('accounts', account, 'hostname')
contact = gajim.contacts.create_contact(jid = hostname) # Fake contact
execute_command_menuitem.connect('activate',
self.on_execute_command, contact, account)
gc_sub_menu = gtk.Menu() # gc is always a submenu
join_group_chat_menuitem.set_submenu(gc_sub_menu)
@ -2220,7 +2209,8 @@ class RosterWindow:
# make some items insensitive if account is offline
if gajim.connections[account].connected < 2:
for widget in [add_contact_menuitem, service_discovery_menuitem,
join_group_chat_menuitem, new_message_menuitem]:
join_group_chat_menuitem, new_message_menuitem,
execute_command_menuitem]:
widget.set_sensitive(False)
else:
xml = gtkgui_helpers.get_glade('zeroconf_context_menu.glade')
@ -2360,7 +2350,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
def on_roster_treeview_key_press_event(self, widget, event):
'''when a key is pressed in the treeviews'''
self.tooltip.hide_tooltip()
if event.keyval == gtk.keysyms.Escape:
if event.keyval == gtk.keysyms.Menu:
self.show_treeview_menu(event)
return True
elif event.keyval == gtk.keysyms.Escape:
self.tree.get_selection().unselect_all()
elif event.keyval == gtk.keysyms.F2:
treeselection = self.tree.get_selection()
@ -2368,10 +2361,12 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
if len(list_of_paths) != 1:
return
path = list_of_paths[0]
type_ = model[path][C_TYPE]
if type_ in ('contact', 'group', 'agent'):
iter = model.get_iter(path)
self.on_rename(widget, iter, path)
type = model[path][C_TYPE]
if type in ('contact', 'group', 'agent'):
if not model[path][C_EDITABLE]:
# we are NOT already renaming it
iter = model.get_iter(path)
self.on_rename(widget, iter, path)
elif event.keyval == gtk.keysyms.Delete:
treeselection = self.tree.get_selection()
@ -3178,10 +3173,6 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
self.tooltip.hide_tooltip()
self.window.hide()
def on_roster_window_popup_menu(self, widget):
event = gtk.gdk.Event(gtk.gdk.KEY_PRESS)
self.show_treeview_menu(event)
def quit_gtkgui_interface(self):
'''When we quit the gtk interface :
tell that to the core and exit gtk'''
@ -3298,6 +3289,15 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
return True
return False
def on_execute_command(self, widget, contact, account, resource=None):
'''Execute command. Full JID needed; if it is other contact,
resource is necessary. Widget is unnecessary, only to be
able to make this a callback.'''
jid = contact.jid
if resource is not None:
jid = jid + u'/' + resource
adhoc_commands.CommandWindow(account, jid)
def on_open_chat_window(self, widget, contact, account, resource = None):
# Get the window containing the chat
fjid = contact.jid
@ -3420,6 +3420,91 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
account = model[iter][C_ACCOUNT].decode('utf-8')
self.draw_contact(jid, account)
def on_editing_started(self, cell, event, row):
''' start editing a cell in the tree'''
path = self.tree.get_cursor()[0]
self.editing_path = path
def on_editing_canceled(self, cell):
'''editing has been canceled'''
path = self.tree.get_cursor()[0]
# do not set new name if row order has changed
if path != self.editing_path:
self.editing_path = None
return
self.editing_path = None
model = self.tree.get_model()
iter = model.get_iter(path)
account = model[iter][C_ACCOUNT].decode('utf-8')
jid = model[iter][C_JID].decode('utf-8')
type = model[iter][C_TYPE]
# restore the number of resources string at the end of contact name
contacts = gajim.contacts.get_contact(account, jid)
if type in ('contact', 'agent'):
self.draw_contact(jid, account)
# reset editable to False
model[iter][C_EDITABLE] = False
def on_cell_edited(self, cell, row, new_text):
'''When an iter is edited:
if text has changed, rename the contact'''
model = self.tree.get_model()
# if this is a last item in the group, row is invalid
try:
iter = model.get_iter_from_string(row)
except:
self.editing_path = None
return
path = model.get_path(iter)
# do not set new name if row order has changed
if path != self.editing_path:
self.editing_path = None
return
self.editing_path = None
new_text = new_text.decode('utf-8')
account = model[iter][C_ACCOUNT].decode('utf-8')
jid = model[iter][C_JID].decode('utf-8')
type = model[iter][C_TYPE]
model[iter][C_EDITABLE] = False
if type in ('contact', 'agent'):
old_text = gajim.contacts.get_contact_with_highest_priority(account,
jid).name
if old_text != new_text:
for u in gajim.contacts.get_contact(account, jid):
u.name = new_text
gajim.connections[account].update_contact(jid, new_text, u.groups)
self.draw_contact(jid, account)
# Update opened chat
ctrl = gajim.interface.msg_win_mgr.get_control(jid, account)
if ctrl:
ctrl.update_ui()
win = gajim.interface.msg_win_mgr.get_window(jid, account)
win.redraw_tab(ctrl)
win.show_title()
elif type == 'group':
# in C_JID column, we hold the group name (which is not escaped)
old_name = model[iter][C_JID].decode('utf-8')
# Groups may not change name from or to a special groups
for g in helpers.special_groups:
if g in (new_text, old_name):
return
# get all contacts in that group
for jid in gajim.contacts.get_jid_list(account):
contact = gajim.contacts.get_contact_with_highest_priority(account,
jid)
if old_name in contact.groups:
# set them in the new one and remove it from the old
contact.groups.remove(old_name)
self.remove_contact(contact, account)
if not new_text in contact.groups:
contact.groups.append(new_text)
self.add_contact_to_roster(contact.jid, account)
gajim.connections[account].update_contact(contact.jid,
contact.name, contact.groups)
# If last removed iter was not visible, gajim.groups is not cleaned
if gajim.groups[account].has_key(old_name):
del gajim.groups[account][old_name]
def on_service_disco_menuitem_activate(self, widget, account):
server_jid = gajim.config.get_per('accounts', account, 'hostname')
if gajim.interface.instances[account]['disco'].has_key(server_jid):
@ -4086,6 +4171,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
def _on_treeview_selection_changed(self, selection):
model, list_of_paths = selection.get_selected_rows()
if len(list_of_paths) == 1 and model[list_of_paths[0]][C_EDITABLE]:
# We are editing this row, do not modify self._last_selected_contact
# Cause that cancel editing
return
if len(self._last_selected_contact):
# update unselected rows
for (jid, account) in self._last_selected_contact:
@ -4154,7 +4243,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
self.gpg_passphrase = {}
#(icon, name, type, jid, account, editable, secondary_pixbuf)
model = gtk.TreeStore(gtk.Image, str, str, str, str, gtk.gdk.Pixbuf)
model = gtk.TreeStore(gtk.Image, str, str, str, str, bool, gtk.gdk.Pixbuf)
model.set_sort_func(1, self.compareIters)
model.set_sort_column_id(1, gtk.SORT_ASCENDING)
@ -4251,8 +4340,12 @@ _('If "%s" accepts this request you will know his or her status.') % jid)
col.set_cell_data_func(render_image, self.iconCellDataFunc, None)
render_text = gtk.CellRendererText() # contact or group or account name
render_text.connect('edited', self.on_cell_edited)
render_text.connect('editing-canceled', self.on_editing_canceled)
render_text.connect('editing-started', self.on_editing_started)
col.pack_start(render_text, expand = True)
col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name
col.add_attribute(render_text, 'editable', C_EDITABLE) # where we hold if the row is editable
col.set_cell_data_func(render_text, self.nameCellDataFunc, None)
render_pixbuf = gtk.CellRendererPixbuf() # tls or avatar img