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