Support removing installed themes.
authorHong Jen Yee (PCMan) <pcman.tw@gmail.com>
Wed, 4 Aug 2010 08:05:07 +0000 (16:05 +0800)
committerHong Jen Yee (PCMan) <pcman.tw@gmail.com>
Wed, 4 Aug 2010 08:05:07 +0000 (16:05 +0800)
data/ui/lxappearance.glade
src/cursor-theme.c
src/icon-theme.c
src/icon-theme.h
src/lxappearance2.h
src/utils.c
src/utils.h

index f1a124d..31e36a9 100644 (file)
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkButton" id="install_icon_theme1">
+                      <object class="GtkButton" id="install_cursor_theme">
                         <property name="label" translatable="yes">Install</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
                       </packing>
                     </child>
                     <child>
-                      <object class="GtkButton" id="remove_icon_theme1">
+                      <object class="GtkButton" id="remove_cursor_theme">
                         <property name="label" translatable="yes">Remove</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
               </packing>
             </child>
             <child>
-              <object class="GtkButton" id="close">
-                <property name="label">gtk-close</property>
+              <object class="GtkButton" id="apply">
+                <property name="label">gtk-apply</property>
                 <property name="visible">True</property>
+                <property name="sensitive">False</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
                 <property name="use_stock">True</property>
               </packing>
             </child>
             <child>
-              <object class="GtkButton" id="apply">
-                <property name="label">gtk-apply</property>
+              <object class="GtkButton" id="close">
+                <property name="label">gtk-close</property>
                 <property name="visible">True</property>
-                <property name="sensitive">False</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
                 <property name="use_stock">True</property>
     </child>
     <action-widgets>
       <action-widget response="1">about</action-widget>
-      <action-widget response="-7">close</action-widget>
       <action-widget response="-10">apply</action-widget>
+      <action-widget response="-7">close</action-widget>
     </action-widgets>
   </object>
   <object class="GtkImage" id="image2">
index 8a44fb2..16f7d55 100644 (file)
@@ -82,6 +82,8 @@ static void on_cursor_theme_sel_changed(GtkTreeSelection* tree_sel, gpointer use
             update_cursor_demo();
             lxappearance_changed();
         }
+
+        gtk_widget_set_sensitive(app.cursor_theme_remove_btn, theme->is_removable);
     }
 }
 
@@ -119,4 +121,7 @@ void cursor_theme_init(GtkBuilder* b)
     app.cursor_demo_view = GTK_WIDGET(gtk_builder_get_object(b, "cursor_demo_view"));
     gtk_icon_view_set_pixbuf_column(app.cursor_demo_view, 0);
     update_cursor_demo();
+
+    /* install and remove */
+    /* this part is already done in icon-theme.c */
 }
index 2faa7f8..7625d43 100644 (file)
 #include "icon-theme.h"
 #include "lxappearance2.h"
 #include <string.h>
+#include <unistd.h>
 #include "utils.h"
 
+char** icon_theme_dirs = NULL;
+
 gint icon_theme_cmp_name(IconTheme* t, const char* name)
 {
     return strcmp(t->name, name);
@@ -43,8 +46,20 @@ static void icon_theme_free(IconTheme* theme)
     g_slice_free(IconTheme, theme);
 }
 
-void load_icon_themes_from_dir(const char* theme_dir, GKeyFile* kf)
+void load_icon_themes_from_dir(const char* base_dir, const char* theme_dir, GKeyFile* kf)
 {
+    /* NOTE:
+     * 1. theoratically, base_dir is identical to theme_dir
+     * the only case that they are different is when we try to install
+     * a new theme. icon themes in a temporary theme dir may later be
+     * installed to base_dir, and we load it from temp. theme dir
+     * before installation. So theme_dir is the temporary dir containing
+     * the themes to install. base_dir is the destination directory to be
+     * installed to.
+     * 2. base_dir is actually a component of global variable icon_theme_dirs.
+     * so it's safe to use this string without strdup() since its life
+     * span is the same as the whole program and won't be freed. */
+
     GDir* dir = g_dir_open(theme_dir, 0, NULL);
     if(dir)
     {
@@ -63,6 +78,9 @@ void load_icon_themes_from_dir(const char* theme_dir, GKeyFile* kf)
 
                 theme->name = g_strdup(name);
                 index_theme = g_build_filename(theme_dir, name, "index.theme", NULL);
+                theme->base_dir = base_dir;
+                theme->is_removable = (0 == access(base_dir, W_OK));
+
                 if(g_key_file_load_from_file(kf, index_theme, 0, NULL))
                 {
                     /* skip hidden ones */
@@ -99,24 +117,13 @@ void load_icon_themes_from_dir(const char* theme_dir, GKeyFile* kf)
 
 static void load_icon_themes()
 {
-    const char* const *dirs = g_get_system_data_dirs();
-    const char* const *pdir;
-    char* dir_path;
+    int n, i;
+    gtk_icon_theme_get_search_path(gtk_icon_theme_get_default(), &icon_theme_dirs, &n);
     GKeyFile* kf = g_key_file_new();
-
-    dir_path = g_build_filename(g_get_home_dir(), ".icons", NULL);
-    load_icon_themes_from_dir(dir_path, kf);
-    g_free(dir_path);
-
-    dir_path = g_build_filename(g_get_user_data_dir(), "icons", NULL);
-    load_icon_themes_from_dir(dir_path, kf);
-    g_free(dir_path);
-
-    for(pdir = dirs; *pdir; ++pdir)
+    for(i = 0; i < n; ++i)
     {
-        dir_path = g_build_filename(*pdir, "icons", NULL);
-        load_icon_themes_from_dir(dir_path, kf);
-        g_free(dir_path);
+        load_icon_themes_from_dir(icon_theme_dirs[i], icon_theme_dirs[i], kf);
+        g_debug("icon_theme_dirs[%d] = %s", i, icon_theme_dirs[i]);
     }
     g_key_file_free(kf);
 }
@@ -152,6 +159,8 @@ static void on_icon_theme_sel_changed(GtkTreeSelection* tree_sel, gpointer user_
 
             lxappearance_changed();
         }
+
+        gtk_widget_set_sensitive(app.icon_theme_remove_btn, theme->is_removable);
     }
 }
 
@@ -160,6 +169,67 @@ static void on_install_theme_clicked(GtkButton* btn, gpointer user_data)
     install_icon_theme(gtk_widget_get_toplevel(btn));
 }
 
+static void on_remove_theme_clicked(GtkButton* btn, gpointer user_data)
+{
+    char* theme_name;
+    GtkTreeSelection* sel;
+    GtkTreeModel* model;
+    GtkTreeIter it;
+
+    if(btn == app.icon_theme_remove_btn) /* remove icon theme */
+        sel = gtk_tree_view_get_selection(app.icon_theme_view);
+    else if(btn == app.cursor_theme_remove_btn) /* remove cursor theme */
+        sel = gtk_tree_view_get_selection(app.cursor_theme_view);
+    else
+        return;
+
+    if(gtk_tree_selection_get_selected(sel, &model, &it))
+    {
+        IconTheme* theme;
+        gboolean both = theme->has_icon && theme->has_cursor;
+
+        if(gtk_tree_model_iter_n_children(model, NULL) < 2)
+        {
+            /* FIXME: the user shouldn't remove the last available theme.
+             * another list needs to be checked, too. */
+            return;
+        }
+
+        gtk_tree_model_get(model, &it, 1, &theme, -1);
+        if(remove_icon_theme(gtk_widget_get_toplevel(btn), theme))
+        {
+            gtk_list_store_remove(GTK_LIST_STORE(model), &it);
+
+            /* select the first theme */
+            gtk_tree_model_get_iter_first(model, &it);
+            gtk_tree_selection_select_iter(sel, &it);
+
+            /* FIXME: in rare case, a theme can contain both icons and cursors */
+            if(both) /* we need to remove item in another list store, too */
+            {
+                model = (model == app.icon_theme_store ? app.cursor_theme_store : app.icon_theme_store);
+                /* find the item in another store */
+                if(gtk_tree_model_get_iter_first(model, &it))
+                {
+                    IconTheme* theme2;
+                    do
+                    {
+                        gtk_tree_model_get(model, &it, 1, &theme2, -1);
+                        if(theme2 == theme)
+                        {
+                            gtk_list_store_remove(GTK_LIST_STORE(model), &it);
+                            /* select the first theme */
+                            gtk_tree_model_get_iter_first(model, &it);
+                            gtk_tree_selection_select_iter(sel, &it);
+                            break;
+                        }
+                    }while(gtk_tree_model_iter_next(model, &it));
+                }
+            }
+        }
+    }
+}
+
 void icon_theme_init(GtkBuilder* b)
 {
     GSList* l;
@@ -167,13 +237,28 @@ void icon_theme_init(GtkBuilder* b)
     GtkTreeIter icon_theme_sel_it = {0};
     GtkTreeIter cursor_theme_sel_it = {0};
     GtkTreeSelection* sel;
+    GtkWidget* btn;
 
     app.icon_theme_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
     app.cursor_theme_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
     app.icon_theme_view = GTK_WIDGET(gtk_builder_get_object(b, "icon_theme_view"));
     app.cursor_theme_view = GTK_WIDGET(gtk_builder_get_object(b, "cursor_theme_view"));
 
-    /* load icon themes */
+    /* install icon and cursor theme */
+    btn = GTK_WIDGET(gtk_builder_get_object(b, "install_icon_theme"));
+    g_signal_connect(btn, "clicked", G_CALLBACK(on_install_theme_clicked), NULL);
+
+    btn = GTK_WIDGET(gtk_builder_get_object(b, "install_cursor_theme"));
+    g_signal_connect(btn, "clicked", G_CALLBACK(on_install_theme_clicked), NULL);
+
+    /* remove icon theme */
+    app.icon_theme_remove_btn = GTK_WIDGET(gtk_builder_get_object(b, "remove_icon_theme"));
+    g_signal_connect(app.icon_theme_remove_btn, "clicked", G_CALLBACK(on_remove_theme_clicked), NULL);
+
+    app.cursor_theme_remove_btn = GTK_WIDGET(gtk_builder_get_object(b, "remove_cursor_theme"));
+    g_signal_connect(app.cursor_theme_remove_btn, "clicked", G_CALLBACK(on_remove_theme_clicked), NULL);
+
+    /* load icon and cursor themes */
     load_icon_themes();
 
     for(l = app.icon_themes; l; l=l->next)
@@ -206,10 +291,14 @@ void icon_theme_init(GtkBuilder* b)
     sel = gtk_tree_view_get_selection(app.icon_theme_view);
     if(icon_theme_sel_it.user_data)
     {
+        IconTheme* theme;
         GtkTreePath* tp = gtk_tree_model_get_path(GTK_TREE_MODEL(app.icon_theme_store), &icon_theme_sel_it);
         gtk_tree_selection_select_iter(sel, &icon_theme_sel_it);
         gtk_tree_view_scroll_to_cell(app.icon_theme_view, tp, NULL, FALSE, 0, 0);
         gtk_tree_path_free(tp);
+
+        gtk_tree_model_get(app.icon_theme_store, &icon_theme_sel_it, 1, &theme, -1);
+        gtk_widget_set_sensitive(app.icon_theme_remove_btn, theme->is_removable);
     }
     g_signal_connect(sel, "changed", G_CALLBACK(on_icon_theme_sel_changed), NULL);
 
@@ -217,15 +306,17 @@ void icon_theme_init(GtkBuilder* b)
     sel = gtk_tree_view_get_selection(app.cursor_theme_view);
     if(cursor_theme_sel_it.user_data)
     {
+        IconTheme* theme;
         GtkTreePath* tp = gtk_tree_model_get_path(GTK_TREE_MODEL(app.cursor_theme_store), &cursor_theme_sel_it);
         gtk_tree_selection_select_iter(sel, &cursor_theme_sel_it);
         gtk_tree_view_scroll_to_cell(app.cursor_theme_view, tp, NULL, FALSE, 0, 0);
         gtk_tree_path_free(tp);
+
+        gtk_tree_model_get(app.cursor_theme_store, &cursor_theme_sel_it, 1, &theme, -1);
+        gtk_widget_set_sensitive(app.cursor_theme_remove_btn, theme->is_removable);
     }
 
     /* load "gtk-icon-sizes" */
-    icon_sizes_init(b);
+    /* icon_sizes_init(b); */
 
-    GtkWidget* btn = gtk_builder_get_object(b, "install_icon_theme");
-    g_signal_connect(btn, "clicked", G_CALLBACK(on_install_theme_clicked), NULL);
 }
index b454b7b..46ab1d7 100644 (file)
 
 G_BEGIN_DECLS
 
+extern char** icon_theme_dirs;
+
 typedef struct
 {
     char* name;
     char* disp_name;
     char* comment;
+    const char* base_dir;
     gboolean has_icon : 1;
     gboolean has_cursor : 1;
+    gboolean is_removable : 1;
 }IconTheme;
 
 void icon_theme_init(GtkBuilder* b);
-void load_icon_themes_from_dir(const char* theme_dir, GKeyFile* kf);
+void load_icon_themes_from_dir(const char* base_dir, const char* theme_dir, GKeyFile* kf);
 
 gint icon_theme_cmp_name(IconTheme* t, const char* name);
 gint icon_theme_cmp_disp_name(IconTheme* t1, IconTheme* t2);
index d9e9373..2a32a3e 100644 (file)
@@ -36,11 +36,13 @@ struct _LXAppearance
 
     GtkWidget* icon_theme_view;
     GtkListStore* icon_theme_store;
+    GtkWidget* icon_theme_remove_btn;
 
     GtkWidget* cursor_theme_view;
     GtkWidget* cursor_demo_view;
     GtkListStore* cursor_theme_store;
     GtkWidget* cursor_size_range;
+    GtkWidget* cursor_theme_remove_btn;
 
     GSList* icon_themes; /* a list of IconTheme struct */
 
index 7701769..90b13e1 100644 (file)
@@ -112,7 +112,7 @@ static void insert_theme_to_models(IconTheme* theme)
 static gboolean install_icon_theme_package(const char* package_path)
 {
     GPid pid = -1;
-    char* user_icons_dir = g_build_filename(g_get_home_dir(), ".icons", NULL);
+    const char* user_icons_dir = icon_theme_dirs[0];
     char* tmp_dir = g_build_filename(user_icons_dir, "tmp.XXXXXX", NULL);
     char* argv[]= {
         "tar",
@@ -152,7 +152,7 @@ static gboolean install_icon_theme_package(const char* package_path)
             GKeyFile* kf = g_key_file_new();
 
             /* convert the themes in the dir to IconTheme structs and add them to app.icon_themes list */
-            load_icon_themes_from_dir(tmp_dir, kf);
+            load_icon_themes_from_dir(user_icons_dir, tmp_dir, kf);
             g_key_file_free(kf);
 
             /* now really move this themes to ~/.icons dir and also update the GUI */
@@ -201,7 +201,6 @@ static gboolean install_icon_theme_package(const char* package_path)
 
 _out:
     g_free(tmp_dir);
-    g_free(user_icons_dir);
     return (pid != -1);
 }
 
@@ -233,3 +232,36 @@ gboolean install_icon_theme(GtkWindow* parent)
     return TRUE;
 }
 
+gboolean remove_icon_theme(GtkWindow* parent, IconTheme* theme)
+{
+    gboolean ret = TRUE;
+    char* dir = g_build_filename(theme->base_dir, theme->name, NULL);
+    char* tmp_dir = g_build_filename(theme->base_dir, "tmp.XXXXXX", NULL);
+g_debug("tmp_dir = %s", tmp_dir);
+    /* move the theme to a tmp dir first. so we can make the
+     * removal atomic. */
+    if(mkdtemp(tmp_dir))
+    {
+        char* tmp_dest = g_build_filename(tmp_dir, theme->name, NULL);
+        if(g_rename(dir, tmp_dest) == 0)
+        {
+            char* argv[] = {
+                "rm",
+                "-rf",
+                tmp_dir,
+                NULL
+            };
+            GPid pid;
+            if(g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL))
+            {
+                ret = show_progress_for_pid(app.dlg, "Remove icon theme", "Removing...", pid);
+            }
+        }
+        g_free(tmp_dest);
+    }
+    else
+        ret = FALSE;
+
+    g_free(dir);
+    return ret;
+}
index 49136db..d008025 100644 (file)
 #define _UTILS_H_
 
 #include <gtk/gtk.h>
+#include "icon-theme.h"
 
 G_BEGIN_DECLS
 
 gboolean show_progress_for_pid(GtkWindow* parent, const char* title, const char* msg, GPid pid);
 
 gboolean install_icon_theme(GtkWindow* parent);
+gboolean remove_icon_theme(GtkWindow* parent, IconTheme* theme);
 
 G_END_DECLS