1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2001-2016 the Claws Mail team
4  * This file (C) 2005 Andrej Kacian <andrej@kacian.sk>
5  *
6  * - Plugin preferences
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21  */
22 
23 #ifdef HAVE_CONFIG_H
24 #  include "config.h"
25 #endif
26 
27 /* Global includes */
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include <gdk/gdkkeysyms.h>
31 
32 /* Claws Mail includes */
33 #include <inc.h>
34 #include <mainwindow.h>
35 #include <prefs_common.h>
36 #include <prefs_gtk.h>
37 
38 /* Local includes */
39 #include "rssyl.h"
40 #include "rssyl_feed.h"
41 #include "rssyl_feed_props.h"
42 #include "rssyl_prefs.h"
43 #include "rssyl_update_feed.h"
44 
rssyl_gtk_prop_store(RFolderItem * ritem)45 static void rssyl_gtk_prop_store(RFolderItem *ritem)
46 {
47 	gchar *url, *auth_user, *auth_pass;
48 	gint x, old_ri, old_fetch_comments;
49 	gboolean use_default_ri = FALSE, keep_old = FALSE;
50 	FolderItem *item;
51 
52 	g_return_if_fail(ritem != NULL);
53 	g_return_if_fail(ritem->feedprop != NULL);
54 	g_return_if_fail(ritem->url != NULL);
55 
56 	url = (gchar *)gtk_entry_get_text(GTK_ENTRY(ritem->feedprop->url));
57 
58 	if( strlen(url) && strcmp(ritem->url, url)) {
59 		rssyl_passwd_set(ritem, NULL);
60 		g_free(ritem->url);
61 		ritem->url = g_strdup(url);
62 	}
63 
64 	ritem->auth->type = gtk_combo_box_get_active(GTK_COMBO_BOX(ritem->feedprop->auth_type));
65 
66 	auth_user = (gchar *)gtk_entry_get_text(GTK_ENTRY(ritem->feedprop->auth_username));
67 	if (auth_user != NULL) {
68 		if (ritem->auth->username) {
69 			g_free(ritem->auth->username);
70 		}
71 		ritem->auth->username = g_strdup(auth_user);
72 	}
73 
74 	auth_pass = (gchar *)gtk_entry_get_text(GTK_ENTRY(ritem->feedprop->auth_password));
75 	rssyl_passwd_set(ritem, auth_pass);
76 
77 	use_default_ri = gtk_toggle_button_get_active(
78 			GTK_TOGGLE_BUTTON(ritem->feedprop->default_refresh_interval));
79 	ritem->default_refresh_interval = use_default_ri;
80 	debug_print("store: default refresh interval is %s\n",
81 			( use_default_ri ? "ON" : "OFF" ) );
82 
83 	/* Use default if checkbutton is set */
84 	if( use_default_ri )
85 		x = rssyl_prefs_get()->refresh;
86 	else
87 		x = gtk_spin_button_get_value_as_int(
88 				GTK_SPIN_BUTTON(ritem->feedprop->refresh_interval) );
89 
90 	old_ri = ritem->refresh_interval;
91 	ritem->refresh_interval = x;
92 
93 	/* Set timer for next automatic refresh, if needed. */
94 	if( x > 0 ) {
95 		if( old_ri != x || ritem->refresh_id == 0 ) {
96 			debug_print("RSSyl: GTK - refresh interval changed to %d , updating "
97 					"timeout\n", ritem->refresh_interval);
98 			rssyl_feed_start_refresh_timeout(ritem);
99 		}
100 	} else
101 		ritem->refresh_id = 0;
102 
103 	old_fetch_comments = ritem->fetch_comments;
104 	ritem->fetch_comments = gtk_toggle_button_get_active(
105 			GTK_TOGGLE_BUTTON(ritem->feedprop->fetch_comments));
106 
107 	if (!old_fetch_comments && ritem->fetch_comments) {
108 		/* reset the RFolderItem's mtime to be sure we get all
109 		 * available comments */
110 		 ritem->item.mtime = 0;
111 	}
112 
113 	ritem->fetch_comments_max_age = gtk_spin_button_get_value_as_int(
114 			GTK_SPIN_BUTTON(ritem->feedprop->fetch_comments_max_age));
115 
116 	keep_old = gtk_toggle_button_get_active(
117 			GTK_TOGGLE_BUTTON(ritem->feedprop->keep_old));
118 	ritem->keep_old = keep_old;
119 
120 	ritem->silent_update =
121 		gtk_combo_box_get_active(GTK_COMBO_BOX(ritem->feedprop->silent_update));
122 
123 	ritem->write_heading = gtk_toggle_button_get_active(
124 			GTK_TOGGLE_BUTTON(ritem->feedprop->write_heading));
125 
126 	ritem->ignore_title_rename = gtk_toggle_button_get_active(
127 			GTK_TOGGLE_BUTTON(ritem->feedprop->ignore_title_rename));
128 
129 	ritem->ssl_verify_peer = gtk_toggle_button_get_active(
130 			GTK_TOGGLE_BUTTON(ritem->feedprop->ssl_verify_peer));
131 
132 	/* Store updated properties */
133 	item = &ritem->item;
134 	item->folder->klass->item_get_xml(item->folder, item);
135 }
136 
137 static gboolean
rssyl_feedprop_togglebutton_toggled_cb(GtkToggleButton * tb,gpointer data)138 rssyl_feedprop_togglebutton_toggled_cb(GtkToggleButton *tb,
139 		gpointer data)
140 {
141 	gboolean active = gtk_toggle_button_get_active(tb);
142 	RFeedProp *feedprop = (RFeedProp *)data;
143 	GtkWidget *sb = NULL;
144 
145 	if( (GtkWidget *)tb == feedprop->default_refresh_interval ) {
146 		active = !active;
147 		sb = feedprop->refresh_interval;
148 	} else if( (GtkWidget *)tb == feedprop->fetch_comments ) {
149 		sb = feedprop->fetch_comments_max_age;
150 	}
151 
152 	g_return_val_if_fail(sb != NULL, FALSE);
153 
154 	gtk_widget_set_sensitive(sb, active);
155 
156 	return FALSE;
157 }
158 
159 static void
rssyl_feedprop_auth_type_changed_cb(GtkComboBox * cb,gpointer data)160 rssyl_feedprop_auth_type_changed_cb(GtkComboBox *cb, gpointer data)
161 {
162 	RFeedProp *feedprop = (RFeedProp *)data;
163 	gboolean enable = (FEED_AUTH_NONE != gtk_combo_box_get_active(cb));
164 	gtk_widget_set_sensitive(GTK_WIDGET(feedprop->auth_username), enable);
165 	gtk_widget_set_sensitive(GTK_WIDGET(feedprop->auth_password), enable);
166 }
167 
168 static gboolean
rssyl_props_cancel_cb(GtkWidget * widget,gpointer data)169 rssyl_props_cancel_cb(GtkWidget *widget, gpointer data)
170 {
171 	RFolderItem *ritem = (RFolderItem *)data;
172 
173 	debug_print("RSSyl: Cancel pressed\n");
174 
175 	gtk_widget_destroy(ritem->feedprop->window);
176 
177 	return FALSE;
178 }
179 
180 static gboolean
rssyl_props_ok_cb(GtkWidget * widget,gpointer data)181 rssyl_props_ok_cb(GtkWidget *widget, gpointer data)
182 {
183 	RFolderItem *ritem = (RFolderItem *)data;
184 
185 	debug_print("RSSyl: OK pressed\n");
186 	rssyl_gtk_prop_store(ritem);
187 
188 	gtk_widget_destroy(ritem->feedprop->window);
189 
190 	return FALSE;
191 }
192 
193 static gboolean
rssyl_props_trim_cb(GtkWidget * widget,gpointer data)194 rssyl_props_trim_cb(GtkWidget *widget, gpointer data)
195 {
196 	RFolderItem *ritem = (RFolderItem *)data;
197 	gboolean k = ritem->keep_old;
198 
199 	if( prefs_common_get_prefs()->work_offline &&
200 			!inc_offline_should_override(TRUE,
201 					ngettext("Claws Mail needs network access in order "
202 					"to update the feed.",
203 					"Claws Mail needs network access in order "
204 					"to update feeds.", 1))) {
205 		return FALSE;
206 	}
207 
208 	if( k )
209 		ritem->keep_old = FALSE;
210 
211 	rssyl_update_feed(ritem, FALSE);
212 
213 	if( k )
214 		ritem->keep_old = TRUE;
215 
216 	return FALSE;
217 }
218 
219 static gboolean
rssyl_props_key_press_cb(GtkWidget * widget,GdkEventKey * event,gpointer data)220 rssyl_props_key_press_cb(GtkWidget *widget, GdkEventKey *event,
221 		gpointer data)
222 {
223 	if (event) {
224 		switch (event->keyval) {
225 			case GDK_KEY_Escape:
226 				rssyl_props_cancel_cb(widget, data);
227 				return TRUE;
228 			case GDK_KEY_Return:
229 			case GDK_KEY_KP_Enter:
230 				rssyl_props_ok_cb(widget, data);
231 				return TRUE;
232 			default:
233 				break;
234 		}
235 	}
236 
237 	return FALSE;
238 }
239 
rssyl_gtk_prop(RFolderItem * ritem)240 void rssyl_gtk_prop(RFolderItem *ritem)
241 {
242 	MainWindow *mainwin = mainwindow_get_mainwindow();
243 	RFeedProp *feedprop;
244 	GtkWidget *vbox, *frame, *label, *hbox,
245 		*inner_vbox, *auth_hbox, *auth_user_label, *auth_pass_label,
246 		*bbox, *cancel_button, *cancel_align,
247 		*cancel_hbox, *cancel_image, *cancel_label, *ok_button, *ok_align,
248 		*ok_hbox, *ok_image, *ok_label, *trim_button, *silent_update_label;
249 	GtkObject *adj;
250 	gint refresh;
251 
252 	g_return_if_fail(ritem != NULL);
253 
254 	feedprop = g_new0(RFeedProp, 1);
255 
256 	/**** Create required widgets ****/
257 
258 	/* Window */
259 	feedprop->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
260 
261 	/* URL entry */
262 	feedprop->url = gtk_entry_new();
263 	gtk_entry_set_text(GTK_ENTRY(feedprop->url), ritem->url);
264 
265 	/* URL auth type combo */
266 	feedprop->auth_type = gtk_combo_box_text_new();
267 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(feedprop->auth_type),
268 			_("No authentication"));
269 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(feedprop->auth_type),
270 			_("HTTP Basic authentication"));
271 	gtk_combo_box_set_active(GTK_COMBO_BOX(feedprop->auth_type),
272 			ritem->auth->type);
273 
274 	/* Auth username */
275 	feedprop->auth_username = gtk_entry_new();
276 	gtk_entry_set_text(GTK_ENTRY(feedprop->auth_username),
277 			(ritem->auth->username != NULL ? ritem->auth->username : ""));
278 
279 	/* Auth password */
280 	feedprop->auth_password = gtk_entry_new();
281 	gtk_entry_set_visibility(GTK_ENTRY(feedprop->auth_password), FALSE);
282 	gchar *pwd = rssyl_passwd_get(ritem);
283 	gtk_entry_set_text(GTK_ENTRY(feedprop->auth_password),
284 			(pwd != NULL ? pwd : ""));
285 	if (pwd != NULL) {
286 		memset(pwd, 0, strlen(pwd));
287 		g_free(pwd);
288 	}
289 
290 	/* "Use default refresh interval" checkbutton */
291 	feedprop->default_refresh_interval = gtk_check_button_new_with_mnemonic(
292 			_("Use default refresh interval"));
293 	gtk_toggle_button_set_active(
294 			GTK_TOGGLE_BUTTON(feedprop->default_refresh_interval),
295 			ritem->default_refresh_interval);
296 
297 	if( ritem->refresh_interval >= 0 && !ritem->default_refresh_interval )
298 		refresh = ritem->refresh_interval;
299 	else
300 		refresh = rssyl_prefs_get()->refresh;
301 
302 	/* "Keep old items" checkbutton */
303 	feedprop->keep_old = gtk_check_button_new_with_mnemonic(
304 			_("Keep old items"));
305 	gtk_toggle_button_set_active(
306 			GTK_TOGGLE_BUTTON(feedprop->keep_old),
307 			ritem->keep_old);
308 
309 	/* "Trim" button */
310 	trim_button = gtk_button_new_with_mnemonic(_("_Trim"));
311 	gtk_widget_set_tooltip_text(trim_button,
312 			_("Update feed, deleting items which are no longer in the source feed"));
313 
314 	feedprop->fetch_comments = gtk_check_button_new_with_mnemonic(
315 			_("Fetch comments if possible"));
316 	gtk_toggle_button_set_active(
317 			GTK_TOGGLE_BUTTON(feedprop->fetch_comments),
318 			ritem->fetch_comments);
319 
320 	/* Fetch comments max age spinbutton */
321 	adj = gtk_adjustment_new(ritem->fetch_comments_max_age,
322 			-1, 100000, 1, 10, 0);
323 	feedprop->fetch_comments_max_age = gtk_spin_button_new(GTK_ADJUSTMENT(adj),
324 			1, 0);
325 
326 	/* Refresh interval spinbutton */
327 	adj = gtk_adjustment_new(refresh,
328 			0, 100000, 10, 100, 0);
329 	feedprop->refresh_interval = gtk_spin_button_new(GTK_ADJUSTMENT(adj),
330 			1, 0);
331 
332 	/* Silent update - combobox */
333 	feedprop->silent_update = gtk_combo_box_text_new();
334 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(feedprop->silent_update),
335 			_("Always mark it as new"));
336 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(feedprop->silent_update),
337 			_("Only mark it as new if its text has changed"));
338 	gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(feedprop->silent_update),
339 			_("Never mark it as new"));
340 	gtk_combo_box_set_active(GTK_COMBO_BOX(feedprop->silent_update),
341 			ritem->silent_update);
342 
343 	feedprop->write_heading = gtk_check_button_new_with_mnemonic(
344 			_("Add item title to the top of message"));
345 	gtk_toggle_button_set_active(
346 			GTK_TOGGLE_BUTTON(feedprop->write_heading),
347 			ritem->write_heading);
348 
349 	/* Ignore title rename - checkbox */
350 	feedprop->ignore_title_rename = gtk_check_button_new_with_mnemonic(
351 			_("Ignore title rename"));
352 	gtk_toggle_button_set_active(
353 			GTK_TOGGLE_BUTTON(feedprop->ignore_title_rename),
354 			ritem->ignore_title_rename);
355 	gtk_widget_set_tooltip_text(feedprop->ignore_title_rename,
356 			_("Enable this to keep current folder name, even if feed author changes title of the feed."));
357 
358 	/* Verify SSL peer certificate */
359 	feedprop->ssl_verify_peer = gtk_check_button_new_with_label(
360 			_("Verify SSL/TLS certificate validity"));
361 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(feedprop->ssl_verify_peer),
362 			ritem->ssl_verify_peer);
363 
364 	/* === Now pack all the widgets */
365 	vbox = gtk_vbox_new(FALSE, 5);
366 	gtk_container_add(GTK_CONTAINER(feedprop->window), vbox);
367 	gtk_container_set_border_width(GTK_CONTAINER(feedprop->window), 10);
368 
369 	inner_vbox = gtk_vbox_new(FALSE, 7);
370 	gtk_box_pack_start(GTK_BOX(inner_vbox), feedprop->url, FALSE, FALSE, 0);
371 	gtk_entry_set_activates_default(GTK_ENTRY(feedprop->url), TRUE);
372 
373 	/* Auth combo + user (label + entry) + pass (label + entry) */
374 	auth_hbox = gtk_hbox_new(FALSE, 7);
375 	gtk_box_pack_start(GTK_BOX(auth_hbox), feedprop->auth_type, FALSE, FALSE, 0);
376 	g_signal_connect(G_OBJECT(feedprop->auth_type), "changed",
377 			G_CALLBACK(rssyl_feedprop_auth_type_changed_cb),
378 			(gpointer) feedprop);
379 	g_signal_emit_by_name(G_OBJECT(feedprop->auth_type), "changed");
380 	auth_user_label = gtk_label_new(_("User name"));
381 	gtk_box_pack_start(GTK_BOX(auth_hbox), auth_user_label, FALSE, FALSE, 0);
382 	gtk_box_pack_start(GTK_BOX(auth_hbox), feedprop->auth_username, FALSE, FALSE, 0);
383 	auth_pass_label = gtk_label_new(_("Password"));
384 	gtk_box_pack_start(GTK_BOX(auth_hbox), auth_pass_label, FALSE, FALSE, 0);
385 	gtk_box_pack_start(GTK_BOX(auth_hbox), feedprop->auth_password, FALSE, FALSE, 0);
386 	gtk_box_pack_start(GTK_BOX(inner_vbox), auth_hbox, FALSE, FALSE, 0);
387 
388 	/* Verify SSL peer certificate - checkbutton */
389 	gtk_box_pack_start(GTK_BOX(inner_vbox), feedprop->ssl_verify_peer, FALSE, FALSE, 0);
390 	/* Ignore title rename - checkbutton */
391 	gtk_box_pack_start(GTK_BOX(inner_vbox), feedprop->ignore_title_rename, FALSE, FALSE, 0);
392 
393 	PACK_FRAME (vbox, frame, _("Source URL"));
394 	gtk_container_set_border_width(GTK_CONTAINER(inner_vbox), 7);
395 	gtk_container_add(GTK_CONTAINER(frame), inner_vbox);
396 
397 	inner_vbox = gtk_vbox_new(FALSE, 7);
398 	/* Fetch comments - checkbutton */
399 	g_signal_connect(G_OBJECT(feedprop->fetch_comments), "toggled",
400 			G_CALLBACK(rssyl_feedprop_togglebutton_toggled_cb),
401 			(gpointer)feedprop);
402 	gtk_box_pack_start(GTK_BOX(inner_vbox), feedprop->fetch_comments, FALSE, FALSE, 0);
403 
404 	hbox = gtk_hbox_new(FALSE, 7);
405 	/* Fetch comments max age - label */
406 	label = gtk_label_new(_("Fetch comments on posts aged less than"));
407 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
408 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
409 	/* Fetch comments max age - spinbutton */
410 	gtk_widget_set_sensitive(feedprop->fetch_comments_max_age,
411 			ritem->fetch_comments);
412 	gtk_box_pack_start(GTK_BOX(hbox), feedprop->fetch_comments_max_age, FALSE, FALSE, 0);
413 	/* Fetch comments max age - units label */
414 	label = gtk_label_new(g_strconcat(_("days"), "<small>    ",
415 				_("Set to -1 to fetch all comments"), "</small>", NULL));
416 	gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
417 	gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
418 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
419 	gtk_box_pack_start(GTK_BOX(inner_vbox), hbox, FALSE, FALSE, 0);
420 
421 	PACK_FRAME (vbox, frame, _("Comments"));
422 	gtk_container_set_border_width(GTK_CONTAINER(inner_vbox), 7);
423 	gtk_container_add(GTK_CONTAINER(frame), inner_vbox);
424 
425 	inner_vbox = gtk_vbox_new(FALSE, 7);
426 	hbox = gtk_hbox_new(FALSE, 7);
427 	/* Write heading - checkbox */
428 	gtk_box_pack_start(GTK_BOX(inner_vbox), feedprop->write_heading, FALSE, FALSE, 0);
429 	/* Keep old items - checkbutton */
430 	gtk_box_pack_start(GTK_BOX(hbox), feedprop->keep_old, FALSE, FALSE, 0);
431 	/* 'Trim' - button */
432 	gtk_box_pack_start(GTK_BOX(hbox), trim_button, FALSE, FALSE, 0);
433 	g_signal_connect(G_OBJECT(trim_button), "clicked",
434 			G_CALLBACK(rssyl_props_trim_cb), ritem);
435 	gtk_box_pack_start(GTK_BOX(inner_vbox), hbox, FALSE, FALSE, 0);
436 
437 	hbox = gtk_hbox_new(FALSE, 7);
438 	/* Silent update - label */
439 	silent_update_label = gtk_label_new(_("If an item changes"));
440 	gtk_box_pack_start(GTK_BOX(hbox), silent_update_label, FALSE, FALSE, 0);
441 	gtk_box_pack_start(GTK_BOX(hbox), feedprop->silent_update, FALSE, FALSE, 0);
442 	gtk_box_pack_start(GTK_BOX(inner_vbox), hbox, FALSE, FALSE, 0);
443 
444 	PACK_FRAME (vbox, frame, _("Items"));
445 	gtk_container_set_border_width(GTK_CONTAINER(inner_vbox), 7);
446 	gtk_container_add(GTK_CONTAINER(frame), inner_vbox);
447 
448 	inner_vbox = gtk_vbox_new(FALSE, 7);
449 	/* Use default refresh interval - checkbutton */
450 	gtk_box_pack_start(GTK_BOX(inner_vbox), feedprop->default_refresh_interval, FALSE, FALSE, 0);
451 	g_signal_connect(G_OBJECT(feedprop->default_refresh_interval), "toggled",
452 			G_CALLBACK(rssyl_feedprop_togglebutton_toggled_cb),
453 			(gpointer)feedprop);
454 
455 	hbox = gtk_hbox_new(FALSE, 7);
456 	/* Refresh interval - label */
457 	label = gtk_label_new(_("Refresh interval"));
458 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
459 	/* Refresh interval - spinbutton */
460 	gtk_widget_set_sensitive(feedprop->refresh_interval,
461 			!ritem->default_refresh_interval);
462 	gtk_box_pack_start(GTK_BOX(hbox), feedprop->refresh_interval, FALSE, FALSE, 0);
463 	/* Refresh interval - units label */
464 	label = gtk_label_new(g_strconcat(_("minutes"), "<small>    ",
465 			_("Set to 0 to disable automatic refreshing for this feed"),
466 			"</small>", NULL));
467 	gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
468 	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
469 	gtk_box_pack_start(GTK_BOX(inner_vbox), hbox, FALSE, FALSE, 0);
470 
471 	PACK_FRAME (vbox, frame, _("Refresh"));
472 	gtk_container_set_border_width(GTK_CONTAINER(inner_vbox), 7);
473 	gtk_container_add(GTK_CONTAINER(frame), inner_vbox);
474 
475 	/* Buttonbox */
476 	bbox = gtk_hbutton_box_new();
477 	gtk_container_set_border_width(GTK_CONTAINER(bbox), 5);
478 	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
479 	gtk_box_set_spacing(GTK_BOX(bbox), 5);
480 	gtk_box_pack_end(GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
481 
482 	/* Cancel button */
483 	cancel_button = gtk_button_new();
484 	gtk_container_add(GTK_CONTAINER(bbox), cancel_button);
485 
486 	cancel_align = gtk_alignment_new(0.5, 0.5, 0, 0);
487 	gtk_container_add(GTK_CONTAINER(cancel_button), cancel_align);
488 
489 	cancel_hbox = gtk_hbox_new(FALSE, 2);
490 	gtk_container_add(GTK_CONTAINER(cancel_align), cancel_hbox);
491 
492 	cancel_image = gtk_image_new_from_stock(GTK_STOCK_CANCEL,
493 			GTK_ICON_SIZE_BUTTON);
494 	gtk_box_pack_start(GTK_BOX(cancel_hbox), cancel_image, FALSE, FALSE, 0);
495 
496 	cancel_label = gtk_label_new_with_mnemonic(_("_Cancel"));
497 	gtk_box_pack_end(GTK_BOX(cancel_hbox), cancel_label, FALSE, FALSE, 0);
498 
499 	g_signal_connect(G_OBJECT(cancel_button), "clicked",
500 			G_CALLBACK(rssyl_props_cancel_cb), ritem);
501 
502 	/* OK button */
503 	ok_button = gtk_button_new();
504 	gtk_container_add(GTK_CONTAINER(bbox), ok_button);
505 	gtk_widget_set_can_default(ok_button, TRUE);
506 
507 	ok_align = gtk_alignment_new(0.5, 0.5, 0, 0);
508 	gtk_container_add(GTK_CONTAINER(ok_button), ok_align);
509 
510 	ok_hbox = gtk_hbox_new(FALSE, 2);
511 	gtk_container_add(GTK_CONTAINER(ok_align), ok_hbox);
512 
513 	ok_image = gtk_image_new_from_stock(GTK_STOCK_OK,
514 			GTK_ICON_SIZE_BUTTON);
515 	gtk_box_pack_start(GTK_BOX(ok_hbox), ok_image, FALSE, FALSE, 0);
516 
517 	ok_label = gtk_label_new_with_mnemonic(_("_OK"));
518 	gtk_box_pack_end(GTK_BOX(ok_hbox), ok_label, FALSE, FALSE, 0);
519 
520 	g_signal_connect(G_OBJECT(ok_button), "clicked",
521 			G_CALLBACK(rssyl_props_ok_cb), ritem);
522 
523 	/* Set some misc. stuff */
524 	gtk_window_set_title(GTK_WINDOW(feedprop->window),
525 			g_strdup(_("Set feed properties")) );
526 	gtk_window_set_modal(GTK_WINDOW(feedprop->window), TRUE);
527 	gtk_window_set_transient_for(GTK_WINDOW(feedprop->window),
528 			GTK_WINDOW(mainwin->window) );
529 
530 	/* Attach callbacks to handle Enter and Escape keys */
531 	g_signal_connect(G_OBJECT(feedprop->window), "key_press_event",
532 			G_CALLBACK(rssyl_props_key_press_cb), ritem);
533 
534 	/* ...and voila! */
535 	gtk_widget_show_all(feedprop->window);
536 	gtk_widget_grab_default(ok_button);
537 
538 	/* Unselect the text in URL entry */
539 	gtk_editable_select_region(GTK_EDITABLE(feedprop->url), 0, 0);
540 
541 	ritem->feedprop = feedprop;
542 }
543