1 /*
2 | Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
3 | Part of the gtkpod project.
4 |
5 | URL: http://www.gtkpod.org/
6 | URL: http://gtkpod.sourceforge.net/
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 | iTunes and iPod are trademarks of Apple
23 |
24 | This product is not supported/written/published by Apple!
25 |
26 | $Id$
27 */
28
29 #ifdef HAVE_CONFIG_H
30 # include <config.h>
31 #endif
32
33 #include <string.h>
34 #include "misc.h"
35 #include "confirmation.h"
36 #include "prefs.h"
37
38
39 static GHashTable *id_hash = NULL;
40
41 typedef struct {
42 GtkWidget *window;
43 GladeXML *window_xml;
44 gboolean scrolled;
45 gchar *option1_key;
46 gboolean option1_invert;
47 gchar *option2_key;
48 gboolean option2_invert;
49 gchar *confirm_again_key;
50 ConfHandler ok_handler;
51 ConfHandler apply_handler;
52 ConfHandler cancel_handler;
53 gpointer user_data1;
54 gpointer user_data2;
55 } ConfData;
56
57
58
59 /* cleanup hash, store window size */
cleanup(gpointer id)60 static void cleanup (gpointer id)
61 {
62 ConfData *cd;
63
64 cd = g_hash_table_lookup (id_hash, id);
65 if (cd)
66 {
67 gint defx, defy;
68 gtk_window_get_size (GTK_WINDOW (cd->window), &defx, &defy);
69 if (cd->scrolled)
70 {
71 prefs_set_int("size_conf_sw.x", defx);
72 prefs_set_int("size_conf_sw.y", defy);
73 }
74 else
75 {
76 prefs_set_int("size_conf.x", defx);
77 prefs_set_int("size_conf.y", defy);
78 }
79
80 gtk_widget_destroy (cd->window);
81 g_free (cd->option1_key);
82 g_free (cd->option2_key);
83 g_free (cd->confirm_again_key);
84
85 g_hash_table_remove (id_hash, id);
86 }
87 }
88
89
on_ok_clicked(GtkWidget * w,gpointer id)90 static void on_ok_clicked (GtkWidget *w, gpointer id)
91 {
92 ConfData *cd;
93
94 cd = g_hash_table_lookup (id_hash, id);
95 if (cd)
96 {
97 gtk_widget_set_sensitive (cd->window, FALSE);
98 if (cd->ok_handler)
99 cd->ok_handler (cd->user_data1, cd->user_data2);
100 cleanup (id);
101 }
102 }
103
on_apply_clicked(GtkWidget * w,gpointer id)104 static void on_apply_clicked (GtkWidget *w, gpointer id)
105 {
106 ConfData *cd;
107
108 cd = g_hash_table_lookup (id_hash, id);
109 if (cd)
110 {
111 gtk_widget_set_sensitive (cd->window, FALSE);
112 if (cd->apply_handler)
113 cd->apply_handler (cd->user_data1, cd->user_data2);
114 gtk_widget_set_sensitive (cd->window, TRUE);
115 }
116 }
117
on_cancel_clicked(GtkWidget * w,gpointer id)118 static void on_cancel_clicked (GtkWidget *w, gpointer id)
119 {
120 ConfData *cd;
121
122 cd = g_hash_table_lookup (id_hash, id);
123 if (cd)
124 {
125 gtk_widget_set_sensitive (cd->window, FALSE);
126 if (cd->cancel_handler)
127 cd->cancel_handler (cd->user_data1, cd->user_data2);
128 cleanup (id);
129 }
130 }
131
132
133 /* Handler to be used when the button should be displayed, but no
134 action is required */
CONF_NULL_HANDLER(gpointer d1,gpointer d2)135 void CONF_NULL_HANDLER (gpointer d1, gpointer d2)
136 {
137 return;
138 }
139
140
on_never_again_toggled(GtkToggleButton * t,gpointer id)141 static void on_never_again_toggled (GtkToggleButton *t, gpointer id)
142 {
143 ConfData *cd;
144
145 cd = g_hash_table_lookup (id_hash, id);
146 if (cd)
147 {
148 if (cd->confirm_again_key)
149 prefs_set_int(cd->confirm_again_key, !gtk_toggle_button_get_active(t));
150 }
151 }
152
on_option1_toggled(GtkToggleButton * t,gpointer id)153 static void on_option1_toggled (GtkToggleButton *t, gpointer id)
154 {
155 ConfData *cd;
156
157 cd = g_hash_table_lookup (id_hash, id);
158 if (cd)
159 {
160 if (cd->option1_key)
161 {
162 gboolean state = gtk_toggle_button_get_active(t);
163 if (cd->option1_invert) prefs_set_int (cd->option1_key, !state);
164 else prefs_set_int (cd->option1_key, state);
165 }
166 }
167 }
168
on_option2_toggled(GtkToggleButton * t,gpointer id)169 static void on_option2_toggled (GtkToggleButton *t, gpointer id)
170 {
171 ConfData *cd;
172
173 cd = g_hash_table_lookup (id_hash, id);
174 if (cd)
175 {
176 if (cd->option2_key)
177 {
178 gboolean state = gtk_toggle_button_get_active(t);
179 if (cd->option2_invert) prefs_set_int (cd->option2_key, !state);
180 else prefs_set_int (cd->option2_key, state);
181 }
182 }
183 }
184
on_response(GtkWidget * w,gint response,gpointer id)185 static void on_response (GtkWidget *w, gint response, gpointer id)
186 {
187 ConfData *cd;
188 /* printf ("r: %d, i: %d\n", response, id); */
189 cd = g_hash_table_lookup (id_hash, id);
190 if (cd)
191 {
192 switch (response)
193 {
194 case GTK_RESPONSE_OK:
195 on_ok_clicked (w, id);
196 break;
197 case GTK_RESPONSE_NONE:
198 case GTK_RESPONSE_CANCEL:
199 case GTK_RESPONSE_DELETE_EVENT:
200 on_cancel_clicked (w, id);
201 break;
202 case GTK_RESPONSE_APPLY:
203 on_apply_clicked (w, id);
204 break;
205 default:
206 g_warning ("Programming error: resonse '%d' received in on_response()\n", response);
207 on_cancel_clicked (w, id);
208 break;
209 }
210 }
211 }
212
confirm_append_text(GladeXML * xml,const gchar * text)213 static void confirm_append_text (GladeXML *xml, const gchar *text)
214 {
215 int i;
216 gchar **strings = g_strsplit (text, "\n", 0);
217 GtkTreeIter iter;
218 GtkAdjustment *adjustment;
219 GtkWidget *w = gtkpod_xml_get_widget (xml, "tree");
220 GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (w)));
221
222 for (i = 0; strings[i]; i++)
223 {
224 if (strings[i][0])
225 {
226 gtk_list_store_append (store, &iter);
227 gtk_list_store_set (store, &iter, 0, strings[i], -1);
228 }
229 }
230
231 w = gtkpod_xml_get_widget (xml, "scroller");
232 adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (w));
233 gtk_adjustment_set_value (adjustment, adjustment->upper - adjustment->page_size);
234
235 g_strfreev (strings);
236 }
237
238 /* gtkpod_confirmation(): open a confirmation window with the
239 information given. If "OK" is clicked, ok_handler() is called,
240 otherwise cancel_handler() is called, each with the parameters
241 "user_data1" and "user_data2". Use "NULL" if you want to
242 omit a parameter. If "confirm_again" is FALSE, ok_handler() is called
243 directly.
244
245 @id: an ID: only one window with a given positive id can be
246 open. Use negative ID if you don't care how many windows
247 are open at the same time (e.g. because they are modal).
248 With positive IDs @text is added to an already open window
249 with the same ID.
250 @modal: should the window be modal (i.e. block the program)?
251 @title: title of the window
252 @label: the text at the top of the window
253 @text: the text displayed in a scrolled window
254 @option_text: text for the option checkbox (or NULL)
255 @option_state: initial state of the option + a flag indicating
256 whether the handler should be called with the inverse state
257 of the toggle button: CONF_STATE_TRUE, CONF_STATE_FALSE,
258 CONF_STATE_INVERT_TRUE, CONF_STATE_INVERT_FALSE
259 @option_key: prefs key for the option (is set to the state of the
260 toggle box)
261 @confirm_again: state of the "confirm again" flag
262 @confirm_again_key: prefs key for the 'never show this dialog
263 again' toggle box (is set to the inverted current state
264 of the toggle box)
265 @ok_handler: function to be called when the OK button is pressed
266 @apply_handler: function to be called when the Apply button is pressed
267 @cancel_handler: function to be called when the cancel button is pressed
268 Note: in modal windows, the ok_, apply_, and cancel_handlers
269 must be set to CONF_NULL_HANDLER if the button is to be shown.
270 @user_data1: first argument to be passed to the ConfHandler
271 @user_data1: second argument to be passed to the ConfHandler
272
273 Pass NULL as "handler" if you want the corresponding button to be
274 hidden.
275
276 Pass CONF_NULL_HANDLER if you want the corresponding button to be
277 shown, but don't want to specify a handler.
278
279 Return value:
280
281 non-modal dialogs:
282
283 GTK_RESPONSE_REJECT: no window was opened because another window
284 with the same ID is already open. Text was appended.
285
286 GTK_RESPONSE_ACCEPT: either a window was opened, or ok_handler()
287 was called directly.
288
289 modal dialogs:
290
291 GTK_RESPONSE_REJECT: no window was opened because another window
292 with the same ID is already open. Text was appended.
293
294 GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, GTK_RESPONSE_APPLY:
295 OK/CANCEL/APPLY pressed. If the window is closed by the user,
296 GTK_RESPONSE_CANCEL will be returned.
297 */
298
gtkpod_confirmation(gint id,gboolean modal,const gchar * title,const gchar * label,const gchar * text,const gchar * option1_text,CONF_STATE option1_state,const gchar * option1_key,const gchar * option2_text,CONF_STATE option2_state,const gchar * option2_key,gboolean confirm_again,const gchar * confirm_again_key,ConfHandler ok_handler,ConfHandler apply_handler,ConfHandler cancel_handler,gpointer user_data1,gpointer user_data2)299 GtkResponseType gtkpod_confirmation (gint id,
300 gboolean modal,
301 const gchar *title,
302 const gchar *label,
303 const gchar *text,
304 const gchar *option1_text,
305 CONF_STATE option1_state,
306 const gchar *option1_key,
307 const gchar *option2_text,
308 CONF_STATE option2_state,
309 const gchar *option2_key,
310 gboolean confirm_again,
311 const gchar *confirm_again_key,
312 ConfHandler ok_handler,
313 ConfHandler apply_handler,
314 ConfHandler cancel_handler,
315 gpointer user_data1,
316 gpointer user_data2)
317 {
318 GtkWidget *window, *w;
319 ConfData *cd;
320 gint defx, defy;
321 GladeXML *confirm_xml;
322 GtkListStore *store;
323 GtkTreeViewColumn *column;
324 GtkCellRenderer *renderer;
325 gchar *full_label;
326
327 if (id_hash == NULL)
328 { /* initialize hash table to store IDs */
329 id_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal,
330 NULL, g_free);
331 }
332
333 if (id >= 0)
334 {
335 if ((cd = g_hash_table_lookup (id_hash, GINT_TO_POINTER(id))))
336 { /* window with same ID already open -- add @text and return */
337 if (text && *text && cd->window)
338 {
339 confirm_append_text (cd->window_xml, text);
340 }
341 return GTK_RESPONSE_REJECT;
342 }
343 }
344 else /* find free ID */
345 {
346 id = 0;
347
348 do
349 {
350 --id;
351 cd = g_hash_table_lookup (id_hash, GINT_TO_POINTER(id));
352 } while (cd != NULL);
353 }
354
355 if (!confirm_again)
356 {
357 /* This question was supposed to be asked "never again" ("don't
358 confirm again" -- so we just call the ok_handler */
359 if (ok_handler && !modal)
360 ok_handler (user_data1, user_data2);
361
362 if (!modal)
363 return GTK_RESPONSE_ACCEPT;
364
365 return GTK_RESPONSE_OK;
366 }
367
368 /* window = create_confirm_dialog (); */
369 confirm_xml = gtkpod_xml_new (xml_file, "confirm_dialog");
370 window = gtkpod_xml_get_widget (confirm_xml, "confirm_dialog");
371 glade_xml_signal_autoconnect (confirm_xml);
372
373 /* insert ID into hash table */
374 cd = g_new0 (ConfData, 1);
375 cd->window = window;
376 cd->window_xml = confirm_xml;
377 cd->option1_key = g_strdup(option1_key);
378 cd->option2_key = g_strdup(option2_key);
379 cd->confirm_again_key = g_strdup(confirm_again_key);
380 cd->ok_handler = ok_handler;
381 cd->apply_handler = apply_handler;
382 cd->cancel_handler = cancel_handler;
383 cd->user_data1 = user_data1;
384 cd->user_data2 = user_data2;
385 g_hash_table_insert (id_hash, GINT_TO_POINTER(id), cd);
386
387 full_label = g_markup_printf_escaped ("<span weight='bold' size='larger'>%s</span>\n\n%s",
388 title ? title : _("Confirmation"), label ? label : "");
389
390 /* Set label */
391 w = gtkpod_xml_get_widget (confirm_xml, "label");
392 gtk_label_set_markup (GTK_LABEL(w), full_label);
393 g_free (full_label);
394
395 /* Set text */
396 w = gtkpod_xml_get_widget (confirm_xml, "tree");
397 store = gtk_list_store_new (1, G_TYPE_STRING);
398 gtk_tree_view_set_model (GTK_TREE_VIEW (w), GTK_TREE_MODEL (store));
399 g_object_unref (store);
400
401 column = gtk_tree_view_column_new ();
402 renderer = gtk_cell_renderer_text_new ();
403
404 /*
405 gint mode, width;
406
407 g_object_get (G_OBJECT (renderer),
408 "wrap-mode", &mode,
409 "wrap-width", &width,
410 NULL);
411
412 printf ("wrap-mode: %d wrap-width:%d\n", mode, width);
413 */
414
415 /* I have no idea why 400, but neither 1000 nor 0 result in the
416 right behavior... */
417 g_object_set (G_OBJECT (renderer),
418 "wrap-mode", PANGO_WRAP_WORD_CHAR,
419 "wrap-width", 400,
420 NULL);
421
422 gtk_tree_view_column_pack_start (column, renderer, TRUE);
423 gtk_tree_view_column_set_attributes (column, renderer, "text", 0, NULL);
424 g_object_set_data (G_OBJECT (w), "renderer", renderer);
425
426 gtk_tree_view_append_column (GTK_TREE_VIEW (w), column);
427
428 if (text)
429 {
430 confirm_append_text (cd->window_xml, text);
431 cd->scrolled = TRUE;
432 defx = prefs_get_int("size_conf_sw.x");
433 defy = prefs_get_int("size_conf_sw.y");
434 }
435 else
436 {
437 /* no text -> hide widget */
438 if ((w = gtkpod_xml_get_widget (confirm_xml, "scroller")))
439 gtk_widget_hide (w);
440
441 cd->scrolled = FALSE;
442 defx = prefs_get_int("size_conf.x");
443 defy = prefs_get_int("size_conf.y");
444 }
445
446 gtk_widget_set_size_request (GTK_WIDGET (window), defx, defy);
447
448 /* Set "Option 1" checkbox */
449 w = gtkpod_xml_get_widget (confirm_xml, "option_vbox");
450
451 if (w && option1_key && option1_text)
452 {
453 gboolean state, invert;
454 GtkWidget *option1_button =
455 gtk_check_button_new_with_mnemonic (option1_text);
456
457 state = ((option1_state==CONF_STATE_INVERT_TRUE) || (option1_state==CONF_STATE_TRUE));
458 invert = ((option1_state==CONF_STATE_INVERT_FALSE) || (option1_state==CONF_STATE_INVERT_TRUE));
459 cd->option1_invert = invert;
460
461 gtk_widget_show (option1_button);
462 gtk_box_pack_start (GTK_BOX (w), option1_button, FALSE, FALSE, 0);
463 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(option1_button),
464 state);
465 g_signal_connect ((gpointer)option1_button,
466 "toggled",
467 G_CALLBACK (on_option1_toggled),
468 GINT_TO_POINTER(id));
469 }
470
471 /* Set "Option 2" checkbox */
472 w = gtkpod_xml_get_widget (confirm_xml, "option_vbox");
473 if (w && option2_key && option2_text)
474 {
475 gboolean state, invert;
476 GtkWidget *option2_button =
477 gtk_check_button_new_with_mnemonic (option2_text);
478
479 state = ((option2_state==CONF_STATE_INVERT_TRUE) || (option2_state==CONF_STATE_TRUE));
480 invert = ((option2_state==CONF_STATE_INVERT_FALSE) || (option2_state==CONF_STATE_INVERT_TRUE));
481 cd->option2_invert = invert;
482
483 gtk_widget_show (option2_button);
484 gtk_box_pack_start (GTK_BOX (w), option2_button, FALSE, FALSE, 0);
485 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(option2_button),
486 state);
487 g_signal_connect ((gpointer)option2_button,
488 "toggled",
489 G_CALLBACK (on_option2_toggled),
490 GINT_TO_POINTER(id));
491 }
492
493 /* Set "Never Again" checkbox */
494 w = gtkpod_xml_get_widget (confirm_xml, "never_again");
495
496 if (w && confirm_again_key)
497 {
498 /* connect signal */
499 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w),
500 !confirm_again);
501 g_signal_connect ((gpointer)w,
502 "toggled",
503 G_CALLBACK (on_never_again_toggled),
504 GINT_TO_POINTER(id));
505 }
506 else if (w)
507 {
508 /* hide "never again" button */
509 gtk_widget_hide (w);
510 }
511
512 /* Hide and set "default" button that can be activated by pressing
513 ENTER in the window (usually OK)*/
514 /* Hide or default CANCEL button */
515 if ((w = gtkpod_xml_get_widget (confirm_xml, "cancel")))
516 {
517 GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
518 gtk_widget_grab_default (w);
519
520 if (!cancel_handler)
521 gtk_widget_hide (w);
522 }
523
524 /* Hide or default APPLY button */
525 if ((w = gtkpod_xml_get_widget (confirm_xml, "apply")))
526 {
527 GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
528 gtk_widget_grab_default (w);
529
530 if (!apply_handler)
531 gtk_widget_hide (w);
532 }
533
534 /* Hide or default OK button */
535 if ((w = gtkpod_xml_get_widget (confirm_xml, "ok")))
536 {
537 GTK_WIDGET_SET_FLAGS (w, GTK_CAN_DEFAULT);
538 gtk_widget_grab_default (w);
539
540 if (!ok_handler)
541 gtk_widget_hide (w);
542 }
543
544 /* Connect Close window */
545 g_signal_connect (GTK_OBJECT (window),
546 "delete_event",
547 G_CALLBACK (on_cancel_clicked),
548 GINT_TO_POINTER(id));
549
550 if (modal)
551 {
552 /* use gtk_dialog_run() to block the application */
553 gint response = gtk_dialog_run (GTK_DIALOG (window));
554 /* cleanup hash, store window size */
555 cleanup (GINT_TO_POINTER(id));
556
557 switch (response)
558 {
559 case GTK_RESPONSE_OK:
560 case GTK_RESPONSE_APPLY:
561 return response;
562 default:
563 return GTK_RESPONSE_CANCEL;
564 }
565 }
566 else
567 {
568 /* Make sure we catch the response */
569 g_signal_connect (GTK_OBJECT (window),
570 "response",
571 G_CALLBACK (on_response),
572 GINT_TO_POINTER(id));
573 gtk_widget_show (window);
574
575 return GTK_RESPONSE_ACCEPT;
576 }
577 }
578
on_confirm_tree_size_allocate(GtkWidget * sender,GtkAllocation * allocation,gpointer e)579 G_MODULE_EXPORT void on_confirm_tree_size_allocate (GtkWidget *sender, GtkAllocation *allocation, gpointer e)
580 {
581 GtkCellRenderer *renderer = GTK_CELL_RENDERER (g_object_get_data (G_OBJECT (sender), "renderer"));
582 g_object_set (renderer, "wrap-width", allocation->width, NULL);
583 }
584
gtkpod_confirmation_simple(GtkWindow * parent,GtkMessageType icon,const gchar * primary_text,const gchar * secondary_text,const gchar * accept_button_text)585 gint gtkpod_confirmation_simple (GtkWindow *parent,
586 GtkMessageType icon,
587 const gchar *primary_text,
588 const gchar *secondary_text,
589 const gchar *accept_button_text)
590 {
591 return gtkpod_confirmation_hig(parent, icon, primary_text, secondary_text,
592 accept_button_text, NULL, NULL, NULL);
593 }
594
gtkpod_confirmation_hig(GtkWindow * parent,GtkMessageType icon,const gchar * primary_text,const gchar * secondary_text,const gchar * accept_button_text,const gchar * cancel_button_text,const gchar * third_button_text,const gchar * help_context)595 gint gtkpod_confirmation_hig (GtkWindow *parent,
596 GtkMessageType icon,
597 const gchar *primary_text,
598 const gchar *secondary_text,
599 const gchar *accept_button_text,
600 const gchar *cancel_button_text,
601 const gchar *third_button_text,
602 const gchar *help_context)
603 {
604 gint result;
605
606 GtkWidget *dialog =
607 gtk_message_dialog_new(parent,
608 GTK_DIALOG_MODAL,
609 icon,
610 GTK_BUTTONS_NONE,
611 "%s",
612 primary_text);
613
614 gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG(dialog),
615 "%s",
616 secondary_text);
617
618 if(third_button_text)
619 gtk_dialog_add_button(GTK_DIALOG(dialog), third_button_text, GTK_RESPONSE_APPLY);
620
621 gtk_dialog_add_buttons(GTK_DIALOG(dialog),
622 cancel_button_text ? cancel_button_text : GTK_STOCK_CANCEL,
623 GTK_RESPONSE_CANCEL,
624 accept_button_text ? accept_button_text : GTK_STOCK_OK,
625 GTK_RESPONSE_OK,
626 NULL);
627
628 result = gtk_dialog_run(GTK_DIALOG(dialog));
629 gtk_widget_destroy(dialog);
630
631 switch (result)
632 {
633 case GTK_RESPONSE_OK:
634 case GTK_RESPONSE_APPLY:
635 return result;
636 default:
637 return GTK_RESPONSE_CANCEL;
638 };
639 }
640