Hopefully fixed all the modal dialogs combinations.
[captive.git] / src / install / acquire / ui-gnome.c
1 /* $Id$
2  * Drivers acquiring installation utility
3  * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include "ui-gnome.h"   /* self */
23 #include <glib/gmessages.h>
24 #include "moduriload.h"
25 #include "main.h"
26 #include <gtk/gtkmain.h>
27 #include <gtk/gtktreeviewcolumn.h>
28 #include <gtk/gtkbox.h>
29 #include <sys/time.h>
30 #include <libgnomeui/gnome-app.h>
31 #include <gtk/gtktreestore.h>
32 #include <gtk/gtkframe.h>
33 #include <gtk/gtkentry.h>
34 #include <libgnomeui/gnome-druid.h>
35 #include <libgnomeui/gnome-app-util.h>
36 #include <libgnomeui/gnome-druid-page-edge.h>
37 #include "final.h"
38 #include <libgnomevfs/gnome-vfs-utils.h>
39 #include <libgnomeui/gnome-dialog.h>
40
41 #include <captive/macros.h>
42 #include <captive/captivemodid.h>
43
44
45 /* Config: */
46 #define PROGRESS_UPDATE_USEC 200000
47 /* Although proper GTK+ locking is provided below there are some
48  * bugs with compatibility of GTK+/Gnome-VFS/GConf.
49  * The main thread executes gtk_main()->g_main_loop_run()
50  * while the working thread initializes Gnome-VFS by GConf and
51  * executes also g_main_loop_run() while sharing some poll() fds.
52  */
53 /* #define UI_GNOME_THREADS 1 */
54
55
56 static GnomeApp *App;
57 static GtkTreeStore *DriversTreeStore;
58 static GtkFrame *DriversFrame;
59 static GtkFrame *ProgressFrame;
60 static GtkEntry *ProgressEntry;
61 static GnomeDruid *Druid;
62 static GtkButton *DruidButtonSkip;
63 static GtkButton *DruidButtonOK;
64 static GtkTreeView *DriversTreeView;
65 static GnomeDruidPage *PageStart;
66 static GnomeDruidPage *ScanDiskPage;
67 static GnomeDruidPage *ScanPathPage;
68 static GnomeDruidPage *MicrosoftComPage;
69 static GnomeDruidPage *PageFinish;
70 static GtkEntry *ScanPathLocationComboEntry;
71 static GtkButton *MicrosoftComConfirmButton;
72 static GtkProgressBar *MicrosoftComProgress;
73 enum {
74                 DRIVERS_TREE_STORE_COLUMN_TYPE,
75                 DRIVERS_TREE_STORE_COLUMN_ID,
76                 DRIVERS_TREE_STORE_COLUMN_NUM,  /* total # */
77                 };
78 #define DRIVERS_TREE_STORE_COLUMN_TYPE_LIST G_TYPE_STRING,G_TYPE_STRING
79
80
81 /* map: (gchar *)type-> (GtkTreeIter *) */
82 static GHashTable *DriversTreeStore_Iter_hash;
83
84 static void DriversTreeStore_Iter_hash_key_destroy_func(gchar *type)
85 {
86         g_return_if_fail(type!=NULL);
87
88         g_free(type);
89 }
90
91 static void DriversTreeStore_Iter_hash_init(void)
92 {
93         if (DriversTreeStore_Iter_hash)
94                 return;
95         DriversTreeStore_Iter_hash=g_hash_table_new_full(g_str_hash,g_str_equal,
96                         (GDestroyNotify)DriversTreeStore_Iter_hash_key_destroy_func,
97                         NULL);  /* value_destroy_func */
98 }
99
100 static GtkTreeIter *DriversTreeStore_Iter_hash_get_iter(const gchar *type)
101 {
102 GtkTreeIter *r;
103
104         g_return_val_if_fail(type!=NULL,NULL);
105
106         DriversTreeStore_Iter_hash_init();
107         if (!(r=g_hash_table_lookup(DriversTreeStore_Iter_hash,type))) {
108                 captive_new(r);
109                 gtk_tree_store_append(DriversTreeStore,
110                                 r,      /* iter */
111                                 NULL);  /* parent */
112                 g_hash_table_insert(DriversTreeStore_Iter_hash,g_strdup(type),r);
113                 }
114
115         return r;
116 }
117
118 static gboolean some_modules_found=FALSE;
119 static gboolean in_progress=FALSE;
120 static GnomeDruidPage *page_active;
121
122 static void state_changed(void)
123 {
124         /* Not yet initialized? */
125         if (!App)
126                 return;
127
128         gtk_widget_set_sensitive(GTK_WIDGET(DruidButtonSkip),
129                         (page_active!=PageStart && page_active!=PageFinish));
130         gtk_widget_set_sensitive(GTK_WIDGET(DruidButtonOK),some_modules_found);
131
132         if (in_progress) {
133                 gtk_widget_set_sensitive(Druid->next,FALSE);
134                 gtk_widget_set_sensitive(GTK_WIDGET(DruidButtonOK),some_modules_found);
135                 gtk_widget_set_sensitive(GTK_WIDGET(MicrosoftComConfirmButton),FALSE);
136                 }
137         else {
138                 /* It is checked by GTK+ whether the text changed: */
139                 gtk_entry_set_text(ProgressEntry,"");
140                 gtk_widget_set_sensitive(Druid->next,
141                                 (page_active!=PageFinish && page_active!=MicrosoftComPage));
142                 gtk_widget_set_sensitive(GTK_WIDGET(MicrosoftComConfirmButton),
143                                 (page_active==MicrosoftComPage));
144                 gtk_progress_bar_set_fraction(MicrosoftComProgress,(gdouble)0);
145                 gtk_progress_bar_set_text(MicrosoftComProgress,"");
146                 }
147 }
148
149 static void ui_gnome_module_best_priority_notify(const gchar *module_type)
150 {
151 GtkTreeIter *iter;
152
153         g_return_if_fail(module_type!=NULL);
154
155         gdk_threads_enter();
156
157         iter=DriversTreeStore_Iter_hash_get_iter(module_type);
158         gtk_tree_store_set(DriversTreeStore,iter,
159                         DRIVERS_TREE_STORE_COLUMN_TYPE,module_type,
160                         DRIVERS_TREE_STORE_COLUMN_ID  ,
161                                         ((0
162                                                                         || !strcmp(module_type,"ntoskrnl.exe")
163                                                                         || !strcmp(module_type,"ntfs.sys"))
164                                                         ? _("NOT FOUND; essential module for NTFS disks access")
165                                                         : _("not found; optional module")),
166                         -1);
167
168         gdk_flush();
169         gdk_threads_leave();
170 }
171
172 static void ui_gnome_module_available_notify(struct module_available *module_available)
173 {
174 GtkTreeIter *iter;
175 static gboolean some_module_ntoskrnl_exe_found=FALSE;
176 static gboolean some_module_ntfs_sys_found=FALSE;
177
178         g_return_if_fail(module_available!=NULL);
179         g_return_if_fail(module_available->module!=NULL);
180
181         gdk_threads_enter();
182
183         iter=DriversTreeStore_Iter_hash_get_iter((const gchar *)module_available->module->type);
184         gtk_tree_store_set(DriversTreeStore,iter,
185                         DRIVERS_TREE_STORE_COLUMN_TYPE,module_available->module->type,
186                         DRIVERS_TREE_STORE_COLUMN_ID  ,module_available->module->id,
187                         -1);
188
189         if (!strcmp((const char *)module_available->module->type,"ntoskrnl.exe"))
190                 some_module_ntoskrnl_exe_found=TRUE;
191         if (!strcmp((const char *)module_available->module->type,"ntfs.sys"))
192                 some_module_ntfs_sys_found=TRUE;
193
194         some_modules_found=some_module_ntoskrnl_exe_found && some_module_ntfs_sys_found;
195         state_changed();
196
197         gdk_flush();
198         gdk_threads_leave();
199 }
200
201 static gboolean all_modules_found=FALSE;
202
203 static void ui_gnome_all_modules_found_notify(void)
204 {
205         gdk_threads_enter();
206
207         all_modules_found=TRUE;
208         state_changed();
209
210         gdk_flush();
211         gdk_threads_leave();
212 }
213
214 static gboolean aborted=FALSE;
215 static gboolean aborted_back=FALSE;     /* 'Back' button was clicked. */
216 static struct timeval ProgressEntry_updated_timeval;
217 static struct timeval ProgressBar_updated_timeval;
218
219 static void progress_start(void)
220 {
221         in_progress=TRUE;
222         aborted=FALSE;
223         aborted_back=FALSE;
224         CAPTIVE_MEMZERO(&ProgressEntry_updated_timeval);
225         CAPTIVE_MEMZERO(&ProgressBar_updated_timeval);
226         state_changed();
227 }
228
229 static void progress_end(void)
230 {
231         in_progress=FALSE;
232         state_changed();
233 }
234
235 static gboolean want_progress_update(struct timeval *timeval)
236 {
237 struct timeval now_timeval;
238 struct timeval diff_timeval;
239
240         g_return_val_if_fail(timeval!=NULL,FALSE);
241
242         gettimeofday(   /* FIXME: errors ignored */
243                         &now_timeval,   /* tv */
244                         NULL);  /* tz */
245         timersub(&now_timeval,timeval,&diff_timeval);
246         if (!timeval->tv_sec || diff_timeval.tv_sec>0 || diff_timeval.tv_usec>=PROGRESS_UPDATE_USEC) {
247                 *timeval=now_timeval;
248                 return TRUE;
249                 }
250         return FALSE;
251 }
252
253 static gboolean ui_gnome_progress(GnomeVFSURI *uri)
254 {
255 gboolean want_gdk_flush=FALSE;
256
257         /* 'uri' may be NULL */
258
259         gdk_threads_enter();
260
261         if (ProgressEntry) {
262 static gchar *uri_text=NULL;
263
264                 /* Store 'uri' on each call (not just if 'diff_timeval' permits)
265                  * as we may get into long cabinet extraction phase with 'uri==NULL' calls
266                  * where we want to display the currently processed 'uri'.
267                  */
268                 if (uri) {
269                         g_free(uri_text);
270                         uri_text=gnome_vfs_uri_to_string(uri,GNOME_VFS_URI_HIDE_PASSWORD);
271                         }
272
273                 if (uri_text) {
274                         if (want_progress_update(&ProgressEntry_updated_timeval)) {
275                                 gtk_entry_set_text(ProgressEntry,
276                                                 uri_text+(strncmp(uri_text,"file://",strlen("file://")) ? 0 : strlen("file://")));
277                                 want_gdk_flush=TRUE;
278                                 }
279                         }
280                 }
281
282 #ifndef UI_GNOME_THREADS
283         while (g_main_context_pending(NULL))
284                 g_main_context_iteration(
285                                 NULL,   /* context */
286                                 FALSE); /* may_block */
287 #endif /* UI_GNOME_THREADS */
288
289         if (want_gdk_flush)
290                 gdk_flush();
291         gdk_threads_leave();
292
293         /* Do not: g_thread_yield();
294          * as it is TOO much expensive and we are multithreaded anyway.
295          */
296
297         if (aborted)
298                 return TRUE;
299         if (all_modules_found)
300                 return TRUE;
301
302         return FALSE;
303 }
304
305 static void ui_gnome_progress_bar(gint done,gint length)
306 {
307         g_return_if_fail(done>=0);
308         g_return_if_fail(length>=0);
309
310         if (!want_progress_update(&ProgressBar_updated_timeval))
311                 return;
312
313         gdk_threads_enter();
314
315         if (!length) {
316                 gtk_progress_bar_pulse(MicrosoftComProgress);
317                 gtk_progress_bar_set_text(MicrosoftComProgress,"");
318                 }
319         else {
320 gchar *length_display;
321
322                 /* Do not format 'done' by gnome_vfs_format_file_size_for_display()
323                  * as the progress would not be visible for large 'done' sizes.
324                  */
325                 gtk_progress_bar_set_fraction(MicrosoftComProgress,((gdouble)done)/length);
326                 length_display=gnome_vfs_format_file_size_for_display(length);
327                 gtk_progress_bar_set_text(MicrosoftComProgress,
328                                 captive_printf_alloca("%d B / %s",(int)done,length_display));
329                 g_free(length_display);
330                 }
331
332         gdk_flush();
333         gdk_threads_leave();
334 }
335
336 /* FIXME: Change it to "prepare" signal. */
337 void on_Page_map(GtkWidget *vbox_widget,GtkWidget *page_widget)
338 {
339         /* Handle non-object (NULL) signal with reversed parameters? */
340         if (GNOME_IS_DRUID_PAGE(vbox_widget) && page_widget==NULL) {
341                 page_widget=vbox_widget;
342                 vbox_widget=NULL;
343                 }
344         g_return_if_fail(vbox_widget==NULL || GTK_IS_VBOX(vbox_widget));
345         g_return_if_fail(GNOME_IS_DRUID_PAGE(page_widget));
346
347         page_active=GNOME_DRUID_PAGE(page_widget);
348         if (page_active==PageFinish) {
349                 gnome_druid_set_show_finish(Druid,FALSE);       /* set it each time */
350                 /**/ if (!some_modules_found)
351                         gnome_druid_page_edge_set_text(GNOME_DRUID_PAGE_EDGE(PageFinish),_(
352                                         "We need at least some version of drivers essential for this project:"
353                                         " ntoskrnl.exe and ntfs.sys. Please click 'Back' button to obtain them"
354                                         " by several methods offered by this installer."));
355                 else {
356 gchar *text;
357
358                         text=final_text(all_modules_found);
359                         gnome_druid_page_edge_set_text(GNOME_DRUID_PAGE_EDGE(PageFinish),text);
360                         g_free(text);
361                         }
362                 }
363         if (page_active==ScanPathPage)
364                 gtk_widget_grab_focus(GTK_WIDGET(ScanPathLocationComboEntry));
365         state_changed();
366
367         if (!vbox_widget)
368                 return;
369
370         /* FIXME: 'freeze' apparently does not help 'repositioning' of
371          * 'DriversTreeView' during first 'map' of each 'Page'.
372          */
373         gtk_widget_freeze_child_notify(vbox_widget);
374
375         gtk_widget_reparent(GTK_WIDGET(DriversFrame),vbox_widget);
376         gtk_widget_reparent(GTK_WIDGET(ProgressFrame),vbox_widget);
377
378         gtk_box_reorder_child(GTK_BOX(vbox_widget),GTK_WIDGET(DriversFrame),
379                         0);     /* position */
380
381         gtk_box_set_child_packing(GTK_BOX(vbox_widget),GTK_WIDGET(DriversFrame),
382                         FALSE,  /* expand */
383                         TRUE,   /* fill */
384                         0,      /* padding */
385                         GTK_PACK_START);
386         gtk_box_set_child_packing(GTK_BOX(vbox_widget),GTK_WIDGET(ProgressFrame),
387                         FALSE,  /* expand */
388                         TRUE,   /* fill */
389                         0,      /* padding */
390                         GTK_PACK_START);
391
392         /* FIXME: Needed to fix (0,0)-position inside parent GdkWindow. */
393         gtk_widget_queue_resize(GTK_WIDGET(DriversTreeView));
394         gtk_widget_queue_resize(GTK_WIDGET(ProgressEntry));
395
396         gtk_widget_thaw_child_notify(vbox_widget);
397 }
398
399 typedef void (*process_t)(void);
400
401 #ifdef UI_GNOME_THREADS
402 /* 'GThreadFunc' type. */
403 gpointer execute_process_func(process_t process /* data */)
404 {
405         (*process)();
406
407         gdk_threads_enter();
408
409         gtk_main_quit();        /* Abort gtk_main() of execute_process(). */
410
411         gdk_flush();
412         gdk_threads_leave();
413
414         return NULL;
415 }
416 #endif /* UI_GNOME_THREADS */
417
418 /* We are called inside gdk_threads_enter(). */
419 static void execute_process(process_t process)
420 {
421 #ifdef UI_GNOME_THREADS
422 GThread *gthread;
423 #endif /* UI_GNOME_THREADS */
424
425         progress_start();
426 #ifdef UI_GNOME_THREADS
427         gthread=g_thread_create_full(
428                         (GThreadFunc)execute_process_func,      /* func */
429                         process,        /* data */
430                         0,      /* stack_size; 0 means the default size */
431                         TRUE,   /* joinable */
432                         TRUE,   /* bound; use system thread */
433                         G_THREAD_PRIORITY_LOW,  /* priority; G_THREAD_PRIORITY_LOW is the lowest one */
434                         NULL);  /* error */
435         gtk_main();     /* We are already called inside gdk_threads_enter(). */
436         /* I hope some other gtk_main_quit() did not occur as we would
437          * locked if the 'process' func did not finish yet.
438          */
439         g_thread_join(gthread);
440 #else /* UI_GNOME_THREADS */
441         (*process)();
442 #endif /* UI_GNOME_THREADS */
443         progress_end();
444 }
445
446 /* 'process_t' typed. */
447 static void process_scan_disk(void)
448 {
449         scan_disks_quick();
450         scan_disks();
451 }
452
453 static GnomeVFSURI *process_scan_path_scan_path_uri;
454
455 /* 'process_t' typed. */
456 static void process_scan_path(void)
457 {
458         mod_uri_load_base_reporting(process_scan_path_scan_path_uri);
459 }
460
461 /* 'process_t' typed. */
462 static void process_microsoft_com(void)
463 {
464         microsoft_com();
465 }
466
467 gboolean on_Page_next(GnomeDruidPage *gnomedruidpage,GtkWidget *widget,gpointer user_data /* unused */)
468 {
469         g_return_val_if_fail(GNOME_IS_DRUID_PAGE(gnomedruidpage),FALSE);
470
471         if (in_progress)        /* bogus callback - we should be non-sensitive */
472                 return TRUE;    /* ignore button press */
473
474         /**/ if (page_active==PageStart) {
475                 if (all_modules_found) {
476                         gnome_druid_set_page(Druid,PageFinish);
477                         return TRUE;    /* ignore button press */
478                         }
479                 }
480         else if (page_active==ScanDiskPage) {
481                 execute_process(process_scan_disk);
482                 if (aborted_back) {
483                         gnome_druid_set_page(Druid,PageStart);
484                         return TRUE;    /* ignore button press */
485                         }
486                 if (all_modules_found) {
487                         gnome_druid_set_page(Druid,PageFinish);
488                         return TRUE;    /* ignore button press */
489                         }
490                 return FALSE;   /* proceed to next page */
491                 }
492         else if (page_active==ScanPathPage) {
493 const gchar *scan_path_uri_text=gtk_entry_get_text(ScanPathLocationComboEntry);
494
495                 if (scan_path_uri_text && *scan_path_uri_text) {
496 GnomeVFSURI *scan_path_uri;
497
498                         if ((scan_path_uri=gnome_vfs_uri_new(scan_path_uri_text))) {
499                                 process_scan_path_scan_path_uri=scan_path_uri;
500                                 execute_process(process_scan_path);
501                                 gnome_vfs_uri_unref(scan_path_uri);
502                                 if (aborted_back) {
503                                         gnome_druid_set_page(Druid,(all_modules_found ? PageStart : ScanDiskPage));
504                                         return TRUE;    /* ignore button press */
505                                         }
506                                 if (all_modules_found) {
507                                         gnome_druid_set_page(Druid,PageFinish);
508                                         return TRUE;    /* ignore button press */
509                                         }
510                                 gtk_entry_set_text(ScanPathLocationComboEntry,"");
511                                 }
512                         else
513                                 g_warning(_("Invalid URI: %s"),scan_path_uri_text);
514                         gtk_widget_grab_focus(GTK_WIDGET(ScanPathLocationComboEntry));
515                         return TRUE;    /* ignore button press; we cleared the URI entry */
516                         }
517                 return FALSE;   /* proceed to next page */
518                 }
519
520         return FALSE;   /* proceed to next page */
521 }
522
523 void on_MicrosoftComConfirmButton_clicked(GtkButton *button,gpointer user_data)
524 {
525         g_return_if_fail(GTK_IS_BUTTON(button));
526
527         if (in_progress)        /* bogus callback */
528                 return;
529         if (page_active!=MicrosoftComPage)      /* bogus callback */
530                 return;
531
532         execute_process(process_microsoft_com);
533         if (aborted_back) {
534                 gnome_druid_set_page(Druid,(all_modules_found ? PageStart : ScanPathPage));
535                 return;
536                 }
537         if (all_modules_found) {
538                 gnome_druid_set_page(Druid,PageFinish);
539                 return;
540                 }
541
542         gnome_druid_set_page(Druid,PageFinish);
543 }
544
545 void on_DruidButtonSkip_clicked(GtkButton *button,gpointer user_data /* unused */)
546 {
547         g_return_if_fail(GTK_IS_BUTTON(button));
548
549         if (in_progress) {
550                 aborted=TRUE;
551                 state_changed();
552                 return;
553                 }
554         if (all_modules_found)
555                 gnome_druid_set_page(Druid,PageFinish);
556         else if (page_active==ScanDiskPage)
557                 gnome_druid_set_page(Druid,ScanPathPage);
558         else if (page_active==ScanPathPage)
559                 gnome_druid_set_page(Druid,MicrosoftComPage);
560         else if (page_active==MicrosoftComPage)
561                 gnome_druid_set_page(Druid,PageFinish);
562 }
563
564 gboolean on_Page_back(GnomeDruidPage *gnomedruidpage,GtkWidget *widget,gpointer user_data)
565 {
566         g_return_val_if_fail(GNOME_IS_DRUID_PAGE(gnomedruidpage),FALSE);
567
568         if (!in_progress) {
569                 if (all_modules_found) {
570                         gnome_druid_set_page(Druid,PageStart);
571                         return TRUE;    /* ignore button press */
572                         }
573                 return FALSE;   /* proceed to previous page */
574                 }
575
576         aborted=TRUE;
577         aborted_back=TRUE;
578         state_changed();
579
580         return TRUE;    /* ignore button press now; we will respect 'aborted_back' */
581 }
582
583 void on_Druid_cancel(GnomeDruid *gnomedruid,gpointer user_data /* unused */)
584 {
585         g_return_if_fail(GNOME_IS_DRUID(gnomedruid));
586
587         /* gtk_main_quit() would not abort the current operation. */
588         exit(EXIT_SUCCESS);
589 }
590
591 static void on_DruidButtonOK_clicked_dialog_callback(gint reply,gint *replyp /* data */)
592 {
593         g_return_if_fail(reply>=0);
594         g_return_if_fail(replyp!=NULL);
595
596         *replyp=reply;
597         gtk_main_quit();
598 }
599
600 void on_DruidButtonOK_clicked(GtkButton *button,gpointer user_data /* unused */)
601 {
602 GtkWidget *dialog;
603 gint reply;
604
605         g_return_if_fail(GTK_IS_BUTTON(button));
606
607         if (all_modules_found)
608                 exit(EXIT_SUCCESS);
609
610         /* TODO: Avoid dialog if already on Finish page. */
611         reply=-1;
612         dialog=gnome_app_ok_cancel_modal(App,_(
613                         "Although essential modules (\"ntoskrnl.exe\" and \"ntfs.sys\") are available "
614                         "you may still want to get their better version and/or more modules. "
615                         "Really quit?"),
616                         (GnomeReplyCallback)on_DruidButtonOK_clicked_dialog_callback,
617                         &reply);        /* data */
618         g_signal_connect((gpointer)dialog,"close",G_CALLBACK(gtk_main_quit),NULL);
619         gnome_dialog_set_close(GNOME_DIALOG(dialog),FALSE);
620         /* Never call gtk_main() from other thread than the initial one.
621          * We would have to switch GTK+ context (g_main_context()?).
622          */
623         gtk_main();
624         if (reply==0)   /* 0 for 'OK', 1 for 'Cancel', left -1 for dialog close. */
625                 exit(EXIT_SUCCESS);
626         /* It is still needed despite: gnome_dialog_set_close(,TRUE);
627          * in: libgnomeui-2.10.0-1
628          * There may be some races regarding when is ran: gtk_main_quit();
629          */
630         gtk_widget_destroy(dialog);
631 }
632
633 static void button_stock_set_label(GtkWidget *widget,const gchar *label_text_new /* callback_data */)
634 {
635         g_return_if_fail(GTK_IS_WIDGET(widget));
636         g_return_if_fail(label_text_new!=NULL);
637
638         /**/ if (GTK_IS_CONTAINER(widget))
639                 gtk_container_foreach(GTK_CONTAINER(widget),
640                                 (GtkCallback)button_stock_set_label,    /* callback */
641                                 (/* de-conts */ gchar *)label_text_new);        /* callback_data */
642         else if (GTK_IS_LABEL(widget))
643                 gtk_label_set_text_with_mnemonic(GTK_LABEL(widget),label_text_new);
644 }
645
646 static void PageFinish_set_label_attr(GtkWidget *widget,gpointer callback_data /* unused */)
647 {
648         g_return_if_fail(GTK_IS_WIDGET(widget));
649
650         /**/ if (GTK_IS_CONTAINER(widget))
651                 gtk_container_foreach(GTK_CONTAINER(widget),
652                                 (GtkCallback)PageFinish_set_label_attr, /* callback */
653                                 callback_data); /* callback_data; unused */
654         else if (GTK_IS_LABEL(widget) && gtk_label_get_line_wrap(GTK_LABEL(widget)))
655                 gtk_label_set_selectable(GTK_LABEL(widget),TRUE);
656 }
657
658 /* of "ui-gnome-interface.h": */
659 GtkWidget *create_App(void);
660 /* of "ui-gnome-support.h": */
661 GtkWidget *lookup_widget(GtkWidget *widget,const gchar *widget_name);
662
663 static void App_init(void)
664 {
665 GtkTreeViewColumn *column;
666 GtkCellRenderer *cell;
667 GtkBox *druid_button_box;
668
669         gdk_threads_enter();
670
671         App=GNOME_APP(create_App());
672
673         DriversTreeView=GTK_TREE_VIEW(lookup_widget(GTK_WIDGET(App),"DriversTreeView"));
674         DriversFrame=GTK_FRAME(lookup_widget(GTK_WIDGET(App),"DriversFrame"));
675         ProgressFrame=GTK_FRAME(lookup_widget(GTK_WIDGET(App),"ProgressFrame"));
676         Druid=GNOME_DRUID(lookup_widget(GTK_WIDGET(App),"Druid"));
677         PageStart=GNOME_DRUID_PAGE(lookup_widget(GTK_WIDGET(App),"PageStart"));
678         ScanDiskPage=GNOME_DRUID_PAGE(lookup_widget(GTK_WIDGET(App),"ScanDiskPage"));
679         ScanPathPage=GNOME_DRUID_PAGE(lookup_widget(GTK_WIDGET(App),"ScanPathPage"));
680         MicrosoftComPage=GNOME_DRUID_PAGE(lookup_widget(GTK_WIDGET(App),"MicrosoftComPage"));
681         PageFinish=GNOME_DRUID_PAGE(lookup_widget(GTK_WIDGET(App),"PageFinish"));
682         ScanPathLocationComboEntry=GTK_ENTRY(lookup_widget(GTK_WIDGET(App),"ScanPathLocationComboEntry"));
683         MicrosoftComConfirmButton=GTK_BUTTON(lookup_widget(GTK_WIDGET(App),"MicrosoftComConfirmButton"));
684         MicrosoftComProgress=GTK_PROGRESS_BAR(lookup_widget(GTK_WIDGET(App),"MicrosoftComProgress"));
685         ProgressEntry=GTK_ENTRY(lookup_widget(GTK_WIDGET(App),"ProgressEntry"));
686
687         druid_button_box=GTK_BOX(gtk_widget_get_parent(Druid->next));
688
689         DriversTreeStore=gtk_tree_store_new(DRIVERS_TREE_STORE_COLUMN_NUM,DRIVERS_TREE_STORE_COLUMN_TYPE_LIST);
690         gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(DriversTreeStore),
691                         DRIVERS_TREE_STORE_COLUMN_TYPE,GTK_SORT_ASCENDING);
692         gtk_tree_view_set_model(DriversTreeView,GTK_TREE_MODEL(DriversTreeStore));
693
694         column=gtk_tree_view_column_new();
695         cell=gtk_cell_renderer_text_new();
696         gtk_tree_view_column_pack_start(column,cell,
697                         TRUE);  /* expand */
698         gtk_tree_view_column_set_attributes(column,cell,
699                         "text",DRIVERS_TREE_STORE_COLUMN_TYPE,
700                         NULL);
701         gtk_tree_view_append_column(DriversTreeView,column);
702
703         column=gtk_tree_view_column_new();
704         cell=gtk_cell_renderer_text_new();
705         gtk_tree_view_column_pack_start(column,cell,
706                         TRUE);  /* expand */
707         gtk_tree_view_column_set_attributes(column,cell,
708                         "text",DRIVERS_TREE_STORE_COLUMN_ID,
709                         NULL);
710         gtk_tree_view_append_column(DriversTreeView,column);
711
712         /* gnome_druid_set_show_finish() just replaces Next<->Finish buttons displayed. */
713         gtk_widget_hide(GTK_WIDGET(Druid->finish));
714
715         DruidButtonSkip=GTK_BUTTON(gtk_button_new_from_stock(GTK_STOCK_REDO));
716         button_stock_set_label(
717                         GTK_WIDGET(DruidButtonSkip),    /* widget */
718                         _("_Skip"));    /* label_text_new */
719         gtk_box_pack_end(druid_button_box,GTK_WIDGET(DruidButtonSkip),FALSE,TRUE,0);
720         gtk_widget_show(GTK_WIDGET(DruidButtonSkip));
721         g_signal_connect((gpointer)DruidButtonSkip,"clicked",G_CALLBACK(on_DruidButtonSkip_clicked),NULL);
722
723         DruidButtonOK=GTK_BUTTON(gtk_button_new_from_stock(GTK_STOCK_OK));
724         gtk_box_pack_end(druid_button_box,GTK_WIDGET(DruidButtonOK),FALSE,TRUE,0);
725         gtk_widget_show(GTK_WIDGET(DruidButtonOK));
726         g_signal_connect((gpointer)DruidButtonOK,"clicked",G_CALLBACK(on_DruidButtonOK_clicked),NULL);
727
728         PageFinish_set_label_attr(
729                         GTK_WIDGET(PageFinish), /* widget */
730                         NULL);  /* callback_data; unused */
731
732         state_changed();
733
734         gdk_threads_leave();
735 }
736
737 static void ui_gnome_g_log_handler(const gchar *log_domain,GLogLevelFlags log_level,const gchar *message,gpointer user_data)
738 {
739 GtkWidget *dialog;
740
741         /* Ignore arrors by cabextract during its abortion. */
742         if (in_progress && aborted)
743                 return;
744
745         gdk_threads_enter();
746
747         /**/ if (log_level & G_LOG_LEVEL_ERROR)
748                 dialog=gnome_app_error(App,message);
749         else if (log_level & (G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING))
750                 dialog=gnome_app_warning(App,message);
751         else
752                 dialog=gnome_app_message(App,message);
753
754         gtk_window_set_modal(GTK_WINDOW(dialog),TRUE);
755         /* See also around: gnome_dialog_set_close(); */
756         gnome_dialog_set_close(GNOME_DIALOG(dialog),FALSE);
757         g_signal_connect((gpointer)dialog,"close"  ,G_CALLBACK(gtk_main_quit),NULL);
758         g_signal_connect((gpointer)dialog,"clicked",G_CALLBACK(gtk_main_quit),NULL);
759         gtk_main();
760         /* See also around: gnome_dialog_set_close(); */
761         gtk_widget_destroy(dialog);
762
763         gdk_flush();
764         gdk_threads_leave();
765 }
766
767 static void ui_gnome_interactive(void)
768 {
769         gdk_threads_enter();
770
771         /* Postpone gtk_widget_show_all() from App_init() here
772          * to have already passed all ui_gnome_module_available_notify().
773          */
774         gnome_druid_set_page(Druid,MicrosoftComPage);
775         gtk_widget_show_all(GTK_WIDGET(App));
776 #if 0
777         /* gnome_druid_set_page(Druid,PageStart); */
778         gnome_druid_set_page(Druid,ScanDiskPage);
779         gnome_druid_set_page(Druid,ScanPathPage);
780         /* gnome_druid_set_page(Druid,MicrosoftComPage); */
781         gnome_druid_set_page(Druid,PageFinish);
782 #endif
783         gnome_druid_set_page(Druid,PageStart);
784
785         gtk_main();
786
787         gdk_threads_leave();
788
789         exit(EXIT_SUCCESS);
790 }
791
792 gboolean ui_gnome_init(void)
793 {
794         acquire_module_available_notify=ui_gnome_module_available_notify;
795         acquire_module_all_modules_found_notify=ui_gnome_all_modules_found_notify;
796         ui_progress=ui_gnome_progress;
797         ui_progress_bar=ui_gnome_progress_bar;
798         ui_interactive=ui_gnome_interactive;
799         captive_captivemodid_module_best_priority_notify=ui_gnome_module_best_priority_notify;
800
801 #ifdef UI_GNOME_THREADS
802         /* gdk_threads_init() must be called before gtk_init()!
803          * gtk_init() gets called by create_App() here.
804          */
805         if (!g_thread_supported())
806                 g_thread_init(NULL);
807         if (!gdk_threads_mutex)
808                 gdk_threads_init();
809 #endif /* UI_GNOME_THREADS */
810
811         /* Graphic widgets will all be hidden yet. */
812         App_init();
813         /* ui_gnome_g_log_handler() needs 'App'. */
814         g_log_set_handler(
815                         G_LOG_DOMAIN,   /* log_domain; "Captive" */
816                         G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL,    /* log_levels */
817                         ui_gnome_g_log_handler, /* log_func */
818                         NULL);  /* user_data */
819
820         return TRUE;
821 }