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:
commit
0a2cd86fce
|
@ -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>
|
||||
|
|
|
@ -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"><b>Choose command to execute:</b></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"><b>An error has occured:</b></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>
|
|
@ -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"><small>Romeo and Juliet</small></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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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''
|
|
@ -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
|
||||
|
|
@ -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,8 +713,22 @@ 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')
|
||||
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',
|
||||
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
|
@ -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,19 +1022,11 @@ 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):
|
||||
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)
|
||||
|
||||
def on_cancelbutton_clicked(self, widget):
|
||||
|
|
214
src/disco.py
214
src/disco.py
|
@ -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,6 +607,7 @@ _('Without a connection, you can not browse available services'))
|
|||
child.destroy(chain = chain)
|
||||
self.children.remove(child)
|
||||
if self.parent:
|
||||
if self in self.parent.children:
|
||||
self.parent.children.remove(self)
|
||||
if chain and not self.parent.children:
|
||||
self.parent.destroy(chain = chain)
|
||||
|
@ -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()
|
||||
|
|
11
src/gajim.py
11
src/gajim.py
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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()
|
||||
|
|
|
@ -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,10 +1680,13 @@ class RosterWindow:
|
|||
|
||||
contacts = gajim.contacts.get_contact(account, jid)
|
||||
if len(contacts) > 1: # sevral resources
|
||||
def resources_submenu(action):
|
||||
""" Build a submenu with contact's resources. """
|
||||
sub_menu = gtk.Menu()
|
||||
start_chat_menuitem.set_submenu(sub_menu)
|
||||
|
||||
iconset = gajim.config.get('iconset')
|
||||
if not iconset:
|
||||
iconset = DEFAULT_ICONSET
|
||||
path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
|
||||
for c in contacts:
|
||||
# icon MUST be different instance for every item
|
||||
|
@ -1734,12 +1696,25 @@ class RosterWindow:
|
|||
icon = state_images[icon_name]
|
||||
item.set_image(icon)
|
||||
sub_menu.append(item)
|
||||
item.connect('activate', self.on_open_chat_window, c, account,
|
||||
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,8 +2361,10 @@ _('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'):
|
||||
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)
|
||||
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue