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
					
				
					 24 changed files with 4568 additions and 139 deletions
				
			
		|  | @ -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> | ||||
|  |  | |||
							
								
								
									
										764
									
								
								data/glade/adhoc_commands_window.glade
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										764
									
								
								data/glade/adhoc_commands_window.glade
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,764 @@ | |||
| <?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> | ||||
| <!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> | ||||
| 
 | ||||
| <glade-interface> | ||||
| 
 | ||||
| <widget class="GtkWindow" id="adhoc_commands_window"> | ||||
|   <property name="visible">True</property> | ||||
|   <property name="title" translatable="yes">Ad-hoc Commands - Gajim</property> | ||||
|   <property name="type">GTK_WINDOW_TOPLEVEL</property> | ||||
|   <property name="window_position">GTK_WIN_POS_NONE</property> | ||||
|   <property name="modal">False</property> | ||||
|   <property name="resizable">True</property> | ||||
|   <property name="destroy_with_parent">False</property> | ||||
|   <property name="decorated">True</property> | ||||
|   <property name="skip_taskbar_hint">False</property> | ||||
|   <property name="skip_pager_hint">False</property> | ||||
|   <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> | ||||
|   <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> | ||||
|   <property name="focus_on_map">True</property> | ||||
|   <signal name="destroy" handler="on_adhoc_commands_window_destroy" last_modification_time="Thu, 22 Jun 2006 22:50:45 GMT"/> | ||||
| 
 | ||||
|   <child> | ||||
|     <widget class="GtkVBox" id="vbox1"> | ||||
|       <property name="border_width">6</property> | ||||
|       <property name="visible">True</property> | ||||
|       <property name="homogeneous">False</property> | ||||
|       <property name="spacing">0</property> | ||||
| 
 | ||||
|       <child> | ||||
| 	<widget class="GtkNotebook" id="stages_notebook"> | ||||
| 	  <property name="width_request">400</property> | ||||
| 	  <property name="height_request">400</property> | ||||
| 	  <property name="visible">True</property> | ||||
| 	  <property name="show_tabs">False</property> | ||||
| 	  <property name="show_border">False</property> | ||||
| 	  <property name="tab_pos">GTK_POS_TOP</property> | ||||
| 	  <property name="scrollable">False</property> | ||||
| 	  <property name="enable_popup">False</property> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkVBox" id="retrieving_commands_stage_vbox"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="homogeneous">False</property> | ||||
| 	      <property name="spacing">0</property> | ||||
| 
 | ||||
| 	      <child> | ||||
| 		<widget class="GtkLabel" id="label247"> | ||||
| 		  <property name="visible">True</property> | ||||
| 		  <property name="label" translatable="yes">Please wait while retrieving command list...</property> | ||||
| 		  <property name="use_underline">False</property> | ||||
| 		  <property name="use_markup">False</property> | ||||
| 		  <property name="justify">GTK_JUSTIFY_LEFT</property> | ||||
| 		  <property name="wrap">False</property> | ||||
| 		  <property name="selectable">False</property> | ||||
| 		  <property name="xalign">0.5</property> | ||||
| 		  <property name="yalign">0.5</property> | ||||
| 		  <property name="xpad">0</property> | ||||
| 		  <property name="ypad">0</property> | ||||
| 		  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> | ||||
| 		  <property name="width_chars">-1</property> | ||||
| 		  <property name="single_line_mode">False</property> | ||||
| 		  <property name="angle">0</property> | ||||
| 		</widget> | ||||
| 		<packing> | ||||
| 		  <property name="padding">0</property> | ||||
| 		  <property name="expand">True</property> | ||||
| 		  <property name="fill">False</property> | ||||
| 		</packing> | ||||
| 	      </child> | ||||
| 
 | ||||
| 	      <child> | ||||
| 		<widget class="GtkProgressBar" id="retrieving_commands_progressbar"> | ||||
| 		  <property name="visible">True</property> | ||||
| 		  <property name="orientation">GTK_PROGRESS_LEFT_TO_RIGHT</property> | ||||
| 		  <property name="fraction">0</property> | ||||
| 		  <property name="pulse_step">0.0500000007451</property> | ||||
| 		  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> | ||||
| 		</widget> | ||||
| 		<packing> | ||||
| 		  <property name="padding">0</property> | ||||
| 		  <property name="expand">True</property> | ||||
| 		  <property name="fill">False</property> | ||||
| 		</packing> | ||||
| 	      </child> | ||||
| 
 | ||||
| 	      <child> | ||||
| 		<placeholder/> | ||||
| 	      </child> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="tab_expand">False</property> | ||||
| 	      <property name="tab_fill">True</property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkLabel" id="label264"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="label" translatable="yes"></property> | ||||
| 	      <property name="use_underline">False</property> | ||||
| 	      <property name="use_markup">False</property> | ||||
| 	      <property name="justify">GTK_JUSTIFY_LEFT</property> | ||||
| 	      <property name="wrap">False</property> | ||||
| 	      <property name="selectable">False</property> | ||||
| 	      <property name="xalign">0.5</property> | ||||
| 	      <property name="yalign">0.5</property> | ||||
| 	      <property name="xpad">0</property> | ||||
| 	      <property name="ypad">0</property> | ||||
| 	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> | ||||
| 	      <property name="width_chars">-1</property> | ||||
| 	      <property name="single_line_mode">False</property> | ||||
| 	      <property name="angle">0</property> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="type">tab</property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <placeholder/> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkLabel" id="label265"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="label" translatable="yes"></property> | ||||
| 	      <property name="use_underline">False</property> | ||||
| 	      <property name="use_markup">False</property> | ||||
| 	      <property name="justify">GTK_JUSTIFY_LEFT</property> | ||||
| 	      <property name="wrap">False</property> | ||||
| 	      <property name="selectable">False</property> | ||||
| 	      <property name="xalign">0.5</property> | ||||
| 	      <property name="yalign">0.5</property> | ||||
| 	      <property name="xpad">0</property> | ||||
| 	      <property name="ypad">0</property> | ||||
| 	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> | ||||
| 	      <property name="width_chars">-1</property> | ||||
| 	      <property name="single_line_mode">False</property> | ||||
| 	      <property name="angle">0</property> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="type">tab</property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkVBox" id="command_list_stage_vbox"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="homogeneous">False</property> | ||||
| 	      <property name="spacing">0</property> | ||||
| 
 | ||||
| 	      <child> | ||||
| 		<widget class="GtkLabel" id="label253"> | ||||
| 		  <property name="visible">True</property> | ||||
| 		  <property name="label" translatable="yes"><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> | ||||
							
								
								
									
										371
									
								
								data/glade/atom_entry_window.glade
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										371
									
								
								data/glade/atom_entry_window.glade
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,371 @@ | |||
| <?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> | ||||
| <!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> | ||||
| 
 | ||||
| <glade-interface> | ||||
| 
 | ||||
| <widget class="GtkWindow" id="atom_entry_window"> | ||||
|   <property name="visible">True</property> | ||||
|   <property name="title" translatable="yes">New entry received</property> | ||||
|   <property name="type">GTK_WINDOW_TOPLEVEL</property> | ||||
|   <property name="window_position">GTK_WIN_POS_NONE</property> | ||||
|   <property name="modal">False</property> | ||||
|   <property name="resizable">True</property> | ||||
|   <property name="destroy_with_parent">False</property> | ||||
|   <property name="decorated">True</property> | ||||
|   <property name="skip_taskbar_hint">False</property> | ||||
|   <property name="skip_pager_hint">False</property> | ||||
|   <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> | ||||
|   <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> | ||||
|   <property name="focus_on_map">True</property> | ||||
| 
 | ||||
|   <child> | ||||
|     <widget class="GtkVBox" id="vbox112"> | ||||
|       <property name="border_width">6</property> | ||||
|       <property name="visible">True</property> | ||||
|       <property name="homogeneous">False</property> | ||||
|       <property name="spacing">0</property> | ||||
| 
 | ||||
|       <child> | ||||
| 	<widget class="GtkLabel" id="new_entry_label"> | ||||
| 	  <property name="visible">True</property> | ||||
| 	  <property name="label" translatable="yes">You have received new entry:</property> | ||||
| 	  <property name="use_underline">False</property> | ||||
| 	  <property name="use_markup">False</property> | ||||
| 	  <property name="justify">GTK_JUSTIFY_LEFT</property> | ||||
| 	  <property name="wrap">False</property> | ||||
| 	  <property name="selectable">False</property> | ||||
| 	  <property name="xalign">0.5</property> | ||||
| 	  <property name="yalign">0.5</property> | ||||
| 	  <property name="xpad">0</property> | ||||
| 	  <property name="ypad">0</property> | ||||
| 	  <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> | ||||
| 	  <property name="width_chars">-1</property> | ||||
| 	  <property name="single_line_mode">False</property> | ||||
| 	  <property name="angle">0</property> | ||||
| 	</widget> | ||||
| 	<packing> | ||||
| 	  <property name="padding">0</property> | ||||
| 	  <property name="expand">False</property> | ||||
| 	  <property name="fill">False</property> | ||||
| 	</packing> | ||||
|       </child> | ||||
| 
 | ||||
|       <child> | ||||
| 	<widget class="GtkHSeparator" id="hseparator14"> | ||||
| 	  <property name="visible">True</property> | ||||
| 	</widget> | ||||
| 	<packing> | ||||
| 	  <property name="padding">0</property> | ||||
| 	  <property name="expand">False</property> | ||||
| 	  <property name="fill">True</property> | ||||
| 	</packing> | ||||
|       </child> | ||||
| 
 | ||||
|       <child> | ||||
| 	<widget class="GtkTable" id="table4"> | ||||
| 	  <property name="visible">True</property> | ||||
| 	  <property name="n_rows">4</property> | ||||
| 	  <property name="n_columns">2</property> | ||||
| 	  <property name="homogeneous">False</property> | ||||
| 	  <property name="row_spacing">0</property> | ||||
| 	  <property name="column_spacing">6</property> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkLabel" id="label1"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="label" translatable="yes">Feed name:</property> | ||||
| 	      <property name="use_underline">False</property> | ||||
| 	      <property name="use_markup">False</property> | ||||
| 	      <property name="justify">GTK_JUSTIFY_RIGHT</property> | ||||
| 	      <property name="wrap">False</property> | ||||
| 	      <property name="selectable">False</property> | ||||
| 	      <property name="xalign">0</property> | ||||
| 	      <property name="yalign">0.5</property> | ||||
| 	      <property name="xpad">0</property> | ||||
| 	      <property name="ypad">0</property> | ||||
| 	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> | ||||
| 	      <property name="width_chars">-1</property> | ||||
| 	      <property name="single_line_mode">False</property> | ||||
| 	      <property name="angle">0</property> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="left_attach">0</property> | ||||
| 	      <property name="right_attach">1</property> | ||||
| 	      <property name="top_attach">0</property> | ||||
| 	      <property name="bottom_attach">1</property> | ||||
| 	      <property name="x_options">fill</property> | ||||
| 	      <property name="y_options"></property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkLabel" id="label2"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="label" translatable="yes">Entry:</property> | ||||
| 	      <property name="use_underline">False</property> | ||||
| 	      <property name="use_markup">False</property> | ||||
| 	      <property name="justify">GTK_JUSTIFY_LEFT</property> | ||||
| 	      <property name="wrap">False</property> | ||||
| 	      <property name="selectable">False</property> | ||||
| 	      <property name="xalign">0</property> | ||||
| 	      <property name="yalign">0.5</property> | ||||
| 	      <property name="xpad">0</property> | ||||
| 	      <property name="ypad">0</property> | ||||
| 	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> | ||||
| 	      <property name="width_chars">-1</property> | ||||
| 	      <property name="single_line_mode">False</property> | ||||
| 	      <property name="angle">0</property> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="left_attach">0</property> | ||||
| 	      <property name="right_attach">1</property> | ||||
| 	      <property name="top_attach">2</property> | ||||
| 	      <property name="bottom_attach">3</property> | ||||
| 	      <property name="x_options">fill</property> | ||||
| 	      <property name="y_options"></property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkLabel" id="label3"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="label" translatable="yes">Last modified:</property> | ||||
| 	      <property name="use_underline">False</property> | ||||
| 	      <property name="use_markup">False</property> | ||||
| 	      <property name="justify">GTK_JUSTIFY_LEFT</property> | ||||
| 	      <property name="wrap">False</property> | ||||
| 	      <property name="selectable">False</property> | ||||
| 	      <property name="xalign">0</property> | ||||
| 	      <property name="yalign">0.5</property> | ||||
| 	      <property name="xpad">0</property> | ||||
| 	      <property name="ypad">0</property> | ||||
| 	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> | ||||
| 	      <property name="width_chars">-1</property> | ||||
| 	      <property name="single_line_mode">False</property> | ||||
| 	      <property name="angle">0</property> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="left_attach">0</property> | ||||
| 	      <property name="right_attach">1</property> | ||||
| 	      <property name="top_attach">3</property> | ||||
| 	      <property name="bottom_attach">4</property> | ||||
| 	      <property name="x_options">fill</property> | ||||
| 	      <property name="y_options"></property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkLabel" id="feed_tagline_label"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="label" translatable="yes"><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> | ||||
|  |  | |||
							
								
								
									
										232
									
								
								data/glade/groups_post_window.glade
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										232
									
								
								data/glade/groups_post_window.glade
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,232 @@ | |||
| <?xml version="1.0" standalone="no"?> <!--*- mode: xml -*--> | ||||
| <!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> | ||||
| 
 | ||||
| <glade-interface> | ||||
| 
 | ||||
| <widget class="GtkWindow" id="groups_post_window"> | ||||
|   <property name="visible">True</property> | ||||
|   <property name="title" translatable="yes">Create new post</property> | ||||
|   <property name="type">GTK_WINDOW_TOPLEVEL</property> | ||||
|   <property name="window_position">GTK_WIN_POS_NONE</property> | ||||
|   <property name="modal">False</property> | ||||
|   <property name="resizable">True</property> | ||||
|   <property name="destroy_with_parent">False</property> | ||||
|   <property name="decorated">True</property> | ||||
|   <property name="skip_taskbar_hint">False</property> | ||||
|   <property name="skip_pager_hint">False</property> | ||||
|   <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> | ||||
|   <property name="gravity">GDK_GRAVITY_NORTH_WEST</property> | ||||
|   <property name="focus_on_map">True</property> | ||||
| 
 | ||||
|   <child> | ||||
|     <widget class="GtkVBox" id="vbox1"> | ||||
|       <property name="border_width">6</property> | ||||
|       <property name="visible">True</property> | ||||
|       <property name="homogeneous">False</property> | ||||
|       <property name="spacing">4</property> | ||||
| 
 | ||||
|       <child> | ||||
| 	<widget class="GtkTable" id="table1"> | ||||
| 	  <property name="visible">True</property> | ||||
| 	  <property name="n_rows">2</property> | ||||
| 	  <property name="n_columns">2</property> | ||||
| 	  <property name="homogeneous">False</property> | ||||
| 	  <property name="row_spacing">0</property> | ||||
| 	  <property name="column_spacing">0</property> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkLabel" id="label1"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="label" translatable="yes">From</property> | ||||
| 	      <property name="use_underline">False</property> | ||||
| 	      <property name="use_markup">False</property> | ||||
| 	      <property name="justify">GTK_JUSTIFY_LEFT</property> | ||||
| 	      <property name="wrap">False</property> | ||||
| 	      <property name="selectable">False</property> | ||||
| 	      <property name="xalign">0</property> | ||||
| 	      <property name="yalign">0.5</property> | ||||
| 	      <property name="xpad">0</property> | ||||
| 	      <property name="ypad">0</property> | ||||
| 	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> | ||||
| 	      <property name="width_chars">-1</property> | ||||
| 	      <property name="single_line_mode">False</property> | ||||
| 	      <property name="angle">0</property> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="left_attach">0</property> | ||||
| 	      <property name="right_attach">1</property> | ||||
| 	      <property name="top_attach">0</property> | ||||
| 	      <property name="bottom_attach">1</property> | ||||
| 	      <property name="x_options">fill</property> | ||||
| 	      <property name="y_options"></property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkLabel" id="label2"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="label" translatable="yes">Subject</property> | ||||
| 	      <property name="use_underline">False</property> | ||||
| 	      <property name="use_markup">False</property> | ||||
| 	      <property name="justify">GTK_JUSTIFY_LEFT</property> | ||||
| 	      <property name="wrap">False</property> | ||||
| 	      <property name="selectable">False</property> | ||||
| 	      <property name="xalign">0</property> | ||||
| 	      <property name="yalign">0.5</property> | ||||
| 	      <property name="xpad">0</property> | ||||
| 	      <property name="ypad">0</property> | ||||
| 	      <property name="ellipsize">PANGO_ELLIPSIZE_NONE</property> | ||||
| 	      <property name="width_chars">-1</property> | ||||
| 	      <property name="single_line_mode">False</property> | ||||
| 	      <property name="angle">0</property> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="left_attach">0</property> | ||||
| 	      <property name="right_attach">1</property> | ||||
| 	      <property name="top_attach">1</property> | ||||
| 	      <property name="bottom_attach">2</property> | ||||
| 	      <property name="x_options">fill</property> | ||||
| 	      <property name="y_options"></property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkEntry" id="from_entry"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="can_focus">True</property> | ||||
| 	      <property name="editable">True</property> | ||||
| 	      <property name="visibility">True</property> | ||||
| 	      <property name="max_length">0</property> | ||||
| 	      <property name="text" translatable="yes"></property> | ||||
| 	      <property name="has_frame">True</property> | ||||
| 	      <property name="invisible_char">●</property> | ||||
| 	      <property name="activates_default">False</property> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="left_attach">1</property> | ||||
| 	      <property name="right_attach">2</property> | ||||
| 	      <property name="top_attach">0</property> | ||||
| 	      <property name="bottom_attach">1</property> | ||||
| 	      <property name="y_options"></property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkEntry" id="subject_entry"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="can_focus">True</property> | ||||
| 	      <property name="editable">True</property> | ||||
| 	      <property name="visibility">True</property> | ||||
| 	      <property name="max_length">0</property> | ||||
| 	      <property name="text" translatable="yes"></property> | ||||
| 	      <property name="has_frame">True</property> | ||||
| 	      <property name="invisible_char">●</property> | ||||
| 	      <property name="activates_default">False</property> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="left_attach">1</property> | ||||
| 	      <property name="right_attach">2</property> | ||||
| 	      <property name="top_attach">1</property> | ||||
| 	      <property name="bottom_attach">2</property> | ||||
| 	      <property name="y_options"></property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 	</widget> | ||||
| 	<packing> | ||||
| 	  <property name="padding">0</property> | ||||
| 	  <property name="expand">False</property> | ||||
| 	  <property name="fill">True</property> | ||||
| 	</packing> | ||||
|       </child> | ||||
| 
 | ||||
|       <child> | ||||
| 	<widget class="GtkScrolledWindow" id="scrolledwindow1"> | ||||
| 	  <property name="visible">True</property> | ||||
| 	  <property name="can_focus">True</property> | ||||
| 	  <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> | ||||
| 	  <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> | ||||
| 	  <property name="shadow_type">GTK_SHADOW_IN</property> | ||||
| 	  <property name="window_placement">GTK_CORNER_TOP_LEFT</property> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkTextView" id="contents_textview"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="can_focus">True</property> | ||||
| 	      <property name="editable">True</property> | ||||
| 	      <property name="overwrite">False</property> | ||||
| 	      <property name="accepts_tab">True</property> | ||||
| 	      <property name="justification">GTK_JUSTIFY_LEFT</property> | ||||
| 	      <property name="wrap_mode">GTK_WRAP_NONE</property> | ||||
| 	      <property name="cursor_visible">True</property> | ||||
| 	      <property name="pixels_above_lines">0</property> | ||||
| 	      <property name="pixels_below_lines">0</property> | ||||
| 	      <property name="pixels_inside_wrap">0</property> | ||||
| 	      <property name="left_margin">0</property> | ||||
| 	      <property name="right_margin">0</property> | ||||
| 	      <property name="indent">0</property> | ||||
| 	      <property name="text" translatable="yes"></property> | ||||
| 	    </widget> | ||||
| 	  </child> | ||||
| 	</widget> | ||||
| 	<packing> | ||||
| 	  <property name="padding">0</property> | ||||
| 	  <property name="expand">True</property> | ||||
| 	  <property name="fill">True</property> | ||||
| 	</packing> | ||||
|       </child> | ||||
| 
 | ||||
|       <child> | ||||
| 	<widget class="GtkHBox" id="hbox1"> | ||||
| 	  <property name="visible">True</property> | ||||
| 	  <property name="homogeneous">False</property> | ||||
| 	  <property name="spacing">0</property> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkButton" id="cancel_button"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="can_focus">True</property> | ||||
| 	      <property name="label">gtk-cancel</property> | ||||
| 	      <property name="use_stock">True</property> | ||||
| 	      <property name="relief">GTK_RELIEF_NORMAL</property> | ||||
| 	      <property name="focus_on_click">True</property> | ||||
| 	      <signal name="clicked" handler="on_cancel_button_clicked" last_modification_time="Fri, 18 Aug 2006 16:27:42 GMT"/> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="padding">0</property> | ||||
| 	      <property name="expand">False</property> | ||||
| 	      <property name="fill">False</property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <placeholder/> | ||||
| 	  </child> | ||||
| 
 | ||||
| 	  <child> | ||||
| 	    <widget class="GtkButton" id="send_button"> | ||||
| 	      <property name="visible">True</property> | ||||
| 	      <property name="can_focus">True</property> | ||||
| 	      <property name="label">gtk-go-forward</property> | ||||
| 	      <property name="use_stock">True</property> | ||||
| 	      <property name="relief">GTK_RELIEF_NORMAL</property> | ||||
| 	      <property name="focus_on_click">True</property> | ||||
| 	      <signal name="clicked" handler="on_send_button_clicked" last_modification_time="Fri, 18 Aug 2006 16:27:48 GMT"/> | ||||
| 	    </widget> | ||||
| 	    <packing> | ||||
| 	      <property name="padding">0</property> | ||||
| 	      <property name="expand">False</property> | ||||
| 	      <property name="fill">False</property> | ||||
| 	    </packing> | ||||
| 	  </child> | ||||
| 	</widget> | ||||
| 	<packing> | ||||
| 	  <property name="padding">0</property> | ||||
| 	  <property name="expand">False</property> | ||||
| 	  <property name="fill">True</property> | ||||
| 	</packing> | ||||
|       </child> | ||||
|     </widget> | ||||
|   </child> | ||||
| </widget> | ||||
| 
 | ||||
| </glade-interface> | ||||
|  | @ -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"> | ||||
|  |  | |||
							
								
								
									
										514
									
								
								src/adhoc_commands.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										514
									
								
								src/adhoc_commands.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,514 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| ##	config.py | ||||
| ## | ||||
| ## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org> | ||||
| ##                         Vincent Hanquez <tab@snarc.org> | ||||
| ## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org> | ||||
| ##                    Vincent Hanquez <tab@snarc.org> | ||||
| ##                    Nikos Kouremenos <nkour@jabber.org> | ||||
| ##                    Dimitur Kirov <dkirov@gmail.com> | ||||
| ##                    Travis Shirk <travis@pobox.com> | ||||
| ##                    Norman Rasmussen <norman@rasmussen.co.za> | ||||
| ## | ||||
| ## This program is free software; you can redistribute it and/or modify | ||||
| ## it under the terms of the GNU General Public License as published | ||||
| ## by the Free Software Foundation; version 2 only. | ||||
| ## | ||||
| ## This program is distributed in the hope that it will be useful, | ||||
| ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| ## GNU General Public License for more details. | ||||
| 
 | ||||
| # TODO: think if we need caching command list. it may be wrong if there will | ||||
| # TODO: be entities that often change the list, it may be slow to fetch it | ||||
| # TODO: every time | ||||
| 
 | ||||
| import gobject | ||||
| import gtk | ||||
| 
 | ||||
| from common import xmpp, gajim, dataforms | ||||
| 
 | ||||
| import gtkgui_helpers | ||||
| import dialogs | ||||
| import dataforms_widget | ||||
| 
 | ||||
| class CommandWindow: | ||||
| 	'''Class for a window for single ad-hoc commands session. Note, that | ||||
| 	there might be more than one for one account/jid pair in one moment. | ||||
| 
 | ||||
| 	TODO: maybe put this window into MessageWindow? consider this when | ||||
| 	TODO: it will be possible to manage more than one window of one | ||||
| 	TODO: account/jid pair in MessageWindowMgr. | ||||
| 
 | ||||
| 	TODO: gtk 2.10 has a special wizard-widget, consider using it...''' | ||||
| 
 | ||||
| 	def __init__(self, account, jid): | ||||
| 		'''Create new window.''' | ||||
| 
 | ||||
| 		# an account object | ||||
| 		self.account = gajim.connections[account] | ||||
| 		self.jid = jid | ||||
| 
 | ||||
| 		self.pulse_id=None	# to satisfy self.setup_pulsing() | ||||
| 		self.commandlist=None	# a list of (commandname, commanddescription) | ||||
| 
 | ||||
| 		# command's data | ||||
| 		self.commandnode = None | ||||
| 		self.sessionid = None | ||||
| 		self.dataform = None | ||||
| 
 | ||||
| 		# retrieving widgets from xml | ||||
| 		self.xml = gtkgui_helpers.get_glade('adhoc_commands_window.glade') | ||||
| 		self.window = self.xml.get_widget('adhoc_commands_window') | ||||
| 		for name in ('cancel_button', 'back_button', 'forward_button', | ||||
| 			'execute_button','close_button','stages_notebook', | ||||
| 			'retrieving_commands_stage_vbox', | ||||
| 			'command_list_stage_vbox','command_list_vbox', | ||||
| 			'sending_form_stage_vbox','sending_form_progressbar', | ||||
| 			'notes_label','no_commands_stage_vbox','error_stage_vbox', | ||||
| 			'error_description_label'): | ||||
| 			self.__dict__[name] = self.xml.get_widget(name) | ||||
| 
 | ||||
| 		# creating data forms widget | ||||
| 		self.data_form_widget = dataforms_widget.DataFormWidget() | ||||
| 		self.data_form_widget.show() | ||||
| 		self.sending_form_stage_vbox.pack_start(self.data_form_widget) | ||||
| 
 | ||||
| 		# setting initial stage | ||||
| 		self.close_button.set_no_show_all(True) | ||||
| 		self.stage1() | ||||
| 
 | ||||
| 		# displaying the window | ||||
| 		self.xml.signal_autoconnect(self) | ||||
| 		self.window.show_all() | ||||
| 
 | ||||
| # these functions are set up by appropriate stageX methods | ||||
| 	def stage_finish(self, *anything): pass | ||||
| 	def stage_cancel_button_clicked(self, *anything): assert False | ||||
| 	def stage_back_button_clicked(self, *anything): assert False | ||||
| 	def stage_forward_button_clicked(self, *anything): assert False | ||||
| 	def stage_execute_button_clicked(self, *anything): assert False | ||||
| 	def stage_close_button_clicked(self, *anything): assert False | ||||
| 	def stage_adhoc_commands_window_delete_event(self, *anything): assert False | ||||
| 	def do_nothing(self, *anything): return False | ||||
| 
 | ||||
| # widget callbacks | ||||
| 	def on_cancel_button_clicked(self, *anything): | ||||
| 		return self.stage_cancel_button_clicked(*anything) | ||||
| 
 | ||||
| 	def on_back_button_clicked(self, *anything): | ||||
| 		return self.stage_back_button_clicked(*anything) | ||||
| 
 | ||||
| 	def on_forward_button_clicked(self, *anything): | ||||
| 		return self.stage_forward_button_clicked(*anything) | ||||
| 
 | ||||
| 	def on_execute_button_clicked(self, *anything): | ||||
| 		return self.stage_execute_button_clicked(*anything) | ||||
| 
 | ||||
| 	def on_close_button_clicked(self, *anything): | ||||
| 		return self.stage_close_button_clicked(*anything) | ||||
| 
 | ||||
| 	def on_adhoc_commands_window_destroy(self, *anything): | ||||
| 		# TODO: do all actions that are needed to remove this object from memory... | ||||
| 		self.remove_pulsing() | ||||
| 
 | ||||
| 	def on_adhoc_commands_window_delete_event(self, *anything): | ||||
| 		return self.stage_adhoc_commands_window_delete_event(self, *anything) | ||||
| 
 | ||||
| 	def __del__(self): | ||||
| 		print "Object has been deleted." | ||||
| 
 | ||||
| # stage 1: waiting for command list | ||||
| 	def stage1(self): | ||||
| 		'''Prepare the first stage. Request command list, | ||||
| 		set appropriate state of widgets.''' | ||||
| 		# close old stage... | ||||
| 		self.stage_finish() | ||||
| 
 | ||||
| 		# show the stage | ||||
| 		self.stages_notebook.set_current_page( | ||||
| 			self.stages_notebook.page_num( | ||||
| 				self.retrieving_commands_stage_vbox)) | ||||
| 
 | ||||
| 		# set widgets' state | ||||
| 		self.cancel_button.set_sensitive(True) | ||||
| 		self.back_button.set_sensitive(False) | ||||
| 		self.forward_button.set_sensitive(False) | ||||
| 		self.execute_button.set_sensitive(False) | ||||
| 
 | ||||
| 		# request command list | ||||
| 		self.request_command_list() | ||||
| 		self.setup_pulsing( | ||||
| 			self.xml.get_widget('retrieving_commands_progressbar')) | ||||
| 
 | ||||
| 		# setup the callbacks | ||||
| 		self.stage_finish = self.stage1_finish | ||||
| 		self.stage_cancel_button_clicked = self.stage1_cancel_button_clicked | ||||
| 		self.stage_adhoc_commands_window_delete_event = self.stage1_adhoc_commands_window_delete_event | ||||
| 
 | ||||
| 	def stage1_finish(self): | ||||
| 		self.remove_pulsing() | ||||
| 
 | ||||
| 	def stage1_cancel_button_clicked(self, widget): | ||||
| 		# cancelling in this stage is not critical, so we don't | ||||
| 		# show any popups to user | ||||
| 		self.stage1_finish() | ||||
| 		self.window.destroy() | ||||
| 
 | ||||
| 	def stage1_adhoc_commands_window_delete_event(self, widget): | ||||
| 		self.stage1_finish() | ||||
| 		return True | ||||
| 
 | ||||
| # stage 2: choosing the command to execute | ||||
| 	def stage2(self): | ||||
| 		'''Populate the command list vbox with radiobuttons | ||||
| 		(TODO: if there is more commands, maybe some kind of list?), | ||||
| 		set widgets' state.''' | ||||
| 		# close old stage | ||||
| 		self.stage_finish() | ||||
| 
 | ||||
| 		assert len(self.commandlist)>0 | ||||
| 
 | ||||
| 		self.stages_notebook.set_current_page( | ||||
| 			self.stages_notebook.page_num( | ||||
| 				self.command_list_stage_vbox)) | ||||
| 
 | ||||
| 		self.cancel_button.set_sensitive(True) | ||||
| 		self.back_button.set_sensitive(False) | ||||
| 		self.forward_button.set_sensitive(True) | ||||
| 		self.execute_button.set_sensitive(False) | ||||
| 
 | ||||
| 		# build the commands list radiobuttons | ||||
| 		first_radio = None | ||||
| 		for (commandnode, commandname) in self.commandlist: | ||||
| 			radio = gtk.RadioButton(first_radio, label=commandname) | ||||
| 			radio.connect("toggled", self.on_command_radiobutton_toggled, commandnode) | ||||
| 			if first_radio is None: | ||||
| 				first_radio = radio | ||||
| 				self.commandnode = commandnode | ||||
| 			self.command_list_vbox.pack_start(radio, expand=False) | ||||
| 		self.command_list_vbox.show_all() | ||||
| 
 | ||||
| 		self.stage_finish = self.stage2_finish | ||||
| 		self.stage_cancel_button_clicked = self.stage2_cancel_button_clicked | ||||
| 		self.stage_forward_button_clicked = self.stage2_forward_button_clicked | ||||
| 		self.stage_adhoc_commands_window_delete_event = self.do_nothing | ||||
| 
 | ||||
| 	def stage2_finish(self): | ||||
| 		'''Remove widgets we created. Not needed when the window is destroyed.''' | ||||
| 		def remove_widget(widget): | ||||
| 			self.command_list_vbox.remove(widget) | ||||
| 		self.command_list_vbox.foreach(remove_widget) | ||||
| 
 | ||||
| 	def stage2_cancel_button_clicked(self, widget): | ||||
| 		self.stage_finish() | ||||
| 		self.window.destroy() | ||||
| 
 | ||||
| 	def stage2_forward_button_clicked(self, widget): | ||||
| 		self.stage3() | ||||
| 
 | ||||
| 	def on_command_radiobutton_toggled(self, widget, commandnode): | ||||
| 		self.commandnode = commandnode | ||||
| 
 | ||||
| 	def on_check_commands_1_button_clicked(self, widget): | ||||
| 		self.stage1() | ||||
| 
 | ||||
| # stage 3: command invocation | ||||
| 	def stage3(self): | ||||
| 		# close old stage | ||||
| 		self.stage_finish() | ||||
| 
 | ||||
| 		assert isinstance(self.commandnode, unicode) | ||||
| 
 | ||||
| 		self.form_status = None | ||||
| 
 | ||||
| 		self.stages_notebook.set_current_page( | ||||
| 			self.stages_notebook.page_num( | ||||
| 				self.sending_form_stage_vbox)) | ||||
| 
 | ||||
| 		self.cancel_button.set_sensitive(True) | ||||
| 		self.back_button.set_sensitive(False) | ||||
| 		self.forward_button.set_sensitive(False) | ||||
| 		self.execute_button.set_sensitive(False) | ||||
| 
 | ||||
| 		self.stage3_submit_form() | ||||
| 
 | ||||
| 		self.stage_finish = self.stage3_finish | ||||
| 		self.stage_cancel_button_clicked = self.stage3_cancel_button_clicked | ||||
| 		self.stage_back_button_clicked = self.stage3_back_button_clicked | ||||
| 		self.stage_forward_button_clicked = self.stage3_forward_button_clicked | ||||
| 		self.stage_execute_button_clicked = self.stage3_execute_button_clicked | ||||
| 		self.stage_close_button_clicked = self.stage3_close_button_clicked | ||||
| 		self.stage_adhoc_commands_window_delete_event = self.stage3_cancel_button_clicked | ||||
| 
 | ||||
| 	def stage3_finish(self): | ||||
| 		pass | ||||
| 
 | ||||
| 	def stage3_cancel_button_clicked(self, widget, *anything): | ||||
| 		''' We are in the middle of executing command. Ask user if he really want to cancel | ||||
| 		the process, then... cancel it. ''' | ||||
| 		# this works also as a handler for window_delete_event, so we have to return appropriate | ||||
| 		# values | ||||
| 		# TODO: translate it | ||||
| 		dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, | ||||
| 			gtk.BUTTONS_YES_NO, 'Cancel confirmation', | ||||
| 			'You are in process of executing command. Do you really want to cancel it?') | ||||
| 		dialog.popup() | ||||
| 		if dialog.get_response()==gtk.RESPONSE_YES: | ||||
| 			self.send_cancel() | ||||
| 			if widget==self.window: | ||||
| 				return False | ||||
| 			else: | ||||
| 				self.window.destroy() | ||||
| 			return False | ||||
| 		return True | ||||
| 
 | ||||
| 	def stage3_close_button_clicked(self, widget): | ||||
| 		# this works also as a handler for window_delete_event, so we have to return appropriate | ||||
| 		# values | ||||
| 		if widget==self.window: | ||||
| 			return False | ||||
| 		else: | ||||
| 			self.window.destroy() | ||||
| 
 | ||||
| 	def stage3_back_button_clicked(self, widget): | ||||
| 		self.stage3_submit_form('prev') | ||||
| 
 | ||||
| 	def stage3_forward_button_clicked(self, widget): | ||||
| 		self.stage3_submit_form('next') | ||||
| 
 | ||||
| 	def stage3_execute_button_clicked(self, widget): | ||||
| 		self.stage3_submit_form('execute') | ||||
| 
 | ||||
| 	def stage3_submit_form(self, action='execute'): | ||||
| 		self.data_form_widget.set_sensitive(False) | ||||
| 		if self.data_form_widget.get_data_form() is None: | ||||
| 			self.data_form_widget.hide() | ||||
| 
 | ||||
| 		self.cancel_button.set_sensitive(True) | ||||
| 		self.back_button.set_sensitive(False) | ||||
| 		self.forward_button.set_sensitive(False) | ||||
| 		self.execute_button.set_sensitive(False) | ||||
| 
 | ||||
| 		self.sending_form_progressbar.show() | ||||
| 		self.setup_pulsing(self.sending_form_progressbar) | ||||
| 		self.send_command(action) | ||||
| 
 | ||||
| 	def stage3_next_form(self, command): | ||||
| 		assert isinstance(command, xmpp.Node) | ||||
| 
 | ||||
| 		self.remove_pulsing() | ||||
| 		self.sending_form_progressbar.hide() | ||||
| 
 | ||||
| 		if self.sessionid is None: | ||||
| 			self.sessionid = command.getAttr('sessionid') | ||||
| 
 | ||||
| 		self.form_status = command.getAttr('status') | ||||
| 
 | ||||
| 		if command.getTag('x') is not None: | ||||
| 			self.dataform = dataforms.ExtendForm(node=command.getTag('x')) | ||||
| 
 | ||||
| 			self.data_form_widget.set_sensitive(True) | ||||
| 			try: | ||||
| 				self.data_form_widget.data_form=self.dataform | ||||
| 			except dataforms.Error: | ||||
| 				# TODO: translate | ||||
| 				self.stage5(error='Service sent malformed data', senderror=True) | ||||
| 			self.data_form_widget.show() | ||||
| 		else: | ||||
| 			self.data_form_widget.hide() | ||||
| 
 | ||||
| 		action = command.getTag('action') | ||||
| 		if action is None: | ||||
| 			self.cancel_button.set_sensitive(True) | ||||
| 			self.back_button.set_sensitive(False) | ||||
| 			self.forward_button.set_sensitive(False) | ||||
| 			self.execute_button.set_sensitive(True) | ||||
| 		else: | ||||
| 			# actions, actions, actions... | ||||
| 			self.cancel_button.set_sensitive(True) | ||||
| 			self.back_button.set_sensitive(action.getTag('prev') is not None) | ||||
| 			self.forward_button.set_sensitive(action.getTag('next') is not None) | ||||
| 			self.execute_button.set_sensitive(True) | ||||
| 
 | ||||
| 		if self.form_status == 'completed': | ||||
| 			self.cancel_button.set_sensitive(False) | ||||
| 			self.back_button.set_sensitive(False) | ||||
| 			self.forward_button.set_sensitive(False) | ||||
| 			self.execute_button.set_no_show_all(True) | ||||
| 			self.execute_button.hide() | ||||
| 			self.close_button.set_no_show_all(False) | ||||
| 			self.close_button.show() | ||||
| 			self.stage_adhoc_commands_window_delete_event = self.stage3_close_button_clicked | ||||
| 
 | ||||
| 		note = command.getTag('note') | ||||
| 		if note is not None: | ||||
| 			self.notes_label.set_text(note.getData().decode('utf-8')) | ||||
| 			self.notes_label.set_no_show_all(False) | ||||
| 			self.notes_label.show() | ||||
| 		else: | ||||
| 			self.notes_label.set_no_show_all(True) | ||||
| 			self.notes_label.hide() | ||||
| 
 | ||||
| # stage 4: no commands are exposed | ||||
| 	def stage4(self): | ||||
| 		'''Display the message. Wait for user to close the window''' | ||||
| 		# close old stage | ||||
| 		self.stage_finish() | ||||
| 
 | ||||
| 		self.stages_notebook.set_current_page( | ||||
| 			self.stages_notebook.page_num( | ||||
| 				self.no_commands_stage_vbox)) | ||||
| 
 | ||||
| 		self.cancel_button.set_sensitive(True) | ||||
| 		self.back_button.set_sensitive(False) | ||||
| 		self.forward_button.set_sensitive(False) | ||||
| 		self.execute_button.set_sensitive(False) | ||||
| 
 | ||||
| 		self.stage_finish = self.do_nothing | ||||
| 		self.stage_cancel_button_clicked = self.stage4_cancel_button_clicked | ||||
| 		self.stage_adhoc_commands_window_delete_event = self.do_nothing | ||||
| 
 | ||||
| 	def stage4_cancel_button_clicked(self, widget): | ||||
| 		self.window.destroy() | ||||
| 
 | ||||
| 	def on_check_commands_2_button_clicked(self, widget): | ||||
| 		self.stage1() | ||||
| 
 | ||||
| # stage 5: an error has occured | ||||
| 	def stage5(self, error=None, errorid=None, senderror=False): | ||||
| 		'''Display the error message. Wait for user to close the window''' | ||||
| 		# TODO: sending error to responder | ||||
| 		# close old stage | ||||
| 		self.stage_finish() | ||||
| 
 | ||||
| 		assert errorid is not None or error is not None | ||||
| 
 | ||||
| 		if errorid is not None: | ||||
| 			# we've got error code, display appropriate message | ||||
| 			errorname = xmpp.NS_STANZAS + ' ' + str(errorid) | ||||
| 			errordesc = xmpp.ERRORS[errorname][2] | ||||
| 			error = errordesc.decode('utf-8') | ||||
| 			del errorname, errordesc | ||||
| 		elif error is not None: | ||||
| 			# we've got error message | ||||
| 			pass | ||||
| 		else: | ||||
| 			# we don't know what's that, bailing out | ||||
| 			assert False | ||||
| 
 | ||||
| 		self.stages_notebook.set_current_page( | ||||
| 			self.stages_notebook.page_num( | ||||
| 				self.error_stage_vbox)) | ||||
| 
 | ||||
| 		self.cancel_button.set_sensitive(True) | ||||
| 		self.back_button.set_sensitive(False) | ||||
| 		self.forward_button.set_sensitive(False) | ||||
| 		self.execute_button.set_sensitive(False) | ||||
| 
 | ||||
| 		self.error_description_label.set_text(error) | ||||
| 
 | ||||
| 		self.stage_finish = self.do_nothing | ||||
| 		self.stage_cancel_button_clicked = self.stage5_cancel_button_clicked | ||||
| 		self.stage_adhoc_commands_window_delete_event = self.do_nothing | ||||
| 
 | ||||
| 	def stage5_cancel_button_clicked(self, widget): | ||||
| 		self.window.destroy() | ||||
| 
 | ||||
| # helpers to handle pulsing in progressbar | ||||
| 	def setup_pulsing(self, progressbar): | ||||
| 		'''Set the progressbar to pulse. Makes a custom | ||||
| 		function to repeatedly call progressbar.pulse() method.''' | ||||
| 		assert self.pulse_id is None | ||||
| 		assert isinstance(progressbar, gtk.ProgressBar) | ||||
| 
 | ||||
| 		def callback(): | ||||
| 			progressbar.pulse() | ||||
| 			return True	# important to keep callback be called back! | ||||
| 
 | ||||
| 		# 12 times per second (80 miliseconds) | ||||
| 		self.pulse_id = gobject.timeout_add(80, callback) | ||||
| 
 | ||||
| 	def remove_pulsing(self): | ||||
| 		'''Stop pulsing, useful when especially when removing widget.''' | ||||
| 		if self.pulse_id is not None: | ||||
| 			gobject.source_remove(self.pulse_id) | ||||
| 		self.pulse_id=None | ||||
| 
 | ||||
| # handling xml stanzas | ||||
| 	def request_command_list(self): | ||||
| 		'''Request the command list. Change stage on delivery.''' | ||||
| 		query = xmpp.Iq(typ='get', to=xmpp.JID(self.jid), xmlns=xmpp.NS_DISCO_ITEMS) | ||||
| 		query.setQuerynode(xmpp.NS_COMMANDS) | ||||
| 
 | ||||
| 		def callback(response): | ||||
| 			'''Called on response to query.''' | ||||
| 			# TODO: move to connection_handlers.py | ||||
| 			# is error => error stage | ||||
| 			error = response.getError() | ||||
| 			if error is not None: | ||||
| 				# extracting error description from xmpp/protocol.py | ||||
| 				self.stage5(errorid = error) | ||||
| 				return | ||||
| 
 | ||||
| 			# no commands => no commands stage | ||||
| 			# commands => command selection stage | ||||
| 			items = response.getTag('query').getTags('item') | ||||
| 			if len(items)==0: | ||||
| 				self.commandlist = [] | ||||
| 				self.stage4() | ||||
| 			else: | ||||
| 				self.commandlist = [(t.getAttr('node'), t.getAttr('name')) for t in items] | ||||
| 				self.stage2() | ||||
| 
 | ||||
| 		self.account.connection.SendAndCallForResponse(query, callback) | ||||
| 
 | ||||
| 	def send_command(self, action='execute'): | ||||
| 		'''Send the command with data form. Wait for reply.''' | ||||
| 		# create the stanza | ||||
| 		assert isinstance(self.commandnode, unicode) | ||||
| 		assert action in ('execute', 'prev', 'next', 'complete') | ||||
| 
 | ||||
| 		stanza = xmpp.Iq(typ='set', to=self.jid) | ||||
| 		cmdnode = stanza.addChild('command', attrs={ | ||||
| 				'xmlns':xmpp.NS_COMMANDS, | ||||
| 				'node':self.commandnode, | ||||
| 				'action':action | ||||
| 			}) | ||||
| 
 | ||||
| 		if self.sessionid is not None: | ||||
| 			cmdnode.setAttr('sessionid', self.sessionid) | ||||
| 
 | ||||
| 		if self.data_form_widget.data_form is not None: | ||||
| #			cmdnode.addChild(node=dataforms.DataForm(tofill=self.data_form_widget.data_form)) | ||||
| 			# TODO: simplified form to send | ||||
| 			cmdnode.addChild(node=self.data_form_widget.data_form) | ||||
| 
 | ||||
| 		def callback(response): | ||||
| 			# TODO: move to connection_handlers.py | ||||
| 			err = response.getError() | ||||
| 			if err is not None: | ||||
| 				self.stage5(errorid = err) | ||||
| 			else: | ||||
| 				self.stage3_next_form(response.getTag('command')) | ||||
| 
 | ||||
| 		self.account.connection.SendAndCallForResponse(stanza, callback) | ||||
| 
 | ||||
| 	def send_cancel(self): | ||||
| 		'''Send the command with action='cancel'. ''' | ||||
| 		assert self.commandnode is not None | ||||
| 		if self.sessionid is not None: | ||||
| 			# we already have sessionid, so the service sent at least one reply. | ||||
| 			stanza = xmpp.Iq(typ='set', to=self.jid) | ||||
| 			stanza.addChild('command', attrs={ | ||||
| 					'xmlns':xmpp.NS_COMMANDS, | ||||
| 					'node':self.commandnode, | ||||
| 					'sessionid':self.sessionid, | ||||
| 					'action':'cancel' | ||||
| 				}) | ||||
| 	 | ||||
| 			self.account.connection.send(stanza) | ||||
| 		else: | ||||
| 			# we did not received any reply from service; TODO: we should wait and | ||||
| 			# then send cancel; for now we do nothing | ||||
| 			pass | ||||
							
								
								
									
										113
									
								
								src/atom_window.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								src/atom_window.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,113 @@ | |||
| '''atom_window.py - a window to display atom entries from pubsub. For now greatly simplified, | ||||
| supports only simple feeds like the one from pubsub.com. ''' | ||||
| 
 | ||||
| import gtk | ||||
| import gtk.gdk | ||||
| 
 | ||||
| import gtkgui_helpers | ||||
| from common import helpers | ||||
| 
 | ||||
| class AtomWindow: | ||||
| 	window = None | ||||
| 	entries = [] | ||||
| 
 | ||||
| 	@classmethod				# python2.4 decorator | ||||
| 	def newAtomEntry(cls, entry): | ||||
| 		''' Queue new entry, open window if there's no one opened. ''' | ||||
| 		cls.entries.append(entry) | ||||
| 
 | ||||
| 		if cls.window is None: | ||||
| 			cls.window = AtomWindow() | ||||
| 		else: | ||||
| 			cls.window.updateCounter() | ||||
| 
 | ||||
| 	def __init__(self): | ||||
| 		''' Create new window... only if we have anything to show. ''' | ||||
| 		assert len(self.__class__.entries)>0 | ||||
| 
 | ||||
| 		self.entry = None	# the entry actually displayed | ||||
| 
 | ||||
| 		self.xml = gtkgui_helpers.get_glade('atom_entry_window.glade') | ||||
| 		self.window = self.xml.get_widget('atom_entry_window') | ||||
| 		for name in ('new_entry_label', 'feed_title_label', 'feed_title_eventbox', | ||||
| 			'feed_tagline_label', 'entry_title_label', 'entry_title_eventbox', | ||||
| 			'last_modified_label', 'close_button', 'next_button'): | ||||
| 			self.__dict__[name] = self.xml.get_widget(name) | ||||
| 
 | ||||
| 		self.displayNextEntry() | ||||
| 
 | ||||
| 		self.xml.signal_autoconnect(self) | ||||
| 		self.window.show_all() | ||||
| 
 | ||||
| 		self.entry_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) | ||||
| 		self.feed_title_eventbox.add_events(gtk.gdk.BUTTON_PRESS_MASK) | ||||
| 
 | ||||
| 	def displayNextEntry(self): | ||||
| 		''' Get next entry from the queue and display it in the window. ''' | ||||
| 		assert len(self.__class__.entries)>0 | ||||
| 
 | ||||
| 		newentry = self.__class__.entries.pop(0) | ||||
| 		 | ||||
| 		# fill the fields | ||||
| 		if newentry.feed_link is not None: | ||||
| 			self.feed_title_label.set_markup( | ||||
| 				u'<span foreground="blue" underline="single">%s</span>' % \ | ||||
| 				gtkgui_helpers.escape_for_pango_markup(newentry.feed_title)) | ||||
| 		else: | ||||
| 			self.feed_title_label.set_markup( | ||||
| 				gtkgui_helpers.escape_for_pango_markup(newentry.feed_title)) | ||||
| 
 | ||||
| 		self.feed_tagline_label.set_markup( | ||||
| 			u'<small>%s</small>' % \ | ||||
| 			gtkgui_helpers.escape_for_pango_markup(newentry.feed_tagline)) | ||||
| 
 | ||||
| 		if newentry.uri is not None: | ||||
| 			self.entry_title_label.set_markup( | ||||
| 				u'<span foreground="blue" underline="single">%s</span>' % \ | ||||
| 				gtkgui_helpers.escape_for_pango_markup(newentry.title)) | ||||
| 		else: | ||||
| 			self.entry_title_label.set_markup( | ||||
| 				gtkgui_helpers.escape_for_pango_markup(newentry.title)) | ||||
| 
 | ||||
| 		self.last_modified_label.set_text(newentry.updated) | ||||
| 
 | ||||
| 		# update the counters | ||||
| 		self.updateCounter() | ||||
| 
 | ||||
| 		self.entry = newentry | ||||
| 
 | ||||
| 	def updateCounter(self): | ||||
| 		''' We display number of events on the top of window, sometimes it needs to be | ||||
| 		changed...''' | ||||
| 		count = len(self.__class__.entries) | ||||
| 		# TODO: translate | ||||
| 		if count>0: | ||||
| 			self.new_entry_label.set_text( \ | ||||
| 				'You have received new entries (and %(count)d not displayed):' % \ | ||||
| 				{'count': count}) | ||||
| 			self.next_button.set_sensitive(True) | ||||
| 		else: | ||||
| 			self.new_entry_label.set_text('You have received new entry:') | ||||
| 			self.next_button.set_sensitive(False) | ||||
| 
 | ||||
| 	def on_close_button_clicked(self, widget): | ||||
| 		self.window.destroy() | ||||
| 
 | ||||
| 	def on_next_button_clicked(self, widget): | ||||
| 		self.displayNextEntry() | ||||
| 
 | ||||
| 	def on_entry_title_button_press_event(self, widget, event): | ||||
| 		# TODO: make it using special gtk2.10 widget | ||||
| 		if event.button == 1:	# left click | ||||
| 			uri = self.entry.uri | ||||
| 			if uri is not None: | ||||
| 				helpers.launch_browser_mailer('url', uri) | ||||
| 		return True | ||||
| 
 | ||||
| 	def on_feed_title_button_press_event(self, widget, event): | ||||
| 		# TODO: make it using special gtk2.10 widget | ||||
| 		if event.button == 1:	# left click | ||||
| 			uri = self.entry.feed_uri | ||||
| 			if uri is not None: | ||||
| 				helpers.launch_browser_mailer('url', uri) | ||||
| 		return True | ||||
							
								
								
									
										138
									
								
								src/common/atom.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/common/atom.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,138 @@ | |||
| # -*- coding: utf-8 -*- | ||||
| ''' Atom (rfc 4287) feed parser, used to read data from atom-over-pubsub transports | ||||
| and services. Very simple. Actually implements only atom:entry. Implement more features | ||||
| if you need. ''' | ||||
| 
 | ||||
| # suggestion: rewrite functions that return dates to return standard python time tuples, | ||||
| # exteneded to contain timezone | ||||
| 
 | ||||
| import xmpp | ||||
| import time | ||||
| 
 | ||||
| class PersonConstruct(xmpp.Node, object): | ||||
| 	''' Not used for now, as we don't need authors/contributors in pubsub.com feeds. | ||||
| 	They rarely exist there. ''' | ||||
| 	def __init__(self, node): | ||||
| 		''' Create person construct from node. ''' | ||||
| 		xmpp.Node.__init__(self, node=node) | ||||
| 
 | ||||
| 	def get_name(self): | ||||
| 		return self.getTagData('name') | ||||
| 
 | ||||
| 	name = property(get_name, None, None, | ||||
| 		'''Conveys a human-readable name for the person. Should not be None, | ||||
| 		although some badly generated atom feeds don't put anything here | ||||
| 		(this is non-standard behavior, still pubsub.com sometimes does that.)''') | ||||
| 
 | ||||
| 	def get_uri(self): | ||||
| 		return self.getTagData('uri') | ||||
| 
 | ||||
| 	uri = property(get_uri, None, None, | ||||
| 		'''Conveys an IRI associated with the person. Might be None when not set.''') | ||||
| 
 | ||||
| 	def get_email(self): | ||||
| 		return self.getTagData('email') | ||||
| 
 | ||||
| 	email = property(get_email, None, None, | ||||
| 		'''Conveys an e-mail address associated with the person. Might be None when | ||||
| 		not set.''') | ||||
| 
 | ||||
| class Entry(xmpp.Node, object): | ||||
| 	def __init__(self, node=None): | ||||
| 		''' Create new atom entry object. ''' | ||||
| 		xmpp.Node.__init__(self, 'entry', node=node) | ||||
| 
 | ||||
| 	def __repr__(self): | ||||
| 		return '<Atom:Entry object of id="%r">' % self.id | ||||
| 
 | ||||
| class OldEntry(xmpp.Node, object): | ||||
| 	''' Parser for feeds from pubsub.com. They use old Atom 0.3 format with | ||||
| 	their extensions. ''' | ||||
| 	def __init__(self, node=None): | ||||
| 		''' Create new Atom 0.3 entry object. ''' | ||||
| 		xmpp.Node.__init__(self, 'entry', node=node) | ||||
| 
 | ||||
| 	def __repr__(self): | ||||
| 		return '<Atom0.3:Entry object of id="%r">' % self.id | ||||
| 
 | ||||
| 	def get_feed_title(self): | ||||
| 		''' Returns title of feed, where the entry was created. The result is the feed name | ||||
| 		concatenated with source-feed title. ''' | ||||
| 		title = u'' | ||||
| 
 | ||||
| 		if self.parent is not None: | ||||
| 			main_feed = self.parent.getTagData('title') | ||||
| 		else: | ||||
| 			main_feed = None | ||||
| 
 | ||||
| 		if self.getTag('source-feed') is not None: | ||||
| 			source_feed = self.getTag('source-feed').getTagData('title') | ||||
| 		else: | ||||
| 			source_feed = None | ||||
| 
 | ||||
| 		if main_feed is not None and source_feed is not None: | ||||
| 			return u'%s: %s' % (main_feed, source_feed) | ||||
| 		elif main_feed is not None: | ||||
| 			return main_feed | ||||
| 		elif source_feed is not None: | ||||
| 			return source_feed | ||||
| 		else: | ||||
| 			return u'' | ||||
| 
 | ||||
| 	feed_title = property(get_feed_title, None, None,  | ||||
| 		''' Title of feed. It is built from entry's original feed title and title of feed | ||||
| 		which delivered this entry. ''') | ||||
| 
 | ||||
| 	def get_feed_link(self): | ||||
| 		''' Get a link to main page of feed (in pubsub.com: second link of rel='alternate', | ||||
| 		first contains raw xml data). ''' | ||||
| 		try: | ||||
| 			return self.getTag('source-feed').getTags('link', {'rel':'alternate'})[1].getData() | ||||
| 		except: | ||||
| 			return None | ||||
| 
 | ||||
| 	feed_link = property(get_feed_link, None, None, | ||||
| 		''' Link to main webpage of the feed. ''') | ||||
| 
 | ||||
| 	def get_title(self): | ||||
| 		''' Get an entry's title. ''' | ||||
| 		return self.getTagData('title') | ||||
| 
 | ||||
| 	title = property(get_title, None, None, | ||||
| 		''' Entry's title. ''') | ||||
| 
 | ||||
| 	def get_uri(self): | ||||
| 		''' Get the uri the entry points to (entry's first link element with rel='alternate' | ||||
| 		or without rel attribute). ''' | ||||
| 		for element in self.getTags('link'): | ||||
| 			if element.attrs.has_key('rel') and element.attrs['rel']<>'alternate': continue | ||||
| 			try: | ||||
| 				return element.attrs['href'] | ||||
| 			except AttributeError: | ||||
| 				pass | ||||
| 		return None | ||||
| 
 | ||||
| 	uri = property(get_uri, None, None, | ||||
| 		''' URI that is pointed by the entry. ''') | ||||
| 
 | ||||
| 	def get_updated(self): | ||||
| 		''' Get the time the entry was updated last time. This should be standarized, | ||||
| 		but pubsub.com sends it in human-readable format. We won't try to parse it. | ||||
| 		(Atom 0.3 uses the word	«modified» for that). | ||||
| 
 | ||||
| 		If there's no time given in the entry, we try with <published> | ||||
| 		and <issued> elements. ''' | ||||
| 		for name in ('updated', 'modified', 'published', 'issued'): | ||||
| 			date = self.getTagData(name) | ||||
| 			if date is not None: break | ||||
| 
 | ||||
| 		if date is None: | ||||
| 			# it is not in the standard format | ||||
| 			return time.asctime() | ||||
| 
 | ||||
| 		return date | ||||
| 
 | ||||
| 	updated = property(get_updated, None, None,  | ||||
| 		''' Last significant modification time. ''') | ||||
| 
 | ||||
| 	feed_tagline = u'' | ||||
							
								
								
									
										252
									
								
								src/common/commands.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										252
									
								
								src/common/commands.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,252 @@ | |||
| ## | ||||
| ## Copyright (C) 2006 Gajim Team | ||||
| ## | ||||
| ## This program is free software; you can redistribute it and/or modify | ||||
| ## it under the terms of the GNU General Public License as published | ||||
| ## by the Free Software Foundation; version 2 only. | ||||
| ## | ||||
| ## This program is distributed in the hope that it will be useful, | ||||
| ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| ## GNU General Public License for more details. | ||||
| ## | ||||
| 
 | ||||
| import xmpp | ||||
| import helpers | ||||
| import dataforms | ||||
| import gajim | ||||
| 
 | ||||
| class AdHocCommand: | ||||
| 	commandnode = 'command' | ||||
| 	commandname = 'The Command' | ||||
| 	commandfeatures = (xmpp.NS_DATA,) | ||||
| 
 | ||||
| 	@staticmethod | ||||
| 	def isVisibleFor(samejid): | ||||
| 		''' This returns True if that command should be visible and invokable | ||||
| 		for others. | ||||
| 		samejid - True when command is invoked by an entity with the same bare jid. | ||||
| 		''' | ||||
| 		return True | ||||
| 
 | ||||
| 	def __init__(self, conn, jid, sessionid): | ||||
| 		self.connection = conn | ||||
| 		self.jid = jid | ||||
| 		self.sessionid = sessionid | ||||
| 
 | ||||
| 	def buildResponse(self, request, status='executing', defaultaction=None, actions=None): | ||||
| 		assert status in ('executing', 'completed', 'canceled') | ||||
| 
 | ||||
| 		response = request.buildReply('result') | ||||
| 		cmd = response.addChild('command', { | ||||
| 			'xmlns': xmpp.NS_COMMANDS, | ||||
| 			'sessionid': self.sessionid, | ||||
| 			'node': self.commandnode, | ||||
| 			'status': status}) | ||||
| 		if defaultaction is not None or actions is not None: | ||||
| 			if defaultaction is not None: | ||||
| 				assert defaultaction in ('cancel', 'execute', 'prev', 'next', 'complete') | ||||
| 				attrs = {'action': defaultaction} | ||||
| 			else: | ||||
| 				attrs = {} | ||||
| 
 | ||||
| 			cmd.addChild('actions', attrs, actions) | ||||
| 		return response, cmd | ||||
| 
 | ||||
| 	def badRequest(self, stanza): | ||||
| 		self.connection.connection.send(xmpp.Error(stanza, xmpp.NS_STANZAS+' bad-request')) | ||||
| 
 | ||||
| 	def cancel(self, request): | ||||
| 		response, cmd = self.buildResponse(request, status='canceled') | ||||
| 		self.connection.connection.send(response) | ||||
| 		return False	# finish the session | ||||
| 
 | ||||
| class ChangeStatusCommand(AdHocCommand): | ||||
| 	commandnode = 'change-status' | ||||
| 	commandname = 'Change status information' | ||||
| 
 | ||||
| 	@staticmethod | ||||
| 	def isVisibleFor(samejid): | ||||
| 		''' Change status is visible only if the entity has the same bare jid. ''' | ||||
| 		return True	# TODO: Remove that! | ||||
| 		return samejid | ||||
| 
 | ||||
| 	def execute(self, request): | ||||
| 		# first query... | ||||
| 		response, cmd = self.buildResponse(request, defaultaction='execute', actions=['execute']) | ||||
| 		 | ||||
| 		cmd.addChild(node=dataforms.SimpleDataForm( | ||||
| 			title='Change status', | ||||
| 			instructions='Set the presence type and description', | ||||
| 			fields=[ | ||||
| 				dataforms.Field('list-single', | ||||
| 					var='presence-type', | ||||
| 					label='Type of presence:', | ||||
| 					options=[ | ||||
| 						(u'free-for-chat', u'Free for chat'), | ||||
| 						(u'online', u'Online'), | ||||
| 						(u'away', u'Away'), | ||||
| 						(u'xa', u'Extended away'), | ||||
| 						(u'dnd', u'Do not disturb'), | ||||
| 						(u'offline', u'Offline - disconnect')], | ||||
| 					value='online', | ||||
| 					required=True), | ||||
| 				dataforms.Field('text-multi', | ||||
| 					var='presence-desc', | ||||
| 					label='Presence description:')])) | ||||
| 
 | ||||
| 		self.connection.connection.send(response) | ||||
| 
 | ||||
| 		# for next invocation | ||||
| 		self.execute = self.changestatus | ||||
| 		 | ||||
| 		return True	# keep the session | ||||
| 
 | ||||
| 	def changestatus(self, request): | ||||
| 		# check if the data is correct | ||||
| 		try: | ||||
| 			form=dataforms.SimpleDataForm(extend=request.getTag('command').getTag('x')) | ||||
| 		except: | ||||
| 			self.badRequest(request) | ||||
| 			return False | ||||
| 		 | ||||
| 		try: | ||||
| 			presencetype = form['presence-type'].value | ||||
| 			if not presencetype in \ | ||||
| 			    ('free-for-chat', 'online', 'away', 'xa', 'dnd', 'offline'): | ||||
| 				self.badRequest(request) | ||||
| 				return False | ||||
| 		except:	# KeyError if there's no presence-type field in form or | ||||
| 			# AttributeError if that field is of wrong type | ||||
| 			self.badRequest(request) | ||||
| 			return False | ||||
| 
 | ||||
| 		try: | ||||
| 			presencedesc = form['presence-desc'].value | ||||
| 		except:	# same exceptions as in last comment | ||||
| 			presencedesc = u'' | ||||
| 
 | ||||
| 		response, cmd = self.buildResponse(request, status='completed') | ||||
| 		cmd.addChild('note', {}, 'The status has been changed.') | ||||
| 
 | ||||
| 		self.connection.connection.send(response) | ||||
| 
 | ||||
| 		# send new status | ||||
| 		gajim.interface.roster.send_status(self.connection.name, presencetype, presencedesc) | ||||
| 
 | ||||
| 		return False	# finish the session | ||||
| 
 | ||||
| class ConnectionCommands: | ||||
| 	''' This class depends on that it is a part of Connection() class. ''' | ||||
| 	def __init__(self): | ||||
| 		# a list of all commands exposed: node -> command class | ||||
| 		self.__commands = {} | ||||
| 		for cmdobj in (ChangeStatusCommand,): | ||||
| 			self.__commands[cmdobj.commandnode] = cmdobj | ||||
| 
 | ||||
| 		# a list of sessions; keys are tuples (jid, sessionid, node) | ||||
| 		self.__sessions = {} | ||||
| 
 | ||||
| 	def getOurBareJID(self): | ||||
| 		return gajim.get_jid_from_account(self.name) | ||||
| 
 | ||||
| 	def isSameJID(self, jid): | ||||
| 		''' Tests if the bare jid given is the same as our bare jid. ''' | ||||
| 		return xmpp.JID(jid).getStripped() == self.getOurBareJID() | ||||
| 
 | ||||
| 	def commandListQuery(self, con, iq_obj): | ||||
| 		iq = iq_obj.buildReply('result') | ||||
| 		jid = helpers.get_full_jid_from_iq(iq_obj) | ||||
| 		q = iq.getTag('query') | ||||
| 
 | ||||
| 		for node, cmd in self.__commands.iteritems(): | ||||
| 			if cmd.isVisibleFor(self.isSameJID(jid)): | ||||
| 				q.addChild('item', { | ||||
| 					# TODO: find the jid | ||||
| 					'jid': self.getOurBareJID()+u'/'+self.server_resource, | ||||
| 					'node': node, | ||||
| 					'name': cmd.commandname}) | ||||
| 
 | ||||
| 		self.connection.send(iq) | ||||
| 
 | ||||
| 	def commandQuery(self, con, iq_obj): | ||||
| 		''' Send disco result for query for command (JEP-0050, example 6.). | ||||
| 		Return True if the result was sent, False if not. ''' | ||||
| 		jid = helpers.get_full_jid_from_iq(iq_obj) | ||||
| 		node = iq_obj.getTagAttr('query', 'node') | ||||
| 
 | ||||
| 		if node not in self.__commands: return False | ||||
| 
 | ||||
| 		cmd = self.__commands[node] | ||||
| 		if cmd.isVisibleFor(self.isSameJID(jid)): | ||||
| 			iq = iq_obj.buildReply('result') | ||||
| 			q = iq.getTag('query') | ||||
| 			q.addChild('identity', attrs = {'type': 'command-node', | ||||
| 			                                'category': 'automation', | ||||
| 			                                'name': cmd.commandname}) | ||||
| 			q.addChild('feature', attrs = {'var': xmpp.NS_COMMANDS}) | ||||
| 			for feature in cmd.commandfeatures: | ||||
| 				q.addChild('feature', attrs = {'var': feature}) | ||||
| 
 | ||||
| 			self.connection.send(iq) | ||||
| 			return True | ||||
| 
 | ||||
| 		return False | ||||
| 
 | ||||
| 	def _CommandExecuteCB(self, con, iq_obj): | ||||
| 		jid = helpers.get_full_jid_from_iq(iq_obj) | ||||
| 
 | ||||
| 		cmd = iq_obj.getTag('command') | ||||
| 		if cmd is None: return | ||||
| 
 | ||||
| 		node = cmd.getAttr('node') | ||||
| 		if node is None: return | ||||
| 
 | ||||
| 		sessionid = cmd.getAttr('sessionid') | ||||
| 		if sessionid is None: | ||||
| 			# we start a new command session... only if we are visible for the jid | ||||
| 			newcmd = self.__commands[node] | ||||
| 			if not newcmd.isVisibleFor(self.isSameJID(jid)): | ||||
| 				return | ||||
| 
 | ||||
| 			# generate new sessionid | ||||
| 			sessionid = self.connection.getAnID() | ||||
| 
 | ||||
| 			# create new instance and run it | ||||
| 			obj = newcmd(conn=self, jid=jid, sessionid=sessionid) | ||||
| 			rc = obj.execute(iq_obj) | ||||
| 			if rc: | ||||
| 				self.__sessions[(jid, sessionid, node)] = obj | ||||
| 			raise xmpp.NodeProcessed | ||||
| 		else: | ||||
| 			# the command is already running, check for it | ||||
| 			magictuple = (jid, sessionid, node) | ||||
| 			if magictuple not in self.__sessions: | ||||
| 				# we don't have this session... ha! | ||||
| 				return | ||||
| 
 | ||||
| 			action = cmd.getAttr('action') | ||||
| 			obj = self.__sessions[magictuple] | ||||
| 
 | ||||
| 			try: | ||||
| 				if action == 'cancel':		rc = obj.cancel(iq_obj) | ||||
| 				elif action == 'prev':		rc = obj.prev(iq_obj) | ||||
| 				elif action == 'next':		rc = obj.next(iq_obj) | ||||
| 				elif action == 'execute' or action is None: | ||||
| 								rc = obj.execute(iq_obj) | ||||
| 				elif action == 'complete':	rc = obj.complete(iq_obj) | ||||
| 				else: | ||||
| 					# action is wrong. stop the session, send error | ||||
| 					raise AttributeError | ||||
| 			except AttributeError: | ||||
| 				# the command probably doesn't handle invoked action... | ||||
| 				# stop the session, return error | ||||
| 				del self.__sessions[magictuple] | ||||
| 				return | ||||
| 
 | ||||
| 			# delete the session if rc is False | ||||
| 			if not rc: | ||||
| 				del self.__sessions[magictuple] | ||||
| 
 | ||||
| 			raise xmpp.NodeProcessed | ||||
| 
 | ||||
|  | @ -33,6 +33,10 @@ import common.xmpp | |||
| from common import GnuPG | ||||
| from common import helpers | ||||
| from common import gajim | ||||
| from common import dataforms | ||||
| from common import atom | ||||
| from common.commands import ConnectionCommands | ||||
| from common.pubsub import ConnectionPubSub | ||||
| 
 | ||||
| STATUS_LIST = ['offline', 'connecting', 'online', 'chat', 'away', 'xa', 'dnd', | ||||
| 	'invisible'] | ||||
|  | @ -661,6 +665,10 @@ class ConnectionDisco: | |||
| 		feature = common.xmpp.Node('feature') | ||||
| 		feature.setAttr('var', common.xmpp.NS_FILE) | ||||
| 		query.addChild(node=feature) | ||||
| 		# exposing adhoc commands | ||||
| 		feature = common.xmpp.Node('feature') | ||||
| 		feature.setAttr('var', common.xmpp.NS_COMMANDS) | ||||
| 		query.addChild(node=feature) | ||||
| 		 | ||||
| 		self.connection.send(iq) | ||||
| 		raise common.xmpp.NodeProcessed | ||||
|  | @ -705,20 +713,34 @@ class ConnectionDisco: | |||
| 		else: | ||||
| 			self.dispatch('AGENT_INFO_ITEMS', (jid, node, items)) | ||||
| 
 | ||||
| 	def _DiscoverItemsGetCB(self, con, iq_obj): | ||||
| 		gajim.log.debug('DiscoverItemsGetCB') | ||||
| 		node = iq_obj.getTagAttr('query', 'node') | ||||
| 		if node==common.xmpp.NS_COMMANDS: | ||||
| 			self.commandListQuery(con, iq_obj) | ||||
| 			raise common.xmpp.NodeProcessed | ||||
| 
 | ||||
| 	def _DiscoverInfoGetCB(self, con, iq_obj): | ||||
| 		gajim.log.debug('DiscoverInfoGetCB') | ||||
| 		iq = iq_obj.buildReply('result') | ||||
| 		q = iq.getTag('query') | ||||
| 		q.addChild('identity', attrs = {'type': 'pc', | ||||
| 						'category': 'client', | ||||
| 						'name': 'Gajim'}) | ||||
| 		q.addChild('feature', attrs = {'var': common.xmpp.NS_BYTESTREAM}) | ||||
| 		q.addChild('feature', attrs = {'var': common.xmpp.NS_SI}) | ||||
| 		q.addChild('feature', attrs = {'var': common.xmpp.NS_FILE}) | ||||
| 		q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC}) | ||||
| 		q.addChild('feature', attrs = {'var': common.xmpp.NS_XHTML_IM}) | ||||
| 		self.connection.send(iq) | ||||
| 		raise common.xmpp.NodeProcessed | ||||
| 		q = iq_obj.getTag('query') | ||||
| 		node = q.getAttr('node') | ||||
| 
 | ||||
| 		if self.commandQuery(con, iq_obj): | ||||
| 			raise NodeProcessed | ||||
| 		 | ||||
| 		elif node is None: | ||||
| 			iq = iq_obj.buildReply('result') | ||||
| 			q = iq.getTag('query') | ||||
| 			q.addChild('identity', attrs = {'type': 'pc', | ||||
| 							'category': 'client', | ||||
| 							'name': 'Gajim'}) | ||||
| 			q.addChild('feature', attrs = {'var': common.xmpp.NS_BYTESTREAM}) | ||||
| 			q.addChild('feature', attrs = {'var': common.xmpp.NS_SI}) | ||||
| 			q.addChild('feature', attrs = {'var': common.xmpp.NS_FILE}) | ||||
| 			q.addChild('feature', attrs = {'var': common.xmpp.NS_MUC}) | ||||
| 			q.addChild('feature', attrs = {'var': common.xmpp.NS_XHTML_IM}) | ||||
| 			self.connection.send(iq) | ||||
| 			raise common.xmpp.NodeProcessed | ||||
| 
 | ||||
| 	def _DiscoverInfoErrorCB(self, con, iq_obj): | ||||
| 		gajim.log.debug('DiscoverInfoErrorCB') | ||||
|  | @ -1103,11 +1125,12 @@ class ConnectionVcard: | |||
| 			else: | ||||
| 				self.dispatch('VCARD', vcard) | ||||
| 
 | ||||
| 
 | ||||
| class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco): | ||||
| class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub): | ||||
| 	def __init__(self): | ||||
| 		ConnectionVcard.__init__(self) | ||||
| 		ConnectionBytestream.__init__(self) | ||||
| 		ConnectionCommands.__init__(self) | ||||
| 		ConnectionPubSub.__init__(self) | ||||
| 		# List of IDs we are waiting answers for {id: (type_of_request, data), } | ||||
| 		self.awaiting_answers = {} | ||||
| 		# List of IDs that will produce a timeout is answer doesn't arrive | ||||
|  | @ -1333,6 +1356,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco) | |||
| 
 | ||||
| 	def _messageCB(self, con, msg): | ||||
| 		'''Called when we receive a message''' | ||||
| 		# check if the message is pubsub#event | ||||
| 		if msg.getTag('event') is not None: | ||||
| 			self._pubsubEventCB(con, msg) | ||||
| 			return | ||||
| 		msgtxt = msg.getBody() | ||||
| 		msghtml = msg.getXHTML() | ||||
| 		mtype = msg.getType() | ||||
|  | @ -1449,6 +1476,32 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco) | |||
| 				subject, chatstate, msg_id, composing_jep, user_nick, msghtml)) | ||||
| 	# END messageCB | ||||
| 
 | ||||
| 	def _pubsubEventCB(self, con, msg): | ||||
| 		''' Called when we receive <message/> with pubsub event. ''' | ||||
| 		# TODO: Logging? (actually services where logging would be useful, should | ||||
| 		# TODO: allow to access archives remotely...) | ||||
| 		event = msg.getTag('event') | ||||
| 
 | ||||
| 		items = event.getTag('items') | ||||
| 		if items is None: return | ||||
| 
 | ||||
| 		for item in items.getTags('item'): | ||||
| 			# check for event type (for now only one type supported: pubsub.com events) | ||||
| 			child = item.getTag('pubsub-message') | ||||
| 			if child is not None: | ||||
| 				# we have pubsub.com notification | ||||
| 				child = child.getTag('feed') | ||||
| 				if child is None: continue | ||||
| 
 | ||||
| 				for entry in child.getTags('entry'): | ||||
| 					# for each entry in feed (there shouldn't be more than one, | ||||
| 					# but to be sure... | ||||
| 					self.dispatch('ATOM_ENTRY', (atom.OldEntry(node=entry),)) | ||||
| 				continue | ||||
| 			# unknown type... probably user has another client who understands that event | ||||
| 		 | ||||
| 		raise common.xmpp.NodeProcessed | ||||
| 
 | ||||
| 	def _presenceCB(self, con, prs): | ||||
| 		'''Called when we receive a presence''' | ||||
| 		ptype = prs.getType() | ||||
|  | @ -1918,12 +1971,17 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco) | |||
| 			common.xmpp.NS_PRIVATE) | ||||
| 		con.RegisterHandler('iq', self._HttpAuthCB, 'get', | ||||
| 			common.xmpp.NS_HTTP_AUTH) | ||||
| 		con.RegisterHandler('iq', self._CommandExecuteCB, 'set', | ||||
| 			common.xmpp.NS_COMMANDS) | ||||
| 		con.RegisterHandler('iq', self._gMailNewMailCB, 'set', | ||||
| 			common.xmpp.NS_GMAILNOTIFY) | ||||
| 		con.RegisterHandler('iq', self._gMailQueryCB, 'result', | ||||
| 			common.xmpp.NS_GMAILNOTIFY) | ||||
| 		con.RegisterHandler('iq', self._DiscoverInfoGetCB, 'get', | ||||
| 			common.xmpp.NS_DISCO_INFO) | ||||
| 		con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get', | ||||
| 			common.xmpp.NS_DISCO_ITEMS) | ||||
| 		con.RegisterHandler('iq', self._PubSubCB, 'result') | ||||
| 		con.RegisterHandler('iq', self._ErrorCB, 'error') | ||||
| 		con.RegisterHandler('iq', self._IqCB) | ||||
| 		con.RegisterHandler('iq', self._StanzaArrivedCB) | ||||
|  |  | |||
							
								
								
									
										424
									
								
								src/common/dataforms.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										424
									
								
								src/common/dataforms.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,424 @@ | |||
| # this will go to src/common/xmpp later, for now it is in src/common | ||||
| """ This module contains wrappers for different parts of data forms (JEP 0004). For information | ||||
| how to use them, read documentation. """ | ||||
| 
 | ||||
| import xmpp | ||||
| 
 | ||||
| # exceptions used in this module | ||||
| class Error(Exception): pass	# base class | ||||
| class UnknownDataForm(Error): pass	# when we get xmpp.Node which we do not understand | ||||
| class WrongFieldValue(Error): pass	# when we get xmpp.Node which contains bad fields | ||||
| 
 | ||||
| # helper class to change class of already existing object | ||||
| class ExtendedNode(xmpp.Node, object): | ||||
| 	@classmethod | ||||
| 	def __new__(cls,  *a, **b): | ||||
| 		if 'extend' not in b.keys(): | ||||
| 			return object.__new__(cls) | ||||
| 
 | ||||
| 		extend = b['extend'] | ||||
| 		assert issubclass(cls, extend.__class__) | ||||
| 		extend.__class__ = cls | ||||
| 		return extend | ||||
| 
 | ||||
| # helper decorator to create properties in cleaner way | ||||
| def nested_property(f): | ||||
| 	ret = f() | ||||
| 	p = {'doc': f.__doc__} | ||||
| 	for v in ('fget', 'fset', 'fdel', 'doc'): | ||||
| 		if v in ret.keys(): p[v]=ret[v] | ||||
| 	return property(**p) | ||||
| 
 | ||||
| # helper to create fields from scratch | ||||
| def Field(typ, **attrs): | ||||
| 	''' Helper function to create a field of given type. ''' | ||||
| 	f = { | ||||
| 		'boolean': BooleanField, | ||||
| 		'fixed': StringField, | ||||
| 		'hidden': StringField, | ||||
| 		'text-private': StringField, | ||||
| 		'text-single': StringField, | ||||
| 		'jid-multi': ListMultiField, | ||||
| 		'jid-single': ListSingleField, | ||||
| 		'list-multi': ListMultiField, | ||||
| 		'list-single': ListSingleField, | ||||
| 		'text-multi': TextMultiField, | ||||
| 	}[typ](typ=typ, **attrs) | ||||
| 	return f | ||||
| 
 | ||||
| def ExtendField(node): | ||||
| 	''' Helper function to extend a node to field of appropriate type. ''' | ||||
| 	# TODO: move the dict out | ||||
| 	typ=node.getAttr('type') | ||||
| 	f = { | ||||
| 		'boolean': BooleanField, | ||||
| 		'fixed': StringField, | ||||
| 		'hidden': StringField, | ||||
| 		'text-private': StringField, | ||||
| 		'text-single': StringField, | ||||
| 		'jid-multi': ListMultiField, | ||||
| 		'jid-single': ListSingleField, | ||||
| 		'list-multi': ListMultiField, | ||||
| 		'list-single': ListSingleField, | ||||
| 		'text-multi': TextMultiField, | ||||
| 	}[typ](extend=node) | ||||
| 	return f | ||||
| 
 | ||||
| def ExtendForm(node): | ||||
| 	''' Helper function to extend a node to form of appropriate type. ''' | ||||
| 	if node.getTag('recorded') is not None: | ||||
| 		return MultipleDataForm(extend=node) | ||||
| 	else: | ||||
| 		return SimpleDataForm(extend=node) | ||||
| 
 | ||||
| class DataField(ExtendedNode): | ||||
| 	""" Keeps data about one field - var, field type, labels, instructions... """ | ||||
| 	def __init__(self, typ=None, var=None, value=None, label=None, desc=None, required=False, | ||||
| 		options=None, extend=None): | ||||
| 
 | ||||
| 		if extend is None: | ||||
| 			ExtendedNode.__init__(self, 'field') | ||||
| 
 | ||||
| 			self.type = typ | ||||
| 			self.var = var | ||||
| 			if value is not None:	self.value = value | ||||
| 			if label is not None:	self.label = label | ||||
| 			if desc  is not None:	self.desc = desc | ||||
| 			self.required = required | ||||
| 			self.options = options | ||||
| 
 | ||||
| 	@nested_property | ||||
| 	def type(): | ||||
| 		'''Type of field. Recognized values are: 'boolean', 'fixed', 'hidden', 'jid-multi', | ||||
| 		'jid-single', 'list-multi', 'list-single', 'text-multi', 'text-private', | ||||
| 		'text-single'. If you set this to something different, DataField will store | ||||
| 		given name, but treat all data as text-single.''' | ||||
| 		def fget(self): | ||||
| 			t = self.getAttr('type') | ||||
| 			if t is None: return 'text-single' | ||||
| 			return t | ||||
| 		def fset(self, value): | ||||
| 			assert isinstance(value, basestring) | ||||
| 			self.setAttr('type', value) | ||||
| 		return locals() | ||||
| 	 | ||||
| 	@nested_property | ||||
| 	def var(): | ||||
| 		'''Field identifier.''' | ||||
| 		def fget(self): | ||||
| 			return self.getAttr('var') | ||||
| 		def fset(self, value): | ||||
| 			assert isinstance(value, basestring) | ||||
| 			self.setAttr('var', value) | ||||
| 		def fdel(self): | ||||
| 			self.delAttr('var') | ||||
| 		return locals() | ||||
| 
 | ||||
| 	@nested_property | ||||
| 	def label(): | ||||
| 		'''Human-readable field name.''' | ||||
| 		def fget(self): | ||||
| 			return self.getAttr('label') | ||||
| 		def fset(self, value): | ||||
| 			assert isinstance(value, basestring) | ||||
| 			self.setAttr('label', value) | ||||
| 		def fdel(self): | ||||
| 			self.delAttr('label') | ||||
| 		return locals() | ||||
| 
 | ||||
| 	@nested_property | ||||
| 	def description(): | ||||
| 		'''Human-readable description of field meaning.''' | ||||
| 		def fget(self): | ||||
| 			return self.getTagData('desc') or u'' | ||||
| 		def fset(self, value): | ||||
| 			assert isinstance(value, basestring) | ||||
| 			if value == '': | ||||
| 				fdel(self) | ||||
| 			else: | ||||
| 				self.setTagData('desc', value) | ||||
| 		def fdel(self): | ||||
| 			t = self.getTag('desc') | ||||
| 			if t is not None: | ||||
| 				self.delChild(t) | ||||
| 		return locals() | ||||
| 
 | ||||
| 	@nested_property | ||||
| 	def required(): | ||||
| 		'''Controls whether this field required to fill. Boolean.''' | ||||
| 		def fget(self): | ||||
| 			return boolean(self.getTag('required')) | ||||
| 		def fset(self, value): | ||||
| 			t = self.getTag('required') | ||||
| 			if t and not value: | ||||
| 				self.delChild(t) | ||||
| 			elif not t and value: | ||||
| 				self.addChild('required') | ||||
| 		return locals() | ||||
| 
 | ||||
| class BooleanField(DataField): | ||||
| 	@nested_property | ||||
| 	def value(): | ||||
| 		'''Value of field. May contain True, False or None.''' | ||||
| 		def fget(self): | ||||
| 			v = self.getTagData('value') | ||||
| 			if v in ('0', 'false'): return False | ||||
| 			if v in ('1', 'true'): return True | ||||
| 			if v is None: return None | ||||
| 			raise WrongFieldValue | ||||
| 		def fset(self, value): | ||||
| 			self.setTagData('value', value and '1' or '0') | ||||
| 		def fdel(self, value): | ||||
| 			t = self.getTag('value') | ||||
| 			if t is not None: | ||||
| 				self.delChild(t) | ||||
| 		return locals() | ||||
| 
 | ||||
| class StringField(DataField): | ||||
| 	''' Covers fields of types: fixed, hidden, text-private, text-single. ''' | ||||
| 	@nested_property | ||||
| 	def value(): | ||||
| 		'''Value of field. May be any unicode string.''' | ||||
| 		def fget(self): | ||||
| 			return self.getTagData('value') or u'' | ||||
| 		def fset(self, value): | ||||
| 			assert isinstance(value, basestring) | ||||
| 			if value == '': | ||||
| 				return fdel(self) | ||||
| 			self.setTagData('value', value) | ||||
| 		def fdel(self): | ||||
| 			try: | ||||
| 				self.delChild(self.getTag('value')) | ||||
| 			except ValueError: # if there already were no value tag | ||||
| 				pass | ||||
| 		return locals() | ||||
| 
 | ||||
| 
 | ||||
| class ListField(DataField): | ||||
| 	''' Covers fields of types: jid-multi, jid-single, list-multi, list-single. ''' | ||||
| 	@nested_property | ||||
| 	def options(): | ||||
| 		'''Options.''' | ||||
| 		def fget(self): | ||||
| 			options = [] | ||||
| 			for element in self.getTags('option'): | ||||
| 				v = element.getTagData('value') | ||||
| 				if v is None: raise WrongFieldValue | ||||
| 				options.append((element.getAttr('label'), v)) | ||||
| 			return options | ||||
| 		def fset(self, values): | ||||
| 			fdel(self) | ||||
| 			for value, label in values: | ||||
| 				self.addChild('option', {'label': label}).setTagData('value', value) | ||||
| 		def fdel(self): | ||||
| 			for element in self.getTags('option'): | ||||
| 				self.delChild(element) | ||||
| 		return locals() | ||||
| 
 | ||||
| 	def iter_options(self): | ||||
| 		for element in self.getTags('option'):	# TODO: iter! | ||||
| 			v = element.getTagData('value') | ||||
| 			if v is None: raise WrongFieldValue | ||||
| 			yield (v, element.getAttr('label')) | ||||
| 
 | ||||
| class ListSingleField(ListField, StringField): | ||||
| 	'''Covers list-single and jid-single fields.''' | ||||
| 	pass | ||||
| 
 | ||||
| class ListMultiField(ListField): | ||||
| 	'''Covers list-multi and jid-multi fields.''' | ||||
| 	@nested_property | ||||
| 	def values(): | ||||
| 		'''Values held in field.''' | ||||
| 		def fget(self): | ||||
| 			values = [] | ||||
| 			for element in self.getTags('value'): | ||||
| 				values.append(element.getData()) | ||||
| 			return values | ||||
| 		def fset(self, values): | ||||
| 			fdel(self) | ||||
| 			for value in values: | ||||
| 				self.addChild('value').setData(value) | ||||
| 		def fdel(self): | ||||
| 			for element in self.getTags('value'): | ||||
| 				self.delChild(element) | ||||
| 		return locals() | ||||
| 	 | ||||
| 	def iter_values(): | ||||
| 		for element in self.getTags('value'): | ||||
| 			yield element.getData() | ||||
| 
 | ||||
| class TextMultiField(DataField): | ||||
| 	@nested_property | ||||
| 	def value(): | ||||
| 		'''Value held in field.''' | ||||
| 		def fget(self): | ||||
| 			value = u'' | ||||
| 			for element in self.getTags('value'):	# TODO: iter! | ||||
| 				value += '\n' + element.getData() | ||||
| 			return value[1:] | ||||
| 		def fset(self, value): | ||||
| 			fdel(self) | ||||
| 			if value == '': return | ||||
| 			for line in value.split('\n'): | ||||
| 				self.addChild('value').setData(line) | ||||
| 		def fdel(self): | ||||
| 			for element in self.getTags('value'): | ||||
| 				self.delChild(element) | ||||
| 		return locals() | ||||
| 
 | ||||
| class DataRecord(ExtendedNode): | ||||
| 	'''The container for data fields - an xml element which has DataField | ||||
| 	elements as children.''' | ||||
| 	def __init__(self, fields=None, associated=None, extend=None): | ||||
| 		self.associated = associated | ||||
| 		self.vars = {} | ||||
| 		if extend is None: | ||||
| 			# we have to build this object from scratch | ||||
| 			xmpp.Node.__init__(self) | ||||
| 
 | ||||
| 			if fields is not None: self.fields = fields | ||||
| 		else: | ||||
| 			# we already have xmpp.Node inside - try to convert all | ||||
| 			# fields into DataField objects | ||||
| 			if fields is None: | ||||
| 				for field in self.getTags('field'): # TODO: iter! | ||||
| 					if not isinstance(field, DataField): | ||||
| 						ExtendField(field) | ||||
| 					self.vars[field.var] = field | ||||
| 			else: | ||||
| 				for field in self.getTags('field'): | ||||
| 					self.delChild(field) | ||||
| 				self.fields = fields | ||||
| 
 | ||||
| 	@nested_property | ||||
| 	def fields(): | ||||
| 		'''List of fields in this record.''' | ||||
| 		def fget(self): | ||||
| 			return self.getTags('field') | ||||
| 		def fset(self, fields): | ||||
| 			fdel(self) | ||||
| 			for field in fields: | ||||
| 				if not isinstance(field, DataField): | ||||
| 					ExtendField(extend=field) | ||||
| 				self.addChild(node=field) | ||||
| 		def fdel(self): | ||||
| 			for element in self.getTags('field'): | ||||
| 				self.delChild(element) | ||||
| 		return locals() | ||||
| 
 | ||||
| 	def iter_fields(self): | ||||
| 		''' Iterate over fields in this record. Do not take associated | ||||
| 		into account. ''' | ||||
| 		for field in self.getTags('field'):	# TODO: iter! | ||||
| 			yield field | ||||
| 
 | ||||
| 	def iter_with_associated(self): | ||||
| 		''' Iterate over associated, yielding both our field and | ||||
| 		associated one together. ''' | ||||
| 		for field in self.associated.iter_fields(): | ||||
| 			yield self[field.var], field | ||||
| 
 | ||||
| 	def __getitem__(self, item): | ||||
| 		return self.vars[item] | ||||
| 
 | ||||
| class DataForm(ExtendedNode): | ||||
| 	def __init__(self, type=None, title=None, instructions=None, extend=None): | ||||
| 		if extend is None: | ||||
| 			# we have to build form from scratch | ||||
| 			xmpp.Node.__init__(self, 'x', attrs={'xmlns': xmpp.NS_DATA}) | ||||
| 
 | ||||
| 		if type is not None:		self.type=type | ||||
| 		if title is not None:		self.title=title | ||||
| 		if instructions is not None:	self.instructions=instructions | ||||
| 
 | ||||
| 	@nested_property | ||||
| 	def type(): | ||||
| 		''' Type of the form. Must be one of: 'form', 'submit', 'cancel', 'result'. | ||||
| 		'form' - this form is to be filled in; you will be able soon to do: | ||||
| 			filledform = DataForm(replyto=thisform)...''' | ||||
| 		def fget(self): | ||||
| 			return self.getAttr('type') | ||||
| 		def fset(self): | ||||
| 			assert type in ('form', 'submit', 'cancel', 'result') | ||||
| 			self.setAttr('type', type) | ||||
| 		return locals() | ||||
| 
 | ||||
| 	@nested_property | ||||
| 	def title(): | ||||
| 		''' Title of the form. Human-readable, should not contain any \\r\\n.''' | ||||
| 		def fget(self): | ||||
| 			return self.getTagData('title') | ||||
| 		def fset(self, title): | ||||
| 			self.setTagData('title', title) | ||||
| 		def fdel(self): | ||||
| 			try: | ||||
| 				self.delChild('title') | ||||
| 			except ValueError: | ||||
| 				pass | ||||
| 		return locals() | ||||
| 
 | ||||
| 	@nested_property | ||||
| 	def instructions(): | ||||
| 		''' Instructions for this form. Human-readable, may contain \\r\\n. ''' | ||||
| 		# TODO: the same code is in TextMultiField. join them | ||||
| 		def fget(self): | ||||
| 			value = u'' | ||||
| 			for value in self.getTags('value'): | ||||
| 				value += '\n' + value.getData() | ||||
| 			return value[1:] | ||||
| 		def fset(self, value): | ||||
| 			fdel(self) | ||||
| 			if value == '': return | ||||
| 			for line in value.split('\n'): | ||||
| 				self.addChild('value').setData(line) | ||||
| 		def fdel(self): | ||||
| 			for value in self.getTags('value'): | ||||
| 				self.delChild(value) | ||||
| 		return locals() | ||||
| 
 | ||||
| class SimpleDataForm(DataForm, DataRecord): | ||||
| 	def __init__(self, type=None, title=None, instructions=None, fields=None, extend=None): | ||||
| 		DataForm.__init__(self, type=type, title=title, instructions=instructions, extend=extend) | ||||
| 		DataRecord.__init__(self, fields=fields, extend=self, associated=self) | ||||
| 
 | ||||
| class MultipleDataForm(DataForm): | ||||
| 	def __init__(self): | ||||
| 		# all records, recorded into DataRecords | ||||
| 		pass | ||||
| 
 | ||||
| 	@nested_property | ||||
| 	def items(): | ||||
| 		''' A list of all records. ''' | ||||
| 		def fget(self): | ||||
| 			return list(self.iter_records()) | ||||
| 		def fset(self, records): | ||||
| 			fdel(self) | ||||
| 			for record in records: | ||||
| 				if not isinstance(record, DataRecord): | ||||
| 					DataRecord(extend=record) | ||||
| 				self.addChild(node=record) | ||||
| 		def fdel(self): | ||||
| 			for record in self.getTags('record'): | ||||
| 				self.delChild(record) | ||||
| 		return locals() | ||||
| 
 | ||||
| 	def iter_records(): | ||||
| 		for record in self.getTags('item'): | ||||
| 			yield item | ||||
| 
 | ||||
| 	@nested_property | ||||
| 	def recorded(): | ||||
| 		''' DataRecord that contains descriptions of fields in records.''' | ||||
| 		def fget(self): | ||||
| 			return self.getTag('recorded') | ||||
| 		def fset(self, record): | ||||
| 			try: | ||||
| 				self.delChild('recorded') | ||||
| 			except: | ||||
| 				pass | ||||
| 
 | ||||
| 			record.setName('recorded') | ||||
| 			self.addChild(node=record) | ||||
| 		return locals() | ||||
| 
 | ||||
							
								
								
									
										51
									
								
								src/common/pubsub.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/common/pubsub.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,51 @@ | |||
| import xmpp | ||||
| import gajim | ||||
| 
 | ||||
| class ConnectionPubSub: | ||||
| 	def __init__(self): | ||||
| 		self.__callbacks={} | ||||
| 
 | ||||
| 	def send_pb_subscription_query(self, jid, cb, *args, **kwargs): | ||||
| 		query = xmpp.Iq('get', to=jid) | ||||
| 		pb = query.addChild('pubsub', {'xmlns': xmpp.NS_PUBSUB}) | ||||
| 		pb.addChild('subscriptions') | ||||
| 
 | ||||
| 		id = self.connection.send(query) | ||||
| 
 | ||||
| 		self.__callbacks[id]=(cb, args, kwargs) | ||||
| 
 | ||||
| 	def send_pb_subscribe(self, jid, node, cb, *args, **kwargs): | ||||
| 		our_jid = gajim.get_jid_from_account(self.name) | ||||
| 		query = xmpp.Iq('set', to=jid) | ||||
| 		pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) | ||||
| 		pb.addChild('subscribe', {'node': node, 'jid': our_jid}) | ||||
| 
 | ||||
| 		id = self.connection.send(query) | ||||
| 
 | ||||
| 		self.__callbacks[id]=(cb, args, kwargs) | ||||
| 
 | ||||
| 	def send_pb_unsubscribe(self, jid, node, cb, *args, **kwargs): | ||||
| 		our_jid = gajim.get_jid_from_account(self.name) | ||||
| 		query = xmpp.Iq('set', to=jid) | ||||
| 		pb = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) | ||||
| 		pb.addChild('unsubscribe', {'node': node, 'jid': our_jid}) | ||||
| 
 | ||||
| 		id = self.connection.send(query) | ||||
| 
 | ||||
| 		self.__callbacks[id]=(cb, args, kwargs) | ||||
| 
 | ||||
| 	def send_pb_publish(self, jid, node, item, id): | ||||
| 		'''Publish item to a node.''' | ||||
| 		query = xmpp.Iq('set', to=jid) | ||||
| 		e = query.addChild('pubsub', namespace=xmpp.NS_PUBSUB) | ||||
| 		e = e.addChild('publish', {'node': node}) | ||||
| 		e = e.addChild('item', {'id': id}, [item])	# TODO: we should generate id... or we shouldn't? | ||||
| 
 | ||||
| 		self.connection.send(query) | ||||
| 
 | ||||
| 	def _PubSubCB(self, conn, stanza): | ||||
| 		try: | ||||
| 			cb, args, kwargs = self.__callbacks.pop(stanza.getID()) | ||||
| 			cb(conn, stanza, *args, **kwargs) | ||||
| 		except KeyError: | ||||
| 			pass | ||||
|  | @ -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') | ||||
|  |  | |||
							
								
								
									
										491
									
								
								src/dataforms_widget.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										491
									
								
								src/dataforms_widget.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,491 @@ | |||
| ##	dataforms.py | ||||
| ## | ||||
| ## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org> | ||||
| ## Copyright (C) 2005-2006 Nikos Kouremenos <nkour@jabber.org> | ||||
| ## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com> | ||||
| ## Copyright (C) 2003-2005 Vincent Hanquez <tab@snarc.org> | ||||
| ## | ||||
| ## This program is free software; you can redistribute it and/or modify | ||||
| ## it under the terms of the GNU General Public License as published | ||||
| ## by the Free Software Foundation; version 2 only. | ||||
| ## | ||||
| ## This program is distributed in the hope that it will be useful, | ||||
| ## but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| ## GNU General Public License for more details. | ||||
| ## | ||||
| """ This module contains widget that can display data form (JEP-0004). | ||||
| Words single and multiple refers here to types of data forms: | ||||
| single means these with one record of data (without <recorded/> element), | ||||
| multiple - these which may contain more data (with <recorded/> element).""" | ||||
| 
 | ||||
| # TODO: forms of type='result' should be read-only | ||||
| # TODO: remove tabs from dialog | ||||
| 
 | ||||
| import gtk | ||||
| import pango | ||||
| 
 | ||||
| import gtkgui_helpers | ||||
| 
 | ||||
| import common.xmpp as xmpp | ||||
| import common.dataforms as dataforms | ||||
| 
 | ||||
| import itertools | ||||
| 
 | ||||
| class DataFormWidget(gtk.Alignment, object): | ||||
| # "public" interface | ||||
| 	""" Data Form widget. Use like any other widget. """ | ||||
| 	def __init__(self, dataformnode=None): | ||||
| 		""" Create a widget. """ | ||||
| 		gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0) | ||||
| 
 | ||||
| 		self._data_form = None | ||||
| 
 | ||||
| 		self.xml=gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_vbox') | ||||
| 		self.xml.signal_autoconnect(self) | ||||
| 		for name in ('instructions_label', 'instructions_hseparator', | ||||
| 				'single_form_viewport', 'data_form_types_notebook', | ||||
| 				'single_form_scrolledwindow', 'multiple_form_hbox', | ||||
| 				'records_treeview', 'add_button', 'remove_button', | ||||
| 				'edit_button', 'up_button', 'down_button', 'clear_button'): | ||||
| 			self.__dict__[name] = self.xml.get_widget(name) | ||||
| 
 | ||||
| 		self.add(self.xml.get_widget('data_form_vbox')) | ||||
| 
 | ||||
| 		if dataformnode is not None: | ||||
| 			self.set_data_form(dataformnode) | ||||
| 
 | ||||
| 		selection = self.records_treeview.get_selection() | ||||
| 		selection.connect('changed', self.on_records_selection_changed) | ||||
| 		selection.set_mode(gtk.SELECTION_MULTIPLE) | ||||
| 
 | ||||
| 	def set_data_form(self, dataform): | ||||
| 		""" Set the data form (xmpp.DataForm) displayed in widget. """ | ||||
| 		assert isinstance(dataform, dataforms.DataForm) | ||||
| 
 | ||||
| 		self.del_data_form() | ||||
| 		self._data_form = dataform | ||||
| 		if isinstance(dataform, dataforms.SimpleDataForm): | ||||
| 			self.build_single_data_form() | ||||
| 		else: | ||||
| 			self.build_multiple_data_form() | ||||
| 
 | ||||
| 		# create appropriate description for instructions field if there isn't any | ||||
| 		if dataform.instructions=='': | ||||
| 			if dataform.type=='result': | ||||
| 				# form is single | ||||
| 				instructions = _('This is result of query.') | ||||
| 			else: | ||||
| 				# form is writable (TODO: move that to build_*_data_form()? | ||||
| 				if isinstance(dataform, dataforms.SimpleDataForm): | ||||
| 					instructions = _('Fill in the form.') | ||||
| 				else: | ||||
| 					instructions = _('Edit items on the list') | ||||
| 		else: | ||||
| 			instructions = dataform.instructions | ||||
| 
 | ||||
| 		self.instructions_label.set_text(instructions) | ||||
| 
 | ||||
| 	def get_data_form(self): | ||||
| 		""" Data form displayed in the widget or None if no form. """ | ||||
| 		return self._data_form | ||||
| 
 | ||||
| 	def del_data_form(self): | ||||
| 		self.clean_data_form() | ||||
| 		self._data_form = None | ||||
| 
 | ||||
| 	data_form = property(get_data_form, set_data_form, del_data_form, | ||||
| 		"Data form presented in a widget") | ||||
| 
 | ||||
| 	def get_title(self): | ||||
| 		""" Get the title of data form, as a unicode object. If no | ||||
| 		title or no form, returns u''. Useful for setting window title. """ | ||||
| 		if self._data_form is not None: | ||||
| 			if self._data_form.title is not None: | ||||
| 				return self._data_form.title | ||||
| 		return u'' | ||||
| 
 | ||||
| 	title = property(get_title, None, None, "Data form title") | ||||
| 
 | ||||
| 	def show(self): | ||||
| 		""" Treat 'us' as one widget. """ | ||||
| 		self.show_all() | ||||
| 
 | ||||
| # "private" methods | ||||
| 
 | ||||
| # we have actually two different kinds of data forms: one is a simple form to fill, | ||||
| # second is a table with several records;  | ||||
| 
 | ||||
| 	def clean_data_form(self): | ||||
| 		'''Remove data about existing form. This metod is empty, because | ||||
| 		it is rewritten by build_*_data_form, according to type of form | ||||
| 		which is actually displayed.''' | ||||
| 		pass | ||||
| 
 | ||||
| 	def build_single_data_form(self): | ||||
| 		'''Invoked when new single form is to be created.''' | ||||
| 		assert isinstance(self._data_form, dataforms.SimpleDataForm) | ||||
| 
 | ||||
| 		self.clean_data_form() | ||||
| 
 | ||||
| 		self.singleform = SingleForm(self._data_form) | ||||
| 		self.singleform.show() | ||||
| 		self.single_form_viewport.add(self.singleform) | ||||
| 		self.data_form_types_notebook.set_current_page( | ||||
| 			self.data_form_types_notebook.page_num( | ||||
| 				self.single_form_scrolledwindow)) | ||||
| 
 | ||||
| 		self.clean_data_form = self.clean_single_data_form | ||||
| 
 | ||||
| 	def clean_single_data_form(self): | ||||
| 		'''(Called as clean_data_form, read the docs of clean_data_form()). | ||||
| 		Remove form from widget.''' | ||||
| 		self.singleform.destroy() | ||||
| 		del self.singleform | ||||
| 
 | ||||
| 	def build_multiple_data_form(self): | ||||
| 		'''Invoked when new multiple form is to be created.''' | ||||
| 		assert isinstance(self._data_form, dataforms.MultipleDataForm) | ||||
| 
 | ||||
| 		self.clean_data_form() | ||||
| 
 | ||||
| 		# creating model for form... | ||||
| 		fieldtypes = [] | ||||
| 		for field in self._data_form.recorded.iter_fields(): | ||||
| 			# note: we store also text-private and hidden fields, | ||||
| 			# we just do not display them. | ||||
| 			# TODO: boolean fields | ||||
| 			#elif field.type=='boolean': fieldtypes.append(bool) | ||||
| 			fieldtypes.append(str) | ||||
| 
 | ||||
| 		self.multiplemodel = gtk.ListStore(*fieldtypes) | ||||
| 
 | ||||
| 		# moving all data to model | ||||
| 		for item in self._data_form.iter_records(): | ||||
| 			# TODO: probably wrong... (.value[s]?, fields not in the same order?) | ||||
| 			# not checking multiple-item forms... | ||||
| 			self.multiplemodel.append([field.value for field in item.iter_fields()]) | ||||
| 
 | ||||
| 		# constructing columns... | ||||
| 		for field, counter in zip(self._data_form.iter_fields(), itertools.count()): | ||||
| 			print repr(field), repr(counter) | ||||
| 			self.records_treeview.append_column( | ||||
| 				gtk.TreeViewColumn(field.label,	gtk.CellRendererText(), | ||||
| 					text=counter)) | ||||
| 
 | ||||
| 		self.records_treeview.set_model(self.multiplemodel) | ||||
| 		self.records_treeview.show_all() | ||||
| 
 | ||||
| 		self.data_form_types_notebook.set_current_page( | ||||
| 			self.data_form_types_notebook.page_num( | ||||
| 				self.multiple_form_hbox)) | ||||
| 
 | ||||
| 		self.clean_data_form = self.clean_multiple_data_form | ||||
| 
 | ||||
| 		# refresh list look | ||||
| 		self.refresh_multiple_buttons() | ||||
| 
 | ||||
| 	def clean_multiple_data_form(self): | ||||
| 		'''(Called as clean_data_form, read the docs of clean_data_form()). | ||||
| 		Remove form from widget.''' | ||||
| 		del self.multiplemodel | ||||
| 
 | ||||
| 	def refresh_multiple_buttons(self): | ||||
| 		''' Checks for treeview state and makes control buttons sensitive.''' | ||||
| 		selection = self.records_treeview.get_selection() | ||||
| 		model = self.records_treeview.get_model() | ||||
| 		count = selection.count_selected_rows() | ||||
| 		if count==0: | ||||
| 			self.remove_button.set_sensitive(False) | ||||
| 			self.edit_button.set_sensitive(False) | ||||
| 			self.up_button.set_sensitive(False) | ||||
| 			self.down_button.set_sensitive(False) | ||||
| 		elif count==1: | ||||
| 			self.remove_button.set_sensitive(True) | ||||
| 			self.edit_button.set_sensitive(True) | ||||
| 			_, (path,) = selection.get_selected_rows() | ||||
| 			iter = model.get_iter(path) | ||||
| 			if model.iter_next(iter) is None: | ||||
| 				self.up_button.set_sensitive(True) | ||||
| 				self.down_button.set_sensitive(False) | ||||
| 			elif path==(0,): | ||||
| 				self.up_button.set_sensitive(False) | ||||
| 				self.down_button.set_sensitive(True) | ||||
| 			else: | ||||
| 				self.up_button.set_sensitive(True) | ||||
| 				self.down_button.set_sensitive(True) | ||||
| 		else: | ||||
| 			self.remove_button.set_sensitive(True) | ||||
| 			self.edit_button.set_sensitive(True) | ||||
| 			self.up_button.set_sensitive(False) | ||||
| 			self.down_button.set_sensitive(False) | ||||
| 
 | ||||
| 		if len(model)==0: | ||||
| 			self.clear_button.set_sensitive(False) | ||||
| 		else: | ||||
| 			self.clear_button.set_sensitive(True) | ||||
| 
 | ||||
| 	def on_clear_button_clicked(self, widget): | ||||
| 		self.records_treeview.get_model().clear() | ||||
| 
 | ||||
| 	def on_remove_button_clicked(self, widget): | ||||
| 		selection = self.records_treeview.get_selection() | ||||
| 		model, rowrefs = selection.get_selected_rows()	# rowref is a list of paths | ||||
| 		for i in xrange(len(rowrefs)): | ||||
| 			rowrefs[i] = gtk.TreeRowReference(model, rowrefs[i]) | ||||
| 		# rowref is a list of row references; need to convert because we will modify the model, | ||||
| 		# paths would change | ||||
| 		for rowref in rowrefs: | ||||
| 			del model[rowref.get_path()] | ||||
| 	 | ||||
| 	def on_up_button_clicked(self, widget): | ||||
| 		selection = self.records_treeview.get_selection() | ||||
| 		model, (path,) = selection.get_selected_rows() | ||||
| 		iter = model.get_iter(path) | ||||
| 		previter = model.get_iter((path[0]-1,))	# constructing path for previous iter | ||||
| 		model.swap(iter, previter) | ||||
| 
 | ||||
| 		self.refresh_multiple_buttons() | ||||
| 
 | ||||
| 	def on_down_button_clicked(self, widget): | ||||
| 		selection = self.records_treeview.get_selection() | ||||
| 		model, (path,) = selection.get_selected_rows() | ||||
| 		iter = model.get_iter(path) | ||||
| 		nextiter = model.iter_next(iter) | ||||
| 		model.swap(iter, nextiter) | ||||
| 
 | ||||
| 		self.refresh_multiple_buttons() | ||||
| 
 | ||||
| 	def on_records_selection_changed(self, widget): | ||||
| 		self.refresh_multiple_buttons() | ||||
| 
 | ||||
| class SingleForm(gtk.Table, object): | ||||
| 	""" Widget that represent DATAFORM_SINGLE mode form. Because this is used | ||||
| 	not only to display single forms, but to form input windows of multiple-type | ||||
| 	forms, it is in another class.""" | ||||
| 	def __init__(self, dataform): | ||||
| 		assert isinstance(dataform, dataforms.SimpleDataForm) | ||||
| 
 | ||||
| 		gtk.Table.__init__(self) | ||||
| 		self.set_col_spacings(6) | ||||
| 		self.set_row_spacings(6) | ||||
| 
 | ||||
| 		self._data_form = dataform | ||||
| 
 | ||||
| 		# building widget | ||||
| 		linecounter = 0 | ||||
| 
 | ||||
| 		# for each field... | ||||
| 		for field in self._data_form.iter_fields(): | ||||
| 			if field.type=='hidden': continue | ||||
| 
 | ||||
| 			commonlabel = True | ||||
| 			commondesc = True | ||||
| 			commonwidget = True | ||||
| 			widget = None | ||||
| 
 | ||||
| 			if field.type=='boolean': | ||||
| 				widget = gtk.CheckButton() | ||||
| 				widget.connect('toggled', self.on_boolean_checkbutton_toggled, field) | ||||
| 				widget.set_active(field.value) | ||||
| 
 | ||||
| 			elif field.type=='fixed': | ||||
| 				leftattach = 1 | ||||
| 				rightattach = 2 | ||||
| 				if field.label is None: | ||||
| 					commonlabel = False | ||||
| 					leftattach = 0 | ||||
| 				if field.description is None: | ||||
| 					commondesc = False | ||||
| 					rightattach = 3 | ||||
| 				 | ||||
| 				commonwidget=False | ||||
| 				widget = gtk.Label(field.value) | ||||
| 				widget.set_line_wrap(True) | ||||
| 				self.attach(widget, leftattach, rightattach, linecounter, linecounter+1, | ||||
| 					xoptions=gtk.FILL, yoptions=gtk.FILL) | ||||
| 
 | ||||
| 			elif field.type == 'list-single': | ||||
| 				# TODO: When more than few choices, make a list | ||||
| 				# TODO: Think of moving that to another function (it could be used | ||||
| 				# TODO: in stage2 of adhoc commands too). | ||||
| 				# TODO: What if we have radio buttons and non-required field? | ||||
| 				# TODO: We cannot deactivate them all... | ||||
| 				widget = gtk.VBox() | ||||
| 				first_radio = None | ||||
| 				for value, label in field.iter_options(): | ||||
| 					radio = gtk.RadioButton(first_radio, label=label) | ||||
| 					radio.connect('toggled', self.on_list_single_radiobutton_toggled, | ||||
| 						field, value) | ||||
| 					if first_radio is None: | ||||
| 						first_radio = radio | ||||
| 						if field.value=='':	# TODO: is None when done | ||||
| 							field.value = value | ||||
| 					if value == field.value: | ||||
| 						radio.set_active(True) | ||||
| 					widget.pack_start(radio, expand=False) | ||||
| 
 | ||||
| 			elif field.type == 'list-multi': | ||||
| 				# TODO: When more than few choices, make a list | ||||
| 				widget = gtk.VBox() | ||||
| 				for value, label in field.iter_options(): | ||||
| 					check = gtk.CheckButton(label, use_underline=False) | ||||
| 					check.set_active(value in field.values) | ||||
| 					check.connect('toggled', self.on_list_multi_checkbutton_toggled, | ||||
| 						field, value) | ||||
| 					widget.pack_start(check, expand=False) | ||||
| 
 | ||||
| 			elif field.type == 'jid-single': | ||||
| 				widget = gtk.Entry() | ||||
| 				widget.connect('changed', self.on_text_single_entry_changed, field) | ||||
| 				widget.set_text(field.value) | ||||
| 
 | ||||
| 			elif field.type == 'jid-multi': | ||||
| 				commonwidget = False | ||||
| 
 | ||||
| 				xml = gtkgui_helpers.get_glade('data_form_window.glade', 'item_list_table') | ||||
| 				widget = xml.get_widget('item_list_table') | ||||
| 				treeview = xml.get_widget('item_treeview') | ||||
| 
 | ||||
| 				listmodel = gtk.ListStore(str) | ||||
| 				for value in field.iter_values(): | ||||
| 					# nobody will create several megabytes long stanza | ||||
| 					listmodel.insert(999999, (value,)) | ||||
| 
 | ||||
| 				treeview.set_model(listmodel) | ||||
| 
 | ||||
| 				renderer = gtk.CellRendererText() | ||||
| 				renderer.set_property('editable', True) | ||||
| 				renderer.connect('edited', | ||||
| 					self.on_jid_multi_cellrenderertext_edited, listmodel, field) | ||||
| 
 | ||||
| 				treeview.append_column(gtk.TreeViewColumn(None, renderer, | ||||
| 					text=0)) | ||||
| 
 | ||||
| 				xml.get_widget('add_button').connect('clicked', | ||||
| 					self.on_jid_multi_add_button_clicked, treeview, listmodel, field) | ||||
| 				xml.get_widget('edit_button').connect('clicked', | ||||
| 					self.on_jid_multi_edit_button_clicked, treeview) | ||||
| 				xml.get_widget('remove_button').connect('clicked', | ||||
| 					self.on_jid_multi_remove_button_clicked, treeview, field) | ||||
| 				xml.get_widget('clear_button').connect('clicked', | ||||
| 					self.on_jid_multi_clean_button_clicked, listmodel, field) | ||||
| 
 | ||||
| 				self.attach(widget, 1, 2, linecounter, linecounter+1) | ||||
| 
 | ||||
| 				del xml | ||||
| 
 | ||||
| 			elif field.type == 'text-private': | ||||
| 				widget = gtk.Entry() | ||||
| 				widget.connect('changed', self.on_text_single_entry_changed, field) | ||||
| 				widget.set_visibility(False) | ||||
| 				widget.set_text(field.value) | ||||
| 
 | ||||
| 			elif field.type == 'text-multi': | ||||
| 				# TODO: bigger text view | ||||
| 				commonwidget = False | ||||
| 
 | ||||
| 				textwidget = gtk.TextView() | ||||
| 				textwidget.set_wrap_mode(gtk.WRAP_WORD) | ||||
| 				textwidget.get_buffer().connect('changed', self.on_text_multi_textbuffer_changed, | ||||
| 					field) | ||||
| 				textwidget.get_buffer().set_text(field.value) | ||||
| 				 | ||||
| 				widget = gtk.ScrolledWindow() | ||||
| 				widget.add(textwidget) | ||||
| 
 | ||||
| 				self.attach(widget, 1, 2, linecounter, linecounter+1) | ||||
| 
 | ||||
| 			else:# field.type == 'text-single' or field.type is nonstandard: | ||||
| 				# JEP says that if we don't understand some type, we | ||||
| 				# should handle it as text-single | ||||
| 				widget = gtk.Entry() | ||||
| 				widget.connect('changed', self.on_text_single_entry_changed, field) | ||||
| 				if field.value is None: | ||||
| 					field.value = u'' | ||||
| 				widget.set_text(field.value) | ||||
| 
 | ||||
| 			if commonlabel and field.label is not None: | ||||
| 				label = gtk.Label(field.label) | ||||
| 				label.set_alignment(1.0, 0.5) | ||||
| 				self.attach(label, 0, 1, linecounter, linecounter+1, | ||||
| 					xoptions=gtk.FILL, yoptions=gtk.FILL) | ||||
| 
 | ||||
| 			if commonwidget: | ||||
| 				assert widget is not None | ||||
| 				self.attach(widget, 1, 2, linecounter, linecounter+1, | ||||
| 					yoptions=gtk.FILL) | ||||
| 			widget.show_all() | ||||
| 
 | ||||
| 			if commondesc and field.description!='': | ||||
| 				label = gtk.Label() | ||||
| 				label.set_markup('<small>'+\ | ||||
| 					gtkgui_helpers.escape_for_pango_markup(field.description)+\ | ||||
| 					'</small>') | ||||
| 				label.set_line_wrap(True) | ||||
| 				self.attach(label, 2, 3, linecounter, linecounter+1, | ||||
| 					xoptions=gtk.FILL|gtk.SHRINK, yoptions=gtk.FILL|gtk.SHRINK) | ||||
| 
 | ||||
| 			linecounter+=1 | ||||
| 		if self.get_property('visible'): | ||||
| 			self.show_all() | ||||
| 
 | ||||
| 	def show(self): | ||||
| 		# simulate that we are one widget | ||||
| 		self.show_all() | ||||
| 
 | ||||
| 	def on_boolean_checkbutton_toggled(self, widget, field): | ||||
| 		field.value = widget.get_active() | ||||
| 
 | ||||
| 	def on_list_single_radiobutton_toggled(self, widget, field, value): | ||||
| 		field.value = value | ||||
| 
 | ||||
| 	def on_list_multi_checkbutton_toggled(self, widget, field, value): | ||||
| 		# TODO: make some methods like add_value and remove_value | ||||
| 		if widget.get_active() and value not in field.values: | ||||
| 			field.values += [value] | ||||
| 		elif not widget.get_active() and value in field.values: | ||||
| 			field.values = [v for v in field.values if v!=value] | ||||
| 
 | ||||
| 	def on_text_single_entry_changed(self, widget, field): | ||||
| 		field.value = widget.get_text() | ||||
| 
 | ||||
| 	def on_text_multi_textbuffer_changed(self, widget, field): | ||||
| 		field.value = widget.get_text( | ||||
| 			widget.get_start_iter(), | ||||
| 			widget.get_end_iter()) | ||||
| 
 | ||||
| 	def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, model, field): | ||||
| 		old=model[path][0] | ||||
| 		model[path][0]=newtext | ||||
| 
 | ||||
| 		values = field.values | ||||
| 		values[values.index(old)]=newtext | ||||
| 		field.values = values | ||||
| 
 | ||||
| 	def on_jid_multi_add_button_clicked(self, widget, treeview, model, field): | ||||
| 		iter = model.insert(999999, ("new@jabber.id",)) | ||||
| 		treeview.set_cursor(model.get_path(iter), treeview.get_column(0), True) | ||||
| 		field.values = field.values + ["new@jabber.id"] | ||||
| 
 | ||||
| 	def on_jid_multi_edit_button_clicked(self, widget, treeview): | ||||
| 		model, iter = treeview.get_selection().get_selected() | ||||
| 		assert iter is not None | ||||
| 
 | ||||
| 		treeview.set_cursor(model.get_path(iter), treeview.get_column(0), True) | ||||
| 
 | ||||
| 	def on_jid_multi_remove_button_clicked(self, widget, treeview, field): | ||||
| 		selection = treeview.get_selection() | ||||
| 		model = treeview.get_model() | ||||
| 		deleted = [] | ||||
| 
 | ||||
| 		def remove(model, path, iter, deleted): | ||||
| 			deleted+=model[iter] | ||||
| 			model.remove(iter) | ||||
| 
 | ||||
| 		selection.selected_foreach(remove, deleted) | ||||
| 		field.values = (v for v in field.values if v not in deleted) | ||||
| 
 | ||||
| 	def on_jid_multi_clean_button_clicked(self, widget, model, field): | ||||
| 		model.clear() | ||||
| 		del field.values | ||||
|  | @ -1002,7 +1002,7 @@ class FTOverwriteConfirmationDialog(ConfirmationDialog): | |||
| class InputDialog: | ||||
| 	'''Class for Input dialog''' | ||||
| 	def __init__(self, title, label_str, input_str = None, is_modal = True, | ||||
| 	ok_handler = None, cancel_handler = None): | ||||
| ok_handler = None): | ||||
| 		# if modal is True you also need to call get_response() | ||||
| 		# and ok_handler won't be used | ||||
| 		self.xml = gtkgui_helpers.get_glade('input_dialog.glade') | ||||
|  | @ -1011,7 +1011,6 @@ class InputDialog: | |||
| 		self.input_entry = self.xml.get_widget('input_entry') | ||||
| 		self.dialog.set_title(title) | ||||
| 		label.set_markup(label_str) | ||||
| 		self.cancel_handler = cancel_handler | ||||
| 		if input_str: | ||||
| 			self.input_entry.set_text(input_str) | ||||
| 			self.input_entry.select_region(0, -1) # select all | ||||
|  | @ -1023,22 +1022,14 @@ class InputDialog: | |||
| 			okbutton.connect('clicked', self.on_okbutton_clicked) | ||||
| 			cancelbutton = self.xml.get_widget('cancelbutton') | ||||
| 			cancelbutton.connect('clicked', self.on_cancelbutton_clicked) | ||||
| 			self.xml.signal_autoconnect(self) | ||||
| 			self.dialog.show_all() | ||||
| 
 | ||||
| 	def on_input_dialog_destroy(self, widget): | ||||
| 		if self.cancel_handler: | ||||
| 			self.cancel_handler() | ||||
| 
 | ||||
| 	def on_okbutton_clicked(self, widget): | ||||
| 	def on_okbutton_clicked(self,  widget): | ||||
| 		user_input = self.input_entry.get_text().decode('utf-8') | ||||
| 		self.dialog.destroy() | ||||
| 		if isinstance(self.ok_handler, tuple): | ||||
| 			self.ok_handler[0](user_input, *self.ok_handler[1:]) | ||||
| 		else: | ||||
| 			self.ok_handler(user_input) | ||||
| 		self.ok_handler(user_input) | ||||
| 	 | ||||
| 	def on_cancelbutton_clicked(self, widget): | ||||
| 	def on_cancelbutton_clicked(self,  widget): | ||||
| 		self.dialog.destroy() | ||||
| 
 | ||||
| 	def get_response(self): | ||||
|  |  | |||
							
								
								
									
										216
									
								
								src/disco.py
									
										
									
									
									
								
							
							
						
						
									
										216
									
								
								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,7 +607,8 @@ _('Without a connection, you can not browse available services')) | |||
| 				child.destroy(chain = chain) | ||||
| 				self.children.remove(child) | ||||
| 		if self.parent: | ||||
| 			self.parent.children.remove(self) | ||||
| 			if self in self.parent.children: | ||||
| 				self.parent.children.remove(self) | ||||
| 			if chain and not self.parent.children: | ||||
| 				self.parent.destroy(chain = chain) | ||||
| 				self.parent = None | ||||
|  | @ -1662,6 +1666,214 @@ class MucBrowser(AgentBrowser): | |||
| 		# switch to alternate query mode | ||||
| 		self.cache.get_items(jid, node, self._channel_altinfo) | ||||
| 
 | ||||
| def PubSubBrowser(account, jid, node): | ||||
| 	''' Returns an AgentBrowser subclass that will display service discovery | ||||
| 	for particular pubsub service. Different pubsub services may need to | ||||
| 	present different data during browsing. ''' | ||||
| 	# for now, only discussion groups are supported... | ||||
| 	# TODO: check if it has appropriate features to be such kind of service | ||||
| 	return DiscussionGroupsBrowser(account, jid, node) | ||||
| 
 | ||||
| class DiscussionGroupsBrowser(AgentBrowser): | ||||
| 	''' For browsing pubsub-based discussion groups service. ''' | ||||
| 	def __init__(self, account, jid, node): | ||||
| 		AgentBrowser.__init__(self, account, jid, node) | ||||
| 
 | ||||
| 		# this will become set object when we get subscriptions; None means | ||||
| 		# we don't know yet which groups are subscribed | ||||
| 		self.subscriptions = None | ||||
| 
 | ||||
| 		# this will become our action widgets when we create them; None means | ||||
| 		# we don't have them yet (needed for check in callback) | ||||
| 		self.subscribe_button = None | ||||
| 		self.unsubscribe_button = None | ||||
| 
 | ||||
| 		gajim.connections[account].send_pb_subscription_query(jid, self._subscriptionsCB) | ||||
| 
 | ||||
| 	def _create_treemodel(self): | ||||
| 		''' Create treemodel for the window. ''' | ||||
| 		# JID, node, name (with description) - pango markup, dont have info?, subscribed? | ||||
| 		model = gtk.ListStore(str, str, str, bool, bool) | ||||
| 		model.set_sort_column_id(3, gtk.SORT_ASCENDING) | ||||
| 		self.window.services_treeview.set_model(model) | ||||
| 
 | ||||
| 		# Name column | ||||
| 		# Pango markup for name and description, description printed with | ||||
| 		# <small/> font | ||||
| 		renderer = gtk.CellRendererText() | ||||
| 		col = gtk.TreeViewColumn(_('Name')) | ||||
| 		col.pack_start(renderer) | ||||
| 		col.set_attributes(renderer, markup=2) | ||||
| 		col.set_resizable(True) | ||||
| 		self.window.services_treeview.insert_column(col, -1) | ||||
| 
 | ||||
| 		# Subscription state | ||||
| 		renderer = gtk.CellRendererToggle() | ||||
| 		col = gtk.TreeViewColumn(_('Subscribed')) | ||||
| 		col.pack_start(renderer) | ||||
| 		col.set_attributes(renderer, inconsistent=3, active=4) | ||||
| 		col.set_resizable(False) | ||||
| 		self.window.services_treeview.insert_column(col, -1) | ||||
| 
 | ||||
| 		self.window.services_treeview.set_headers_visible(True) | ||||
| 
 | ||||
| 	def _add_item(self, model, jid, node, item, force): | ||||
| 		''' Called when we got basic information about new node from query. | ||||
| 		Show the item. ''' | ||||
| 		name = item.get('name', '') | ||||
| 
 | ||||
| 		if self.subscriptions is not None: | ||||
| 			dunno = False | ||||
| 			subscribed = name in self.subscriptions | ||||
| 		else: | ||||
| 			dunno = True | ||||
| 			subscribed = False | ||||
| 
 | ||||
| 		name = gtkgui_helpers.escape_for_pango_markup(name) | ||||
| 		name = '<b>%s</b>' % name | ||||
| 
 | ||||
| 		model.append((jid, node, name, dunno, subscribed)) | ||||
| 
 | ||||
| 	def _add_actions(self): | ||||
| 		self.post_button = gtk.Button(label=_('New post'), use_underline=True) | ||||
| 		self.post_button.set_sensitive(False) | ||||
| 		self.post_button.connect('clicked', self.on_post_button_clicked) | ||||
| 		self.window.action_buttonbox.add(self.post_button) | ||||
| 		self.post_button.show_all() | ||||
| 
 | ||||
| 		self.subscribe_button = gtk.Button(label=_('_Subscribe'), use_underline=True) | ||||
| 		self.subscribe_button.set_sensitive(False) | ||||
| 		self.subscribe_button.connect('clicked', self.on_subscribe_button_clicked) | ||||
| 		self.window.action_buttonbox.add(self.subscribe_button) | ||||
| 		self.subscribe_button.show_all() | ||||
| 
 | ||||
| 		self.unsubscribe_button = gtk.Button(label=_('_Unsubscribe'), use_underline=True) | ||||
| 		self.unsubscribe_button.set_sensitive(False) | ||||
| 		self.unsubscribe_button.connect('clicked', self.on_unsubscribe_button_clicked) | ||||
| 		self.window.action_buttonbox.add(self.unsubscribe_button) | ||||
| 		self.unsubscribe_button.show_all() | ||||
| 
 | ||||
| 	def _clean_actions(self): | ||||
| 		if self.post_button is not None: | ||||
| 			self.post_button.destroy() | ||||
| 			self.post_button = None | ||||
| 
 | ||||
| 		if self.subscribe_button is not None: | ||||
| 			self.subscribe_button.destroy() | ||||
| 			self.subscribe_button = None | ||||
| 
 | ||||
| 		if self.unsubscribe_button is not None: | ||||
| 			self.unsubscribe_button.destroy() | ||||
| 			self.unsubscribe_button = None | ||||
| 
 | ||||
| 	def update_actions(self): | ||||
| 		'''Called when user selected a row. Make subscribe/unsubscribe buttons | ||||
| 		sensitive appropriatelly.''' | ||||
| 		# we have nothing to do if we don't have buttons... | ||||
| 		if self.subscribe_button is None: return | ||||
| 
 | ||||
| 		model, iter = self.window.services_treeview.get_selection().get_selected() | ||||
| 		if not iter or self.subscriptions is None: | ||||
| 			# no item selected or no subscriptions info, all buttons are insensitive | ||||
| 			self.post_button.set_sensitive(False) | ||||
| 			self.subscribe_button.set_sensitive(False) | ||||
| 			self.unsubscribe_button.set_sensitive(False) | ||||
| 		else: | ||||
| 			subscribed = model.get_value(iter, 4) # 4 = subscribed? | ||||
| 			self.post_button.set_sensitive(subscribed) | ||||
| 			self.subscribe_button.set_sensitive(not subscribed) | ||||
| 			self.unsubscribe_button.set_sensitive(subscribed) | ||||
| 
 | ||||
| 	def on_post_button_clicked(self, widget): | ||||
| 		'''Called when 'post' button is pressed. Open window to create post''' | ||||
| 		model, iter = self.window.services_treeview.get_selection().get_selected() | ||||
| 		if iter is None: return | ||||
| 
 | ||||
| 		groupnode = model.get_value(iter, 1)	# 1 = groupnode | ||||
| 
 | ||||
| 		groups.GroupsPostWindow(self.account, self.jid, groupnode) | ||||
| 
 | ||||
| 	def on_subscribe_button_clicked(self, widget): | ||||
| 		'''Called when 'subscribe' button is pressed. Send subscribtion request.''' | ||||
| 		model, iter = self.window.services_treeview.get_selection().get_selected() | ||||
| 		if iter is None: return | ||||
| 
 | ||||
| 		groupnode = model.get_value(iter, 1)	# 1 = groupnode | ||||
| 
 | ||||
| 		gajim.connections[self.account].send_pb_subscribe(self.jid, groupnode, self._subscribeCB, groupnode) | ||||
| 		 | ||||
| 	def on_unsubscribe_button_clicked(self, widget): | ||||
| 		'''Called when 'unsubscribe' button is pressed. Send unsubscription request.''' | ||||
| 		model, iter = self.window.services_treeview.get_selection().get_selected() | ||||
| 		if iter is None: return | ||||
| 
 | ||||
| 		groupnode = model.get_value(iter, 1)    # 1 = groupnode | ||||
| 		 | ||||
| 		gajim.connections[self.account].send_pb_unsubscribe(self.jid, groupnode, self._unsubscribeCB, groupnode) | ||||
| 
 | ||||
| 	def _subscriptionsCB(self, conn, request): | ||||
| 		''' We got the subscribed groups list stanza. Now, if we already | ||||
| 		have items on the list, we should actualize them. ''' | ||||
| 		print 0 | ||||
| 		try: | ||||
| 			subscriptions = request.getTag('pubsub').getTag('subscriptions') | ||||
| 		except: | ||||
| 			return  | ||||
| 
 | ||||
| 		print 1 | ||||
| 		groups = set() | ||||
| 		for child in subscriptions.getTags('subscription'): | ||||
| 			print 2, repr(child), str(child) | ||||
| 			groups.add(child['node']) | ||||
| 		print 3, groups | ||||
| 
 | ||||
| 		self.subscriptions = groups | ||||
| 
 | ||||
| 		# try to setup existing items in model | ||||
| 		model = self.window.services_treeview.get_model() | ||||
| 		print 4 | ||||
| 		for row in model: | ||||
| 			print 5 | ||||
| 			# 1 = group node | ||||
| 			# 3 = insensitive checkbox for subscribed | ||||
| 			# 4 = subscribed? | ||||
| 			groupnode = row[1] | ||||
| 			row[3]=False | ||||
| 			row[4]=groupnode in groups | ||||
| 		print 6 | ||||
| 
 | ||||
| 		# we now know subscriptions, update button states | ||||
| 		self.update_actions() | ||||
| 
 | ||||
| 		raise xmpp.NodeProcessed | ||||
| 
 | ||||
| 	def _subscribeCB(self, conn, request, groupnode): | ||||
| 		'''We have just subscribed to a node. Update UI''' | ||||
| 		self.subscriptions.add(groupnode) | ||||
| 
 | ||||
| 		model = self.window.services_treeview.get_model() | ||||
| 		for row in model: | ||||
| 			if row[1] == groupnode: # 1 = groupnode | ||||
| 				row[4]=True | ||||
| 				break | ||||
| 
 | ||||
| 		self.update_actions() | ||||
| 
 | ||||
| 		raise xmpp.NodeProcessed | ||||
| 
 | ||||
| 	def _unsubscribeCB(self, conn, request, groupnode): | ||||
| 		'''We have just unsubscribed from a node. Update UI''' | ||||
| 		self.subscriptions.remove(groupnode) | ||||
| 
 | ||||
| 		model = self.window.services_treeview.get_model() | ||||
| 		for row in model: | ||||
| 			if row[1] == groupnode: # 1 = groupnode | ||||
| 				row[4]=False | ||||
| 				break | ||||
| 
 | ||||
| 		self.update_actions() | ||||
| 
 | ||||
| 		raise xmpp.NodeProcessed | ||||
| 
 | ||||
| # Fill the global agent type info dictionary | ||||
| _agent_type_info = _gen_agent_type_info() | ||||
|  |  | |||
							
								
								
									
										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 | ||||
|  |  | |||
							
								
								
									
										47
									
								
								src/groups.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/groups.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,47 @@ | |||
| '''Window to create new post for discussion groups service.''' | ||||
| 
 | ||||
| import gtk | ||||
| 
 | ||||
| from common import gajim, xmpp | ||||
| import gtkgui_helpers | ||||
| 
 | ||||
| class GroupsPostWindow: | ||||
| 	def __init__(self, account, servicejid, groupid): | ||||
| 		'''Open new 'create post' window to create message for groupid on servicejid service.''' | ||||
| 		assert isinstance(servicejid, basestring) | ||||
| 		assert isinstance(groupid, basestring) | ||||
| 
 | ||||
| 		self.account = account | ||||
| 		self.servicejid = servicejid | ||||
| 		self.groupid = groupid | ||||
| 
 | ||||
| 		self.xml = gtkgui_helpers.get_glade('groups_post_window.glade') | ||||
| 		self.window = self.xml.get_widget('groups_post_window') | ||||
| 		for name in ('from_entry', 'subject_entry', 'contents_textview'): | ||||
| 			self.__dict__[name] = self.xml.get_widget(name) | ||||
| 
 | ||||
| 		self.xml.signal_autoconnect(self) | ||||
| 		self.window.show_all() | ||||
| 
 | ||||
| 	def on_cancel_button_clicked(self, w): | ||||
| 		'''Close window.''' | ||||
| 		self.window.destroy() | ||||
| 
 | ||||
| 	def on_send_button_clicked(self, w): | ||||
| 		'''Gather info from widgets and send it as a message.''' | ||||
| 		# constructing item to publish... that's atom:entry element | ||||
| 		item = xmpp.Node('entry', {'xmlns':'http://www.w3.org/2005/Atom'}) | ||||
| 		author = item.addChild('author') | ||||
| 		author.addChild('name', {}, [self.from_entry.get_text()]) | ||||
| 		item.addChild('generator', {}, ['Gajim']) | ||||
| 		item.addChild('title', {}, [self.subject_entry.get_text()]) | ||||
| 		item.addChild('id', {}, ['0']) | ||||
| 
 | ||||
| 		buffer = self.contents_textview.get_buffer() | ||||
| 		item.addChild('content', {}, [buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())]) | ||||
| 
 | ||||
| 		# publish it to node | ||||
| 		gajim.connections[self.account].send_pb_publish(self.servicejid, self.groupid, item, '0') | ||||
| 
 | ||||
| 		# close the window | ||||
| 		self.window.destroy() | ||||
|  | @ -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,25 +1680,41 @@ class RosterWindow: | |||
| 
 | ||||
| 		contacts = gajim.contacts.get_contact(account, jid) | ||||
| 		if len(contacts) > 1: # sevral resources | ||||
| 			sub_menu = gtk.Menu() | ||||
| 			start_chat_menuitem.set_submenu(sub_menu) | ||||
| 			def resources_submenu(action): | ||||
| 				""" Build a submenu with contact's resources. """ | ||||
| 				sub_menu = gtk.Menu() | ||||
| 
 | ||||
| 			iconset = gajim.config.get('iconset') | ||||
| 			path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') | ||||
| 			for c in contacts: | ||||
| 				# icon MUST be different instance for every item | ||||
| 				state_images = self.load_iconset(path) | ||||
| 				item = gtk.ImageMenuItem(c.resource + ' (' + str(c.priority) + ')') | ||||
| 				icon_name = helpers.get_icon_name_to_show(c, account) | ||||
| 				icon = state_images[icon_name] | ||||
| 				item.set_image(icon) | ||||
| 				sub_menu.append(item) | ||||
| 				item.connect('activate', self.on_open_chat_window, c, account, | ||||
| 					c.resource) | ||||
| 				iconset = gajim.config.get('iconset') | ||||
| 				if not iconset: | ||||
| 					iconset = DEFAULT_ICONSET | ||||
| 				path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16') | ||||
| 				for c in contacts: | ||||
| 					# icon MUST be different instance for every item | ||||
| 					state_images = self.load_iconset(path) | ||||
| 					item = gtk.ImageMenuItem(c.resource + ' (' + str(c.priority) + ')') | ||||
| 					icon_name = helpers.get_icon_name_to_show(c, account) | ||||
| 					icon = state_images[icon_name] | ||||
| 					item.set_image(icon) | ||||
| 					sub_menu.append(item) | ||||
| 					item.connect('activate', action, c, account, | ||||
| 						c.resource) | ||||
| 				return sub_menu | ||||
| 
 | ||||
| 			start_chat_menuitem.set_submenu(resources_submenu(self.on_open_chat_window)) | ||||
| 			execute_command_menuitem.set_submenu(resources_submenu(self.on_execute_command)) | ||||
| 
 | ||||
| 		else: # one resource | ||||
| 			start_chat_menuitem.connect('activate', | ||||
| 				self.on_roster_treeview_row_activated, tree_path) | ||||
| 				self.on_open_chat_window, contact, account) | ||||
| 			# we cannot execute commands when the resource is unknown | ||||
| 			# TODO: that's true only if the entity is a contact, | ||||
| 			# TODO: we need to show this also for transports | ||||
| 			if contact.resource: | ||||
| 				execute_command_menuitem.connect('activate', | ||||
| 					self.on_execute_command, contact, account, contact.resource) | ||||
| 			else: | ||||
| 				execute_command_menuitem.hide() | ||||
| 				execute_command_menuitem.set_no_show_all(True) | ||||
| 
 | ||||
| 		if contact.resource: | ||||
| 			send_file_menuitem.connect('activate', | ||||
|  | @ -1846,7 +1821,7 @@ class RosterWindow: | |||
| 			for widget in [start_chat_menuitem, send_single_message_menuitem, | ||||
| 			rename_menuitem, edit_groups_menuitem, send_file_menuitem, | ||||
| 			subscription_menuitem, add_to_roster_menuitem, | ||||
| 			remove_from_roster_menuitem]: | ||||
| 			remove_from_roster_menuitem, execute_command_menuitem]: | ||||
| 				widget.set_sensitive(False) | ||||
| 
 | ||||
| 		event_button = gtkgui_helpers.get_possible_button_event(event) | ||||
|  | @ -2094,7 +2069,16 @@ class RosterWindow: | |||
| 		item.connect('activate', self.on_edit_agent, contact, account) | ||||
| 		if gajim.account_is_disconnected(account): | ||||
| 			item.set_sensitive(False) | ||||
| 		 | ||||
| 
 | ||||
| 		item = gtk.ImageMenuItem(_('Execute Command...')) | ||||
| 		icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) | ||||
| 		item.set_image(icon) | ||||
| 		menu.append(item) | ||||
| 		item.connect('activate', self.on_execute_command, contact, account, | ||||
| 			contact.resource) | ||||
| 		if not is_connected: | ||||
| 			item.set_sensitive(False) | ||||
| 
 | ||||
| 		item = gtk.ImageMenuItem(_('_Rename')) | ||||
| 		# add a special img for rename menuitem | ||||
| 		path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', | ||||
|  | @ -2167,6 +2151,7 @@ class RosterWindow: | |||
| 			new_message_menuitem = xml.get_widget('new_message_menuitem') | ||||
| 			add_contact_menuitem = xml.get_widget('add_contact_menuitem') | ||||
| 			service_discovery_menuitem = xml.get_widget('service_discovery_menuitem') | ||||
| 			execute_command_menuitem = xml.get_widget('execute_command_menuitem') | ||||
| 			edit_account_menuitem = xml.get_widget('edit_account_menuitem') | ||||
| 			sub_menu = gtk.Menu() | ||||
| 			status_menuitem.set_submenu(sub_menu) | ||||
|  | @ -2210,6 +2195,10 @@ class RosterWindow: | |||
| 			add_contact_menuitem.connect('activate', self.on_add_new_contact, account) | ||||
| 			service_discovery_menuitem.connect('activate', | ||||
| 				self.on_service_disco_menuitem_activate, account) | ||||
| 			hostname = gajim.config.get_per('accounts', account, 'hostname') | ||||
| 			contact = gajim.contacts.create_contact(jid = hostname) # Fake contact | ||||
| 			execute_command_menuitem.connect('activate', | ||||
| 				self.on_execute_command, contact, account) | ||||
| 			 | ||||
| 			gc_sub_menu = gtk.Menu() # gc is always a submenu | ||||
| 			join_group_chat_menuitem.set_submenu(gc_sub_menu) | ||||
|  | @ -2220,7 +2209,8 @@ class RosterWindow: | |||
| 			# make some items insensitive if account is offline | ||||
| 			if gajim.connections[account].connected < 2: | ||||
| 				for widget in [add_contact_menuitem, service_discovery_menuitem, | ||||
| 				join_group_chat_menuitem, new_message_menuitem]: | ||||
| 				join_group_chat_menuitem, new_message_menuitem, | ||||
| 				execute_command_menuitem]: | ||||
| 					widget.set_sensitive(False) | ||||
| 		else: | ||||
| 			xml = gtkgui_helpers.get_glade('zeroconf_context_menu.glade') | ||||
|  | @ -2360,7 +2350,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid) | |||
| 	def on_roster_treeview_key_press_event(self, widget, event): | ||||
| 		'''when a key is pressed in the treeviews''' | ||||
| 		self.tooltip.hide_tooltip() | ||||
| 		if event.keyval == gtk.keysyms.Escape: | ||||
| 		if event.keyval == gtk.keysyms.Menu: | ||||
| 			self.show_treeview_menu(event) | ||||
| 			return True | ||||
| 		elif event.keyval == gtk.keysyms.Escape: | ||||
| 			self.tree.get_selection().unselect_all() | ||||
| 		elif event.keyval == gtk.keysyms.F2: | ||||
| 			treeselection = self.tree.get_selection() | ||||
|  | @ -2368,10 +2361,12 @@ _('If "%s" accepts this request you will know his or her status.') % jid) | |||
| 			if len(list_of_paths) != 1: | ||||
| 				return | ||||
| 			path = list_of_paths[0] | ||||
| 			type_ = model[path][C_TYPE] | ||||
| 			if type_ in ('contact', 'group', 'agent'): | ||||
| 				iter = model.get_iter(path) | ||||
| 				self.on_rename(widget, iter, path) | ||||
| 			type = model[path][C_TYPE] | ||||
| 			if type in ('contact', 'group', 'agent'): | ||||
| 				if not model[path][C_EDITABLE]: | ||||
| 					# we are NOT already renaming it | ||||
| 					iter = model.get_iter(path) | ||||
| 					self.on_rename(widget, iter, path) | ||||
| 
 | ||||
| 		elif event.keyval == gtk.keysyms.Delete: | ||||
| 			treeselection = self.tree.get_selection() | ||||
|  | @ -3178,10 +3173,6 @@ _('If "%s" accepts this request you will know his or her status.') % jid) | |||
| 				self.tooltip.hide_tooltip() | ||||
| 				self.window.hide() | ||||
| 
 | ||||
| 	def on_roster_window_popup_menu(self, widget): | ||||
| 		event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) | ||||
| 		self.show_treeview_menu(event) | ||||
| 
 | ||||
| 	def quit_gtkgui_interface(self): | ||||
| 		'''When we quit the gtk interface : | ||||
| 		tell that to the core and exit gtk''' | ||||
|  | @ -3298,6 +3289,15 @@ _('If "%s" accepts this request you will know his or her status.') % jid) | |||
| 			return True | ||||
| 		return False | ||||
| 
 | ||||
| 	def on_execute_command(self, widget, contact, account, resource=None): | ||||
| 		'''Execute command. Full JID needed; if it is other contact, | ||||
| 		resource is necessary. Widget is unnecessary, only to be | ||||
| 		able to make this a callback.''' | ||||
| 		jid = contact.jid | ||||
| 		if resource is not None: | ||||
| 			jid = jid + u'/' + resource | ||||
| 		adhoc_commands.CommandWindow(account, jid) | ||||
| 
 | ||||
| 	def on_open_chat_window(self, widget, contact, account, resource = None): | ||||
| 		# Get the window containing the chat | ||||
| 		fjid = contact.jid | ||||
|  | @ -3420,6 +3420,91 @@ _('If "%s" accepts this request you will know his or her status.') % jid) | |||
| 			account = model[iter][C_ACCOUNT].decode('utf-8') | ||||
| 			self.draw_contact(jid, account) | ||||
| 
 | ||||
| 	def on_editing_started(self, cell, event, row): | ||||
| 		''' start editing a cell in the tree''' | ||||
| 		path = self.tree.get_cursor()[0] | ||||
| 		self.editing_path = path | ||||
| 
 | ||||
| 	def on_editing_canceled(self, cell): | ||||
| 		'''editing has been canceled''' | ||||
| 		path = self.tree.get_cursor()[0] | ||||
| 		# do not set new name if row order has changed | ||||
| 		if path != self.editing_path: | ||||
| 			self.editing_path = None | ||||
| 			return | ||||
| 		self.editing_path = None | ||||
| 		model = self.tree.get_model() | ||||
| 		iter = model.get_iter(path) | ||||
| 		account = model[iter][C_ACCOUNT].decode('utf-8') | ||||
| 		jid = model[iter][C_JID].decode('utf-8') | ||||
| 		type = model[iter][C_TYPE] | ||||
| 		# restore the number of resources string at the end of contact name | ||||
| 		contacts = gajim.contacts.get_contact(account, jid) | ||||
| 		if type in ('contact', 'agent'): | ||||
| 			self.draw_contact(jid, account) | ||||
| 		# reset editable to False | ||||
| 		model[iter][C_EDITABLE] = False | ||||
| 
 | ||||
| 	def on_cell_edited(self, cell, row, new_text): | ||||
| 		'''When an iter is edited: | ||||
| 		if text has changed, rename the contact''' | ||||
| 		model = self.tree.get_model() | ||||
| 		# if this is a last item in the group, row is invalid | ||||
| 		try: | ||||
| 			iter = model.get_iter_from_string(row) | ||||
| 		except: | ||||
| 			self.editing_path = None | ||||
| 			return | ||||
| 		path = model.get_path(iter) | ||||
| 		# do not set new name if row order has changed | ||||
| 		if path != self.editing_path: | ||||
| 			self.editing_path = None | ||||
| 			return | ||||
| 		self.editing_path = None | ||||
| 		new_text = new_text.decode('utf-8') | ||||
| 		account = model[iter][C_ACCOUNT].decode('utf-8') | ||||
| 		jid = model[iter][C_JID].decode('utf-8') | ||||
| 		type = model[iter][C_TYPE] | ||||
| 		model[iter][C_EDITABLE] = False | ||||
| 		if type in ('contact', 'agent'): | ||||
| 			old_text = gajim.contacts.get_contact_with_highest_priority(account, | ||||
| 				jid).name | ||||
| 			if old_text != new_text: | ||||
| 				for u in gajim.contacts.get_contact(account, jid): | ||||
| 					u.name = new_text | ||||
| 				gajim.connections[account].update_contact(jid, new_text, u.groups) | ||||
| 			self.draw_contact(jid, account) | ||||
| 			# Update opened chat | ||||
| 			ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) | ||||
| 			if ctrl: | ||||
| 				ctrl.update_ui() | ||||
| 				win = gajim.interface.msg_win_mgr.get_window(jid, account) | ||||
| 				win.redraw_tab(ctrl) | ||||
| 				win.show_title() | ||||
| 		elif type == 'group': | ||||
| 			# in C_JID column, we hold the group name (which is not escaped) | ||||
| 			old_name = model[iter][C_JID].decode('utf-8') | ||||
| 			# Groups may not change name from or to a special groups | ||||
| 			for g in helpers.special_groups: | ||||
| 				if g in (new_text, old_name): | ||||
| 					return | ||||
| 			# get all contacts in that group | ||||
| 			for jid in gajim.contacts.get_jid_list(account): | ||||
| 				contact = gajim.contacts.get_contact_with_highest_priority(account, | ||||
| 					jid) | ||||
| 				if old_name in contact.groups: | ||||
| 					# set them in the new one and remove it from the old | ||||
| 					contact.groups.remove(old_name) | ||||
| 					self.remove_contact(contact, account) | ||||
| 					if not new_text in contact.groups: | ||||
| 						contact.groups.append(new_text) | ||||
| 					self.add_contact_to_roster(contact.jid, account) | ||||
| 					gajim.connections[account].update_contact(contact.jid, | ||||
| 						contact.name, contact.groups) | ||||
| 			# If last removed iter was not visible, gajim.groups is not cleaned | ||||
| 			if gajim.groups[account].has_key(old_name): | ||||
| 				del gajim.groups[account][old_name] | ||||
| 
 | ||||
| 	def on_service_disco_menuitem_activate(self, widget, account): | ||||
| 		server_jid = gajim.config.get_per('accounts', account, 'hostname') | ||||
| 		if gajim.interface.instances[account]['disco'].has_key(server_jid): | ||||
|  | @ -4086,6 +4171,10 @@ _('If "%s" accepts this request you will know his or her status.') % jid) | |||
| 
 | ||||
| 	def _on_treeview_selection_changed(self, selection): | ||||
| 		model, list_of_paths = selection.get_selected_rows() | ||||
| 		if len(list_of_paths) == 1 and model[list_of_paths[0]][C_EDITABLE]: | ||||
| 			# We are editing this row, do not modify self._last_selected_contact | ||||
| 			# Cause that cancel editing | ||||
| 			return | ||||
| 		if len(self._last_selected_contact): | ||||
| 			# update unselected rows | ||||
| 			for (jid, account) in self._last_selected_contact: | ||||
|  | @ -4154,7 +4243,7 @@ _('If "%s" accepts this request you will know his or her status.') % jid) | |||
| 		self.gpg_passphrase = {} | ||||
| 
 | ||||
| 		#(icon, name, type, jid, account, editable, secondary_pixbuf) | ||||
| 		model = gtk.TreeStore(gtk.Image, str, str, str, str, gtk.gdk.Pixbuf) | ||||
| 		model = gtk.TreeStore(gtk.Image, str, str, str, str, bool, gtk.gdk.Pixbuf) | ||||
| 
 | ||||
| 		model.set_sort_func(1, self.compareIters) | ||||
| 		model.set_sort_column_id(1, gtk.SORT_ASCENDING) | ||||
|  | @ -4251,8 +4340,12 @@ _('If "%s" accepts this request you will know his or her status.') % jid) | |||
| 		col.set_cell_data_func(render_image, self.iconCellDataFunc, None) | ||||
| 
 | ||||
| 		render_text = gtk.CellRendererText() # contact or group or account name | ||||
| 		render_text.connect('edited', self.on_cell_edited) | ||||
| 		render_text.connect('editing-canceled', self.on_editing_canceled) | ||||
| 		render_text.connect('editing-started', self.on_editing_started) | ||||
| 		col.pack_start(render_text, expand = True) | ||||
| 		col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name | ||||
| 		col.add_attribute(render_text, 'editable', C_EDITABLE) # where we hold if the row is editable | ||||
| 		col.set_cell_data_func(render_text, self.nameCellDataFunc, None) | ||||
| 
 | ||||
| 		render_pixbuf = gtk.CellRendererPixbuf() # tls or avatar img | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue