Merging upstream version 0.2.1.
[debian/lxappearance.git] / src / main-dlg.c
1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif
4
5 #include <gtk/gtk.h>
6 #include <gdk/gdkx.h>
7
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <unistd.h>
11 #include <string.h>
12
13 /* for kill & waitpid */
14 #include <sys/types.h>
15 #include <signal.h>
16 #include <sys/wait.h>
17
18 #include "main-dlg.h"
19 #include "main-dlg-ui.h"
20 #include "glade-support.h"
21
22 enum {
23 COL_DISP_NAME,
24 COL_NAME,
25 N_COLS
26 };
27
28 #define GET_WIDGET_WITH_TYPE(name, type) name = type( lookup_widget( dlg, #name ))
29
30 #define INIT_LIST(name, prop) \
31 GET_WIDGET_WITH_TYPE( name##_view, GTK_TREE_VIEW ); \
32 name##_list = init_tree_view( name##_view, G_CALLBACK(on_list_sel_changed), prop); \
33 load_##name##s( name##_list, name##_name );
34
35 #define enable_apply() gtk_dialog_set_response_sensitive(GTK_DIALOG (main_dlg), GTK_RESPONSE_APPLY, TRUE )
36 #define disable_apply() gtk_dialog_set_response_sensitive(GTK_DIALOG (main_dlg), GTK_RESPONSE_APPLY, FALSE )
37
38 extern gboolean under_lxde; /* wether lxde-xsettings daemon is active */
39
40 extern GtkWidget* main_dlg; /* defined in main.c */
41
42 static GtkTreeView* gtk_theme_view = NULL;
43 static GtkListStore* gtk_theme_list = NULL;
44
45 static GtkTreeView* icon_theme_view = NULL;
46 static GtkListStore* icon_theme_list = NULL;
47
48 static char* gtk_theme_name = NULL;
49 static char* icon_theme_name = NULL;
50 static char* font_name = NULL;
51 static GtkToolbarStyle tb_style = GTK_TOOLBAR_BOTH;
52
53 extern char tmp_rc_file[];
54 static char* rc_file = NULL;
55
56 /*
57 static GtkTreeView* font_view = NULL;
58 static GtkListStore* font_list = NULL;
59 */
60 static GtkWidget* demo_box = NULL;
61 static GtkWidget* demo_socket = NULL;
62 static GPid demo_pid = 0;
63
64 /* older versions of glib don't provde these API */
65 #if ! GLIB_CHECK_VERSION(2, 8, 0)
66
67 #include <sys/types.h>
68 #include <sys/stat.h>
69 #include <unistd.h>
70 #include <string.h>
71 #include <errno.h>
72
73 int mkdir_with_parents(const gchar *pathname, int mode)
74 {
75 struct stat statbuf;
76 char *dir, *sep;
77 dir = g_strdup( pathname );
78 sep = dir[0] == '/' ? dir + 1 : dir;
79 do {
80 sep = strchr( sep, '/' );
81 if( G_LIKELY( sep ) )
82 *sep = '\0';
83
84 if( stat( dir, &statbuf) == 0 )
85 {
86 if( ! S_ISDIR(statbuf.st_mode) ) /* parent not dir */
87 goto err;
88 }
89 else /* stat failed */
90 {
91 if( errno == ENOENT ) /* not exists */
92 {
93 if( mkdir( dir, mode ) == -1 )
94 goto err;
95 }
96 else
97 goto err; /* unknown error */
98 }
99
100 if( G_LIKELY( sep ) )
101 {
102 *sep = '/';
103 ++sep;
104 }
105 else
106 break;
107 }while( sep );
108 g_free( dir );
109 return 0;
110 err:
111 g_free( dir );
112 return -1;
113 }
114 #endif
115
116
117 static void reload_demo_process()
118 {
119 char* argv[5];
120 char wid[16];
121
122 if( demo_pid > 0 ) /* kill old demo */
123 {
124 int stat;
125 kill( demo_pid, SIGTERM );
126 waitpid( demo_pid, &stat, 0 );
127 demo_pid = 0;
128 }
129
130 g_snprintf( wid, 16, "%ld", gtk_socket_get_id(GTK_SOCKET (demo_socket)) );
131
132 argv[0] = g_get_prgname();
133 argv[1] = "demo";
134 argv[2] = wid;
135 argv[3] = tmp_rc_file;
136 argv[4] = NULL;
137 g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, &demo_pid, NULL );
138
139 /* reloading demo means the current selected theme is changed */
140 enable_apply();
141 }
142
143 static void write_rc_file( const char* path )
144 {
145 FILE* f;
146 static char* tb_styles[] = {
147 "GTK_TOOLBAR_ICONS",
148 "GTK_TOOLBAR_TEXT",
149 "GTK_TOOLBAR_BOTH",
150 "GTK_TOOLBAR_BOTH_HORIZ"
151 };
152
153 if( f = fopen( path, "w" ) )
154 {
155 fputs( "# DO NOT EDIT! This file will be overwritten by LXAppearance.\n"
156 "# Any customization should be done in ~/.gtkrc-2.0.mine\n\n", f );
157
158 fprintf( f, "gtk-theme-name=\"%s\"\n", gtk_theme_name );
159 fprintf( f, "gtk-icon-theme-name=\"%s\"\n", icon_theme_name );
160 fprintf( f, "gtk-font-name=\"%s\"\n", font_name );
161 fprintf( f, "gtk-toolbar-style=%d\n", tb_style );
162
163 fprintf( f, "include \"%s/.gtkrc-2.0.mine\"\n", g_get_home_dir() );
164
165 fclose( f );
166 }
167 }
168
169 static void create_lxde_config_dir()
170 {
171 char* dir = g_build_filename( g_get_user_config_dir(), "lxde", NULL );
172 #if GTK_CHECK_VERSION( 2, 8, 0 )
173 g_mkdir_with_parents( dir, 0755 );
174 #else
175 mkdir_with_parents( dir, 0755 );
176 #endif
177 g_free( dir );
178 }
179
180 static void write_lxde_config()
181 {
182 FILE* f;
183 char* file, *data;
184 gsize len;
185 GKeyFile* kf = g_key_file_new();
186 gboolean ret;
187
188 file = g_build_filename( g_get_user_config_dir(), "lxde/config", NULL );
189 ret = g_key_file_load_from_file( kf, file, G_KEY_FILE_KEEP_COMMENTS, NULL );
190
191 if( ! ret )
192 {
193 const gchar* const * dir;
194 const gchar* const * dirs = g_get_system_data_dirs();
195 create_lxde_config_dir();
196 /* load system-wide config file */
197 for( dir = dirs; *dir; ++dir )
198 {
199 char* path = g_build_filename( *dir, "lxde/config", NULL );
200 ret = g_key_file_load_from_file( kf, path, 0, NULL );
201 g_free( path );
202 if( ret )
203 break;
204 }
205 }
206
207 g_key_file_set_string( kf, "GTK", "sNet/ThemeName", gtk_theme_name );
208 g_key_file_set_string( kf, "GTK", "sNet/IconThemeName", icon_theme_name );
209 g_key_file_set_string( kf, "GTK", "sGtk/FontName", font_name );
210 g_key_file_set_integer( kf, "GTK", "iGtk/ToolbarStyle", tb_style );
211
212 data = g_key_file_to_data( kf, &len, NULL );
213 g_key_file_free( kf );
214
215 if( f = fopen( file, "w" ) )
216 {
217 fwrite( data, sizeof(char), len, f );
218 fclose( f );
219 }
220 g_free( data );
221 }
222
223 static void reload_theme()
224 {
225
226 gtk_rc_reparse_all();
227 }
228
229 static void on_list_sel_changed( GtkTreeSelection* sel, const char* prop )
230 {
231 GtkTreeIter it;
232 GtkTreeModel* model;
233 if( gtk_tree_selection_get_selected( sel, &model, &it ) )
234 {
235 char* name;
236 gtk_tree_model_get( model, &it, COL_NAME, &name, -1 );
237
238 if( model == GTK_TREE_MODEL (gtk_theme_list) ) /* gtk+ theme */
239 {
240 if( name && gtk_theme_name && 0 == strcmp( name, gtk_theme_name ) )
241 goto out;
242 g_free( gtk_theme_name );
243 gtk_theme_name = name;
244
245 if( under_lxde )
246 g_object_set( gtk_settings_get_default(), "gtk-theme-name", name, NULL );
247 }
248 else if( model == GTK_TREE_MODEL (icon_theme_list) ) /* icon theme */
249 {
250 if( name && icon_theme_name && 0 == strcmp( name, icon_theme_name ) )
251 goto out;
252 g_free( icon_theme_name );
253 icon_theme_name = name;
254
255 if( under_lxde )
256 g_object_set( gtk_settings_get_default(), "gtk-icon-theme-name", name, NULL );
257 }
258
259 if( under_lxde )
260 {
261 enable_apply();
262 }
263 else
264 {
265 write_rc_file( tmp_rc_file );
266 //gtk_rc_reparse_all_for_settings(gtk_settings_get_default(), TRUE);
267 reload_demo_process();
268 }
269 return;
270 out:
271 g_free( name );
272 }
273 }
274
275 static gint sort_func( GtkTreeModel* model, GtkTreeIter* it1, GtkTreeIter* it2, gpointer data )
276 {
277 char* str1, *str2;
278 int ret;
279 gtk_tree_model_get( model, it1, COL_DISP_NAME, &str1, -1 );
280 gtk_tree_model_get( model, it2, COL_DISP_NAME, &str2, -1 );
281 ret = g_utf8_collate( str1, str2 );
282 g_free( str1 );
283 g_free( str2 );
284 return ret;
285 }
286
287 static GtkListStore* init_tree_view( GtkTreeView* view, GCallback on_sel_changed, const char* prop )
288 {
289 GtkTreeViewColumn* col;
290 GtkListStore* list;
291 GtkTreeSelection* sel;
292 int text_col = strcmp(prop, "gtk-theme-name") ? COL_DISP_NAME : COL_NAME;
293
294 col = gtk_tree_view_column_new_with_attributes( NULL, gtk_cell_renderer_text_new(),
295 "text", text_col, NULL );
296 gtk_tree_view_append_column( view, col );
297
298 sel = gtk_tree_view_get_selection(view);
299 g_signal_connect( sel, "changed", on_sel_changed, GINT_TO_POINTER(prop) );
300
301 list = gtk_list_store_new( N_COLS, G_TYPE_STRING, G_TYPE_STRING );
302 gtk_tree_sortable_set_sort_func( (GtkTreeSortable*)list, text_col, sort_func, NULL, NULL );
303 gtk_tree_view_set_model( view, (GtkTreeModel*)list );
304 g_object_unref( list );
305 return list;
306 }
307
308 typedef gboolean (*ThemeFunc)(const char* file, const char* dir, const char* name, GtkListStore* list, GtkTreeIter* it);
309
310 static void load_themes_from_dir( GtkListStore* list,
311 const char* dir_path,
312 const char* lookup,
313 GtkTreeSelection* sel,
314 const char* init_sel,
315 ThemeFunc theme_func )
316 {
317 GDir* dir;
318 if( dir = g_dir_open( dir_path, 0, NULL ) )
319 {
320 const char* name;
321 while( name = g_dir_read_name( dir ) )
322 {
323 char* file = g_build_filename( dir_path, name, lookup, NULL );
324 if( g_file_test( file, G_FILE_TEST_EXISTS ) )
325 {
326 gboolean add = TRUE;
327 GtkTreeIter it;
328
329 /* prevent duplication */
330 if( gtk_tree_model_get_iter_first(GTK_TREE_MODEL (list), &it ) )
331 {
332 char* _name;
333 do {
334 _name = NULL;
335 gtk_tree_model_get(GTK_TREE_MODEL (list), &it, COL_NAME, &_name, -1);
336 if( _name && strcmp(_name, name) == 0 )
337 {
338 add = FALSE;
339 g_free(_name);
340 break;
341 }
342 g_free(_name);
343 }
344 while( gtk_tree_model_iter_next(GTK_TREE_MODEL (list), &it ) );
345 }
346
347 if( add )
348 {
349 gtk_list_store_append( list, &it );
350 gtk_list_store_set( list, &it, COL_NAME, name, -1 );
351
352 if( theme_func )
353 {
354 if( ! theme_func(file, (char *) dir, name, list, &it) )
355 add = FALSE;
356 }
357
358 if( add )
359 {
360 if( 0 == strcmp( name, init_sel ) )
361 {
362 GtkTreeView* view;
363 GtkTreePath* tp;
364 gtk_tree_selection_select_iter( sel, &it );
365 view = gtk_tree_selection_get_tree_view( sel );
366 tp = gtk_tree_model_get_path( (GtkTreeModel*)list, &it );
367 gtk_tree_view_scroll_to_cell( view, tp, NULL, FALSE, 0, 0 );
368 gtk_tree_path_free( tp );
369 }
370 }
371 else
372 gtk_list_store_remove( list, &it );
373 }
374 }
375 g_free( file );
376 }
377 g_dir_close( dir );
378 }
379 }
380
381 static void load_from_data_dirs( GtkListStore* list,
382 const char* relative_path,
383 const char* lookup,
384 GtkTreeSelection* sel,
385 const char* init_sel,
386 ThemeFunc theme_func )
387 {
388 const char* const *dirs = g_get_system_data_dirs();
389 const char* const *dir;
390 char* dir_path;
391 for( dir = dirs; *dir; ++dir )
392 {
393 dir_path = g_build_filename( *dir, relative_path, NULL );
394 load_themes_from_dir( list, dir_path, lookup, sel, init_sel, theme_func );
395 g_free( dir_path );
396 }
397 dir_path = g_build_filename( g_get_user_data_dir(), relative_path, NULL );
398 load_themes_from_dir( list, dir_path, lookup, sel, init_sel, theme_func );
399 g_free( dir_path );
400 }
401
402 static gboolean icon_theme_func(const char* file, const char* dir, const char* name, GtkListStore* list, GtkTreeIter* it)
403 {
404 GKeyFile* kf;
405 char* disp_name = NULL;
406 if( g_str_has_prefix( name, "default." ) )
407 return FALSE;
408
409 kf = g_key_file_new();
410 if( g_key_file_load_from_file(kf, file, 0, NULL) )
411 {
412 if( g_key_file_has_key(kf, "Icon Theme", "Directories", NULL)
413 && ! g_key_file_get_boolean(kf, "Icon Theme", "Hidden", NULL) )
414 {
415 disp_name = g_key_file_get_locale_string(kf, "Icon Theme", "Name", NULL, NULL);
416 gtk_list_store_set( list, it, COL_DISP_NAME, disp_name ? disp_name : name, -1 );
417 }
418 }
419 g_key_file_free(kf);
420 return disp_name != NULL;
421 }
422
423 static gboolean cursor_theme_func(const char* dir, const char* name, const char* lookup)
424 {
425 char* ret = NULL;
426 /*
427 GKeyFile* kf = g_key_file_new();
428 if( g_key_file_load_from_file(kf, lookup, 0, NULL) )
429 {
430 if( g_key_file_has_key(kf, "Icon Theme", "Directories", NULL) )
431 {
432 ret = g_key_file_get_locale_string(kf, "Icon Theme", "Name", NULL, NULL);
433 }
434 }
435 g_key_file_free(kf);
436 */
437 return ret != NULL;
438 }
439
440 static void load_gtk_themes( GtkListStore* list, const char* cur_sel )
441 {
442 char* path;
443 GtkTreeSelection* sel = gtk_tree_view_get_selection( gtk_theme_view );
444 load_from_data_dirs( list, "themes", "gtk-2.0", sel, cur_sel, NULL );
445 path = g_build_filename( g_get_home_dir(), ".themes", NULL );
446 load_themes_from_dir( list, path, "gtk-2.0", sel, cur_sel, NULL );
447 g_free( path );
448 gtk_tree_sortable_set_sort_column_id( (GtkTreeSortable*)list, 0, GTK_SORT_ASCENDING );
449 }
450
451 static void load_icon_themes( GtkListStore* list, const char* cur_sel )
452 {
453 char* path;
454 GtkTreeSelection* sel = gtk_tree_view_get_selection( icon_theme_view );
455 load_from_data_dirs( list, "icons", "index.theme", sel, cur_sel, icon_theme_func );
456 path = g_build_filename( g_get_home_dir(), ".icons", NULL );
457 load_themes_from_dir( list, path, "index.theme", sel, cur_sel, icon_theme_func );
458 g_free( path );
459 gtk_tree_sortable_set_sort_column_id( (GtkTreeSortable*)list, 0, GTK_SORT_ASCENDING );
460 }
461
462 /*
463 static void load_fonts( GtkListStore* list )
464 {
465
466 }
467 */
468
469 gboolean center_win( GtkWidget* dlg )
470 {
471 gtk_widget_show( dlg );
472 return FALSE;
473 }
474
475 static void on_demo_loaded( GtkSocket* socket, GtkWidget* dlg )
476 {
477 /* sleep for 0.8 sec for loading of the demo window */
478 /* FIXME: we need a better way to do this, such as IPC */
479 g_timeout_add_full( G_PRIORITY_LOW, 800, (GSourceFunc)center_win, dlg, NULL );
480 g_signal_handlers_disconnect_by_func( socket, on_demo_loaded, dlg );
481 }
482
483 void main_dlg_init( GtkWidget* dlg )
484 {
485 char* files[] = { tmp_rc_file, NULL };
486 char** def_files = gtk_rc_get_default_files();
487 char** file;
488
489 /* no lxde-settings daemon, use gtkrc-2.0 */
490 if( ! under_lxde )
491 {
492 for( file = def_files; *file; ++file )
493 {
494 if( 0 == access( *file, W_OK ) )
495 rc_file = *file;
496 }
497 if( rc_file )
498 rc_file = g_strdup( rc_file );
499 else
500 rc_file = g_build_filename( g_get_home_dir(), ".gtkrc-2.0", NULL );
501 }
502
503 g_object_get( gtk_settings_get_default(),
504 "gtk-theme-name", &gtk_theme_name,
505 "gtk-icon-theme-name", &icon_theme_name,
506 "gtk-font-name", &font_name,
507 "gtk-toolbar-style", &tb_style,
508 NULL );
509
510 if( ! gtk_theme_name )
511 gtk_theme_name = g_strdup( "Raleigh" );
512 if( ! icon_theme_name )
513 gtk_theme_name = g_strdup( "hicolor" );
514 if( ! font_name )
515 font_name = g_strdup( "Sans 10" );
516
517 /* no lxde-settings daemon, use gtkrc-2.0 */
518 if( ! under_lxde )
519 write_rc_file( tmp_rc_file );
520
521 INIT_LIST( gtk_theme, "gtk-theme-name" )
522 INIT_LIST( icon_theme, "gtk-icon-theme-name" )
523 gtk_font_button_set_font_name( (GtkFontButton*)lookup_widget(dlg, "font"), font_name );
524
525 gtk_combo_box_set_active( (GtkComboBox*)lookup_widget(dlg, "tb_style"), tb_style < 4 ? tb_style : 3 );
526
527 GET_WIDGET_WITH_TYPE( demo_box, GTK_WIDGET );
528 gtk_widget_show( demo_box );
529
530 if( under_lxde )
531 {
532 /* XSettings daemon of LXDE is running, gtkrc is useless.
533 * We should set properties of GtkSettings object on the fly.
534 * This will cause problems with some themes, but we have no choice.
535 */
536 show_demo( (GdkNativeWindow)demo_box );
537 gtk_widget_show_all( dlg );
538 }
539 else
540 {
541 /* no lxde-settings daemon, use gtkrc-2.0 and load preview in another process */
542 demo_socket = gtk_socket_new();
543 g_signal_connect( demo_socket, "plug-added", G_CALLBACK(on_demo_loaded), dlg );
544 g_signal_connect( demo_socket, "plug-removed", G_CALLBACK(gtk_true), NULL );
545 gtk_widget_show( demo_socket );
546 gtk_container_add( (GtkContainer*)demo_box, demo_socket );
547
548 gtk_widget_realize( dlg );
549 reload_demo_process();
550 }
551
552 disable_apply();
553 }
554
555 static void reload_all_programs( gboolean icon_only )
556 {
557 GdkEventClient event;
558 event.type = GDK_CLIENT_EVENT;
559 event.send_event = TRUE;
560 event.window = NULL;
561
562 if( under_lxde )
563 {
564 event.message_type = gdk_atom_intern("LXDE_SETTINGS", FALSE);
565 event.data.b[0] = 0; /* LXS_RELOAD */
566 }
567 else
568 {
569 if( icon_only )
570 event.message_type = gdk_atom_intern("_GTK_LOAD_ICONTHEMES", FALSE);
571 else
572 event.message_type = gdk_atom_intern("_GTK_READ_RCFILES", FALSE);
573 }
574 event.data_format = 8;
575 gdk_event_send_clientmessage_toall((GdkEvent *)&event);
576 }
577
578 void
579 on_apply_clicked (GtkButton *button,
580 gpointer user_data)
581 {
582 if( under_lxde )
583 write_lxde_config();
584 else
585 write_rc_file( rc_file );
586
587 reload_all_programs( FALSE );
588 disable_apply();
589 }
590
591
592 void
593 on_font_changed (GtkFontButton *fontbutton,
594 gpointer user_data)
595 {
596 const char* name = gtk_font_button_get_font_name(fontbutton);
597 if( name && font_name && 0 == strcmp( name, font_name ) )
598 return;
599 g_free( font_name );
600 font_name = g_strdup( name );
601
602 if( under_lxde )
603 {
604 g_object_set( gtk_settings_get_default(), "gtk-font-name", font_name, NULL );
605
606 enable_apply();
607 }
608 else
609 {
610 write_rc_file( tmp_rc_file );
611 reload_demo_process();
612 }
613 }
614
615 void
616 on_install_theme_clicked (GtkButton *button,
617 gpointer user_data)
618 {
619 GtkFileFilter* filter = gtk_file_filter_new();
620 GtkWidget* fc = gtk_file_chooser_dialog_new( _("Select an icon theme"), NULL,
621 GTK_FILE_CHOOSER_ACTION_OPEN,
622 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
623 GTK_STOCK_OPEN, GTK_RESPONSE_OK, NULL );
624
625 gtk_file_filter_add_pattern( filter, "*.tar.gz" );
626 gtk_file_filter_add_pattern( filter, "*.tar.bz2" );
627 gtk_file_filter_set_name( filter, _("*.tar.gz, *.tar.bz2 (Icon Theme)") );
628
629 gtk_file_chooser_add_filter( GTK_FILE_CHOOSER(fc), filter );
630 gtk_file_chooser_set_filter( GTK_FILE_CHOOSER(fc), filter );
631
632 if( gtk_dialog_run( (GtkDialog*)fc ) == GTK_RESPONSE_OK )
633 {
634 char* file = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER(fc) );
635 char* argv[]={
636 PACKAGE_DATA_DIR"/lxappearance/install-icon-theme.sh",
637 file, NULL };
638 int status = 0;
639 char* stdo = NULL;
640 if( g_spawn_sync( NULL, argv, NULL, 0, NULL, NULL, &stdo, NULL, &status, NULL ) && 0 == status )
641 {
642 char* sep = stdo ? strchr( stdo, '\n' ) : NULL;
643 if( sep )
644 *sep = '\0';
645
646 /* reload all icon themes */
647 gtk_list_store_clear( icon_theme_list );
648 load_icon_themes( icon_theme_list, stdo ? stdo : "" );
649 }
650 g_free( file );
651 }
652
653 gtk_widget_destroy( fc );
654 }
655
656
657 void
658 on_remove_theme_clicked (GtkButton *button,
659 gpointer user_data)
660 {
661
662 }
663
664
665 void
666 on_tb_style_changed (GtkComboBox *combobox,
667 gpointer user_data)
668 {
669 int sel = gtk_combo_box_get_active( combobox );
670 if( sel == tb_style || sel < 0 )
671 return;
672 tb_style = sel;
673
674 if( under_lxde )
675 {
676 g_object_set( gtk_settings_get_default(), "gtk-toolbar-style", tb_style, NULL );
677 enable_apply();
678 }
679 else
680 {
681 write_rc_file( tmp_rc_file );
682 reload_demo_process();
683 }
684 }
685