/*
 *  themebrowser.c:	Theme browser
 *
 *  Written by:		Ullrich Hafner
 *  
 *  Copyright (C) 2000 Ullrich Hafner <hafner@bigfoot.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 */

/*
 *  $Date: 2001/03/16 16:52:28 $
 *  $Author: hafner $
 *  $Revision: 1.4 $
 *  $State: Exp $
 */

#include "config.h"

#include <gtk/gtk.h>
#include "proplist_t.h"

#ifdef HAVE_UNISTD_H
#	include <unistd.h>
#endif /* HAVE_UNISTD_H */
/* unistd.h defines _POSIX_VERSION on POSIX.1 systems. */
#if defined(HAVE_DIRENT_H) || defined(_POSIX_VERSION)
#   include <dirent.h>
#   define NLENGTH(dirent) (strlen ((dirent)->d_name))
#else
#   define dirent direct
#   define NLENGTH(dirent) ((dirent)->d_namlen)
#   ifdef HAVE_SYS_NDIR_H
#       include <sys/ndir.h>
#   endif /* HAVE_SYS_NDIR_H */
#   ifdef HAVE_SYS_DIR_H
#       include <sys/dir.h>
#   endif /* HAVE_SYS_DIR_H */
#   ifdef HAVE_NDIR_H
#       include <ndir.h>
#   endif /* HAVE_NDIR_H */
#endif /* not (HAVE_DIRENT_H or _POSIX_VERSION) */
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <ctype.h>

#include "themebrowser.h"
#include "dndtree.h"
#include "rimage.h"
#include "previews.h"
#include "window.h"
#include "path.h"
#include "icons.h"
#include "dialog.h"
#include "misc.h"
#include "error.h"

/*****************************************************************************

			      global variables
  
*****************************************************************************/

extern proplist_t  windowmaker;
extern proplist_t  wmconfig;
extern GtkTooltips *tooltips;
extern bool_t	    changed;
extern GtkWidget   *main_window;

typedef struct themeinfo
{
   bool_t 	 istheme;
   GtkCTreeNode *parent;
   char         *fullname;
   char         *name;
   char 	*suffix;
} themeinfo_t;

typedef struct themerename
{
   char *oldname;
   char *newname;
} themerename_t;

GtkCTree *tree = NULL;

static GtkWidget *workspaceback        = NULL;
static GtkWidget *titlebarback         = NULL;
static GtkWidget *unfocus_titlebarback = NULL;
static GtkWidget *parent_titlebarback  = NULL;
static GtkWidget *menutitleback        = NULL;
static GtkWidget *menuitemback 	       = NULL;
static GtkWidget *iconback             = NULL;

static proplist_t theme_keys 	   = NULL;
static bool_t	   previews_active = NO;
static GtkWidget  *preview_update_menu = NULL;

/*****************************************************************************

			         prototypes
  
*****************************************************************************/

static void
update_themes_timeout (char *filename);
static void
toggle_selection_mode (GtkWidget *widget, gpointer ptr);
static void
update_selection (GtkCList *themelist, gint row, gint column, GdkEvent *event,
		  gpointer data);
static void
insert_dnd_elements (GtkCList *clist, const char *start);
static void  
drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
		    GtkSelectionData *data, guint info, guint time);
static void
init_dnd (GtkWidget *widget);
static gint
insert_theme_files (GtkWidget *widget, gpointer ptr);
static void
cancel_installation (GtkWidget *widget, gpointer ptr);
static void
install_themes (GtkWidget *widget, gpointer ptr);
static void
open_theme_browser (GtkWidget *button, gpointer data);
static void
install_theme_dialog (GtkWidget *widget, gpointer ptr);
static void
remove_theme_dialog (GtkWidget *button, gpointer ptr);
static void
remove_theme (GtkWidget *widget, gpointer ptr);
static void
remove_file (gpointer data, gpointer user_data);
static proplist_t
get_color (proplist_t texture);
static void
compute_preview (GtkWidget *progress_bar, GtkWidget *progress_label,
		 unsigned n, unsigned nelem,
		 const char *name, unsigned width, unsigned height);
static void
free_memory (gpointer data, gpointer user_data);
static void
preview_dialog (GtkWidget *button, gpointer ptr);
static void
leave_preview (GtkWidget *button, gpointer ptr);
static void
set_theme (GtkWidget *button, gpointer ptr);
static void
load_theme (GtkWidget *button, gpointer ptr);
static bool_t
getstyle_call (const char *themename);
static GList *
remove_file_from_list (GList *list, const char *file);
static GList *
get_theme_files (const char *themename);
static void
save_theme_frontend (GtkWidget *widget, gpointer ptr);
static void
save_theme_backend (GtkWidget *widget, gpointer ptr);
static void
activate_themename (GtkWidget *widget, gpointer ptr);
static void
set_themename (GtkWidget *widget, gpointer ptr);
static gint
hide_window (GtkWidget *widget, gpointer ptr);
static void
save_theme_dialog (GtkWidget *widget, gpointer ptr);
static GtkWidget *
theme_preview (void);
static char *
theme_image_filename (const char *name, const char *attribute,
		      bool_t combined);
static void
theme_gradient (const char *name, const char *attribute, GtkWidget *preview);
static void
select_theme (GtkCTree *tree, const char *text);
static gint
compare_themename (gconstpointer a, gconstpointer b);
static void
new_directory_dialog (GtkWidget *widget, gpointer ptr);
static void
new_directory_backend (GtkWidget *widget, gpointer ptr);
static void
select_directory_dialog (GtkWidget *button, gpointer data);
static void
info_destructor (gpointer data);
static void
set_node_text (GtkCTree *tree, GtkCTreeNode *node, const char *text);
static void
activate_button (GtkWidget *widget, gpointer ptr);
static void
rename_theme (GtkWidget *widget, gpointer ptr);
static void
rename_theme_backend (themerename_t *renameinfo);
static void
rename_theme_delete (GtkWidget *widget, gpointer ptr);
GtkCTreeNode *
get_selected_node (GtkCTree *tree);
static void
rename_theme_dialog (GtkWidget *widget, gpointer ptr);
static gint
context_menu (GtkCTree *ctree, GdkEventButton *event, gpointer ptr);
static void
selection_made (GtkCTree *ctree, GtkCTreeNode *node, gint column,
		gpointer data);
static GtkCTreeNode *
append_directory (const char *dirname, GtkCTree *tree,
		  GtkCTreeNode *parent, GtkCTreeNode *sibling);
static GtkCTreeNode *
append_node (char *dirname, GtkCTree *tree,
	     GtkCTreeNode *parent, GtkCTreeNode *sibling, bool_t theme);
static gboolean
node_drag_compare (GtkCTree *ctree, GtkCTreeNode *source_node,
		   GtkCTreeNode *new_parent, GtkCTreeNode *new_sibling);
static void
tree_moved (GtkCTree *ctree, GtkCTreeNode *node, GtkCTreeNode *new_parent, 
	    GtkCTreeNode *new_sibling);
static void
expand_recursive (GtkWidget *widget, gpointer ptr);
static void
collapse_recursive (GtkWidget *widget, gpointer ptr);

/*****************************************************************************

			        public code
  
*****************************************************************************/

void
generate_theme_menu (GtkMenu *themes_menu)
{
   GtkWidget *item;

   item = gtk_menu_item_new_with_label (_("Install..."));
   gtk_menu_append (themes_menu, item);
   gtk_signal_connect (GTK_OBJECT (item), "activate",
		       GTK_SIGNAL_FUNC (install_theme_dialog), NULL);
   
#ifdef GETSTYLE
   item = gtk_menu_item_new_with_label (_("Save..."));
   gtk_menu_append (themes_menu, item);
   gtk_signal_connect (GTK_OBJECT(item), "activate",
		       GTK_SIGNAL_FUNC (save_theme_dialog), NULL);
#endif /* GETSTYLE */
   
   item = gtk_menu_item_new_with_label (_("Directory list..."));
   gtk_menu_append (themes_menu, item);
   gtk_widget_show (item);
   gtk_signal_connect (GTK_OBJECT(item), "activate",
		       GTK_SIGNAL_FUNC (select_directory_dialog), NULL);
   
   item = gtk_menu_item_new_with_label (_("Update previews"));
   gtk_menu_append (themes_menu, item);
   gtk_signal_connect (GTK_OBJECT (item), "activate",
		       GTK_SIGNAL_FUNC (generate_previews), NULL);
}

GtkWidget *
theme_browser (GtkWidget *box, bool_t section)
/*
 *  Generate theme dialog window: scrolled theme list, theme path list,
 *  and action button box
 */
{
   GtkWidget *scrolled;
   GtkWidget *hbox;
   GtkWidget *vbox;
   
   /*
    *  Scrolled window for ctree
    */
   scrolled = gtk_scrolled_window_new (NULL, NULL);
   gtk_container_set_border_width (GTK_CONTAINER (scrolled), 5);
   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
				   GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
   /*
    *  Ctree to show the menu structure
    */
   tree = gtk_dndtree_new (1, 0);
   gtk_object_set_user_data (GTK_OBJECT (tree), tree);
   gtk_clist_set_row_height (GTK_CLIST (tree), 18);
   
   gtk_clist_set_selection_mode (GTK_CLIST (tree), GTK_SELECTION_BROWSE);
   gtk_clist_set_column_auto_resize (GTK_CLIST (tree), 0, TRUE);
   gtk_ctree_set_line_style (GTK_CTREE (tree), GTK_CTREE_LINES_DOTTED);
   gtk_ctree_set_drag_compare_func (GTK_CTREE (tree), node_drag_compare);
   gtk_signal_connect (GTK_OBJECT (tree), "tree_move",
		       GTK_SIGNAL_FUNC (tree_moved), NULL);
   
   gtk_container_add (GTK_CONTAINER (scrolled), GTK_WIDGET (tree)); 

   build_theme_list ();
   
   gtk_signal_connect (GTK_OBJECT(tree), "tree_select_row",
		       GTK_SIGNAL_FUNC (selection_made), NULL);
   gtk_signal_connect (GTK_OBJECT (tree), "button_press_event",
		       GTK_SIGNAL_FUNC (context_menu), NULL);
   
   /*
    *  Horizontal container (tree, theme info)
    */
   hbox = gtk_hbox_new (FALSE, 0);

   gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
   
   vbox = gtk_vbox_new (FALSE, 0);
   
   gtk_box_pack_start (GTK_BOX (vbox), scrolled, TRUE, TRUE, 0);

   {
      GtkWidget *button;

      button = gtk_check_button_new_with_label (_("Single selection with "
						  "drag and drop"));
      gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
      gtk_signal_connect (GTK_OBJECT (button), "toggled",
			  GTK_SIGNAL_FUNC (toggle_selection_mode), tree);
      gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
   }
   
   gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);

   vbox = gtk_vbox_new (TRUE, 0);
   gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, TRUE, 0);
   gtk_box_pack_start (GTK_BOX (vbox), theme_preview (), FALSE, FALSE, 0);

   gtk_box_pack_start (GTK_BOX (box), hbox, TRUE, TRUE, 0);

   gtk_widget_show_all (hbox);
   
   return hbox;
}

void
build_theme_list (void)
/*
 *  Build theme list by scanning the directories of ThemePath.
 *
 *  No return value.
 *
 *  Side effects:
 *	Old tree is discarded and new tree elements are added.
 */
{
   proplist_t   pltheme = WMCreatePLString ("ThemePath");
   proplist_t   dirlist = WMGetFromPLDictionary (windowmaker, pltheme);
   GtkCTreeNode *sibling = NULL;
   unsigned 	 n;
      
   WMReleasePropList (pltheme);

   gtk_clist_freeze (GTK_CLIST (tree));
   gtk_clist_clear (GTK_CLIST (tree));
   
   for (n = 0; n < WMGetPropListItemCount (dirlist); n++)
   {
      proplist_t pldir   = WMGetFromPLArray (dirlist, n);
      char	 *dirname = expand_tilde (WMGetFromPLString (pldir));
	 
      sibling = append_directory (dirname, GTK_CTREE (tree), NULL, sibling);
      gtk_ctree_sort_recursive (GTK_CTREE (tree), sibling);
   }

   gtk_clist_thaw (GTK_CLIST (tree));
   gtk_ctree_select (tree, gtk_ctree_node_nth (tree, 0));
}


/*****************************************************************************

			  preview generation
  
*****************************************************************************/

void
generate_previews (GtkWidget *button, gpointer ptr)
{
   GList    *background_list = NULL;
   GList    *titlebar_list   = NULL;
   GList    *icons_list      = NULL;
   unsigned  row;

   previews_active = YES;
   if (preview_update_menu)
      gtk_widget_set_sensitive (preview_update_menu, FALSE);

   /*
    *  Append image filenames in lists one for icons, titlebars and background
    */
   for (row = 0; row < (unsigned) GTK_CLIST (tree)->rows; row++)
   {
      GtkCTreeNode *theme = gtk_ctree_node_nth (tree, row);
      themeinfo_t  *info  = gtk_ctree_node_get_row_data (tree, theme);

      if (info->istheme)
      {
	 char *filename;
      
	 filename = theme_image_filename (info->fullname, "WorkspaceBack", NO);
	 if (filename)
	    background_list = g_list_append (background_list, filename);
	 filename = theme_image_filename (info->fullname, "IconBack", NO);
	 if (filename)
	    titlebar_list = g_list_append (titlebar_list, filename);
	 filename = theme_image_filename (info->fullname, "FTitleBack", NO);
	 if (filename)
	    icons_list = g_list_append (icons_list, filename);
	 filename = theme_image_filename (info->fullname, "UTitleBack", NO);
	 if (filename)
	    icons_list = g_list_append (icons_list, filename);
	 filename = theme_image_filename (info->fullname, "PTitleBack", NO);
	 if (filename)
	    icons_list = g_list_append (icons_list, filename);
	 filename = theme_image_filename (info->fullname, "MenuTitleBack", NO);
	 if (filename)
	    icons_list = g_list_append (icons_list, filename);
	 filename = theme_image_filename (info->fullname, "MenuTextBack", NO);
	 if (filename)
	    icons_list = g_list_append (icons_list, filename);
      }
   }

   /*
    *  Update each of the images
    */
   {
      GtkWidget *progress_window;
      GtkWidget *progress_label;
      GtkWidget *progress_bar;
      GtkWidget *vbox;

      /*
       *  Generate a progressbar and window
       */
      progress_window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
      gtk_window_set_position (GTK_WINDOW (progress_window),
			       GTK_WIN_POS_MOUSE);
      gtk_window_set_title (GTK_WINDOW (progress_window),
			    _("Update previews"));
      gtk_signal_connect (GTK_OBJECT (progress_window), "delete_event",
			  GTK_SIGNAL_FUNC (gtk_true), NULL);
      gtk_widget_set_usize (progress_window, 250, -1);
   
      vbox = gtk_vbox_new (FALSE, 5);
      gtk_container_add (GTK_CONTAINER (progress_window), vbox);
      gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
      progress_label = gtk_label_new (_("Update previews"));
      gtk_box_pack_start (GTK_BOX (vbox), progress_label, FALSE, TRUE, 0);

      {
	 GtkAdjustment *adj;
	 
	 adj = (GtkAdjustment *) gtk_adjustment_new (0, 1, 300, 0, 0, 0);
	 
	 progress_bar = gtk_progress_bar_new_with_adjustment (adj);
      }

      gtk_box_pack_start (GTK_BOX (vbox), progress_bar, FALSE, TRUE, 5);
      gtk_widget_show_all (progress_window);
      
      {
	 GList **listptr = (GList **) ptr;
	 int 	 n;
	 int 	 m 	 = g_list_length (background_list)
			   + g_list_length (titlebar_list)
			   + g_list_length (icons_list);

	 if (listptr && *listptr)
	    m += g_list_length (*listptr);
	 
	 for (n = 0; n < (int) g_list_length (background_list); n++)
	    compute_preview (progress_bar, progress_label, n, m,
			     g_list_nth_data (background_list, n), 160, 120);
	 for (n = 0; n < (int) g_list_length (titlebar_list); n++)
	    compute_preview (progress_bar, progress_label,
			     n + g_list_length (background_list), m,
			     g_list_nth_data (titlebar_list, n), 64, 64);
	 for (n = 0; n < (int) g_list_length (icons_list); n++)
	    compute_preview (progress_bar, progress_label,
			     n + g_list_length (background_list)
			     + g_list_length (titlebar_list), m,
			     g_list_nth_data (icons_list, n), 234, 22);
	 if (listptr && *listptr)
	 {
	    GList *list = *listptr;
	    
	    for (n = 0; n < (int) g_list_length (list); n++)
	    {
	       previewdata_t *pd    = g_list_nth_data (list, n);
	       char	     *pname = preview_name (pd->name);
	       char	     *path  = get_pixmap_path (pname);

	       if (!path)
	       {
		  compute_preview (progress_bar, progress_label,
				   n + g_list_length (background_list)
				   + g_list_length (titlebar_list)
				   + g_list_length (icons_list),
				   m, pd->name, 64, 64);
		  path = get_pixmap_path (pname);
	       }
	       if (path)
	       {
		  GtkWidget *pixmap;

		  pixmap = gtk_object_get_user_data (GTK_OBJECT (pd->button));
		  make_pixmap (path, 64, 64, pixmap);
		  Free (path);
	       }
	       Free (pname);
	    }
	 }
      }
      gtk_widget_destroy (progress_window);
   }

   g_list_foreach (background_list, free_memory, NULL);
   g_list_free (background_list);
   g_list_foreach (titlebar_list, free_memory, NULL);
   g_list_free (titlebar_list);
   g_list_foreach (icons_list, free_memory, NULL);
   g_list_free (icons_list);
   previews_active = NO;
   if (preview_update_menu)
      gtk_widget_set_sensitive (preview_update_menu, TRUE);
}

bool_t
load_attributes (const char *filename, const char *path)
/*
 *  Load attributes from a file 'filename' in directory 'path'.
 *
 *  Return value:
 *	true on success
 */
{
   static char * deprecated_keys[] = { "DisplayFont" };
   proplist_t theme = read_proplist (filename);

   if (theme)
   {
      unsigned	 n;
      proplist_t keys;
      proplist_t plupdate    = WMCreatePLString ("Update");
      proplist_t plupdateptr = WMCreatePLString ("UpdatePtr");

      /*
       * Remove deprecated keys from style
       */
      for (n = 0; n < sizeof (deprecated_keys) / sizeof (char *); n++)
      {
	 proplist_t depkeypl = WMCreatePLString (deprecated_keys[n]);
	 if (WMGetFromPLDictionary (theme, depkeypl))
	    WMRemoveFromPLDictionary (theme, depkeypl);
	 WMReleasePropList (depkeypl);
      }
      
      /*
       * Modify style file that doesn't contain resizebarback
       */
      {
	 proplist_t rsbpl     = WMCreatePLString ("ResizebarBack");
	 proplist_t resizebar = WMGetFromPLDictionary (theme, rsbpl);

	 if (!resizebar)
	 {
	    proplist_t utitle = WMCreatePLString ("UTitleBack");
	    proplist_t tmp    = WMGetFromPLDictionary (theme, utitle);

	    WMReleasePropList (utitle);
	    if (tmp)
	    {
	       proplist_t color = get_color (tmp);
	       if (color)
	       {
		  proplist_t array;

		  array = WMCreatePLArray (WMCreatePLString ("solid"),
						   WMRetainPropList (color), NULL);
		  WMPutInPLDictionary (theme, rsbpl, array);
		  WMReleasePropList (array);
	       }
	    }
	 }
	 WMReleasePropList (rsbpl);
      }
      /*
       * Modify style file that doesn't contain iconbarback
       */
      {
	 proplist_t icontitle = WMCreatePLString ("IconTitleColor");
	 proplist_t iconback  = WMCreatePLString ("IconTitleBack");
	 proplist_t iconpl    = WMGetFromPLDictionary (theme, icontitle);

	 if (!iconpl)
	    iconpl = WMGetFromPLDictionary (theme, iconback);
	 if (!iconpl)
	 {
	    proplist_t ftitle = WMCreatePLString ("FTitleColor");
	    proplist_t fback  = WMCreatePLString ("FTitleBack");
	    proplist_t tmp    = WMGetFromPLDictionary (theme, ftitle);

	    WMReleasePropList (ftitle);
	    if (tmp)
	       WMPutInPLDictionary (theme, icontitle, tmp);
	    tmp = WMGetFromPLDictionary (theme, fback);
	    WMReleasePropList (fback);
	    if (tmp)
	    {
	       proplist_t color = get_color (tmp);
	       if (color)
		  WMPutInPLDictionary (theme, iconback, color);
	    }
	 }
	 WMReleasePropList (icontitle);
	 WMReleasePropList (iconback);
      }
      keys = WMGetPLDictionaryKeys (theme)      ;
      for (n = 0; n < WMGetPropListItemCount (keys); n++)
      {
	 proplist_t key    = WMGetFromPLArray (keys, n);
	 proplist_t value  = WMGetFromPLDictionary (theme, key);
	 proplist_t keydef = WMGetFromPLDictionary (wmconfig, key);

	 if (keydef)
	 {
	    proplist_t keydata;
	    proplist_t keyupdate;

	    keydata   = WMGetFromPLDictionary (keydef, plupdateptr);
	    keyupdate = WMGetFromPLDictionary (keydef, plupdate);
		  
	    if (keyupdate && keydata)
	    {
	       gpointer   data;
	       update_fct update;  

	       data   = * (gpointer *) WMGetPLDataBytes (keydata);
	       update = * (update_fct *) WMGetPLDataBytes (keyupdate);

	       update (key, data, value, path);
	    }
	    else
	       warning (_("Attribute %s not yet supported."),
			WMGetFromPLString (key));
	 }
	 else
	    warning (_("Attribute %s not yet supported."), WMGetFromPLString (key));
      }
      WMReleasePropList (plupdateptr);
      WMReleasePropList (plupdate);
      WMReleasePropList (keys);
      WMReleasePropList (theme);
      return TRUE;
   }
   else
      return FALSE;
}

void
connect_update_function (proplist_t key, gpointer ptr, update_fct update)
/*
 *  Connect an 'update' function with the current 'key' and
 *  generic datapointer 'ptr'.
 *
 *  Side effects:
 *	wmconfig dictionary Update and UpdatePtr are set
 */
{
   proplist_t plupdate    = WMCreatePLString ("Update");
   proplist_t plupdateptr = WMCreatePLString ("UpdatePtr");
   proplist_t keydef      = WMGetFromPLDictionary (wmconfig, key);
   proplist_t data;
   
   data = WMCreatePLDataWithBytes ((unsigned char *) &ptr, sizeof (gpointer));
   WMPutInPLDictionary (keydef, plupdateptr, data);
   WMReleasePropList (data);

   data = WMCreatePLDataWithBytes ((unsigned char *) &update, sizeof (update_fct));
   WMPutInPLDictionary (keydef, plupdate, data);
   WMReleasePropList (data);

   WMReleasePropList (plupdateptr);
   WMReleasePropList (plupdate);
}
/*****************************************************************************

			     private code
  
*****************************************************************************/

static void
toggle_selection_mode (GtkWidget *widget, gpointer ptr)
/*
 *  Toggle confirm panel when using the EXIT or SHUTDOWN command.
 */
{
   GtkCTree *tree = GTK_CTREE (ptr);

   if (GTK_TOGGLE_BUTTON (widget)->active)
   {
      gtk_clist_set_selection_mode (GTK_CLIST (tree), GTK_SELECTION_BROWSE);
      gtk_clist_set_reorderable (GTK_CLIST (tree), TRUE);
   }
   else
   {
      gtk_clist_set_selection_mode (GTK_CLIST (tree), GTK_SELECTION_EXTENDED);
      gtk_clist_set_reorderable (GTK_CLIST (tree), FALSE);
   }
}

static gint
context_menu (GtkCTree *ctree, GdkEventButton *event, gpointer ptr)
/*
 *  Popup context menu (right click)
 *
 *  Return value:
 *	TRUE  show popup menu
 *	FALSE don't show popup menu
 */
{
   gint		 row;
   gint		 column;
   GtkCTreeNode *selection;
   bool_t 	 single_selection = NO;
   bool_t 	 istheme 	  = NO;
   bool_t	 toplevel 	  = NO;
   
   if (!gtk_clist_get_selection_info (GTK_CLIST (ctree),
				      event->x, event->y, 
				      &row, &column) || event->button != 3)
      return FALSE;

   selection 	    = get_selected_node (tree);
   single_selection = selection != NULL;

   if (single_selection)
   {
      themeinfo_t *info = gtk_ctree_node_get_row_data (tree, selection);
      
      istheme  = info->istheme;
      toplevel = !info->parent;
   }
   else
      istheme = NO;
   
   {
      static GtkWidget *menu = NULL;
      GtkWidget	       *menu_item;
      
      if (menu)
	 gtk_widget_destroy (menu);

      menu = gtk_menu_new ();

#ifdef GETSTYLE
      if (istheme)
      {
	 menu_item = gtk_menu_item_new_with_label (_("Open"));
	 gtk_menu_append (GTK_MENU (menu), menu_item);
	 gtk_widget_show (menu_item);
	 gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			     GTK_SIGNAL_FUNC (load_theme), NULL);
      }
#endif /* GETSTYLE */

      if (single_selection && !toplevel)
      {
	 menu_item = gtk_menu_item_new_with_label (_("Rename..."));
	 gtk_menu_append (GTK_MENU (menu), menu_item);
	 gtk_widget_show (menu_item);
	 gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			     GTK_SIGNAL_FUNC (rename_theme_dialog), ctree);
      }

#ifdef GETSTYLE
      if (single_selection)
      {
	 menu_item = gtk_menu_item_new_with_label (_("Save..."));
	 gtk_menu_append (GTK_MENU (menu), menu_item);
	 gtk_widget_show (menu_item);
	 gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			     GTK_SIGNAL_FUNC (save_theme_dialog), NULL);
      }
#endif /* GETSTYLE */

      
      if (!toplevel)
      {
	 menu_item = gtk_menu_item_new_with_label (_("Remove..."));
	 gtk_menu_append (GTK_MENU (menu), menu_item);
	 gtk_widget_show (menu_item);
	 gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			     GTK_SIGNAL_FUNC (remove_theme_dialog), NULL);
      }
      
      if (single_selection && !toplevel)
      {
	 menu_item = gtk_menu_item_new_with_label (_("New directory..."));
	 gtk_menu_append (GTK_MENU (menu), menu_item);
	 gtk_widget_show (menu_item);
	 gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			     GTK_SIGNAL_FUNC (new_directory_dialog), ctree);
      }

#ifdef GETSTYLE
#	ifdef SETSTYLE
      if (!single_selection || istheme)
      {
	 menu_item = gtk_menu_item_new ();
	 gtk_menu_append (GTK_MENU (menu), menu_item);
	 gtk_widget_show (menu_item);
      
	 menu_item = gtk_menu_item_new_with_label (_("Preview"));
	 gtk_menu_append (GTK_MENU (menu), menu_item);
	 gtk_widget_show (menu_item);
	 gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			     GTK_SIGNAL_FUNC (preview_dialog), NULL);
      }
#	endif /* SETSTYLE */
#endif /* GETSTYLE */

#ifdef GETSTYLE
      if (istheme)
      {
	 menu_item = gtk_menu_item_new_with_label (_("Set"));
	 gtk_menu_append (GTK_MENU (menu), menu_item);
	 gtk_widget_show (menu_item);
	 gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			     GTK_SIGNAL_FUNC (set_theme), NULL);
      }
#endif /* GETSTYLE */

      menu_item = gtk_menu_item_new ();
      gtk_menu_append (GTK_MENU (menu), menu_item);
      gtk_widget_show (menu_item);
      
      menu_item = gtk_menu_item_new_with_label (_("Install..."));
      gtk_menu_append (GTK_MENU (menu), menu_item);
      gtk_widget_show (menu_item);
      gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			  GTK_SIGNAL_FUNC (install_theme_dialog), NULL);

      menu_item = gtk_menu_item_new_with_label (_("Directory list..."));
      gtk_menu_append (GTK_MENU (menu), menu_item);
      gtk_widget_show (menu_item);
      gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			  GTK_SIGNAL_FUNC (select_directory_dialog), NULL);
      
      menu_item = gtk_menu_item_new_with_label (_("Expand directory tree"));
      gtk_menu_append (GTK_MENU (menu), menu_item);
      gtk_widget_show (menu_item);
      gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			  GTK_SIGNAL_FUNC (expand_recursive), NULL);

      menu_item = gtk_menu_item_new_with_label (_("Collapse directory tree"));
      gtk_menu_append (GTK_MENU (menu), menu_item);
      gtk_widget_show (menu_item);
      gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			  GTK_SIGNAL_FUNC (collapse_recursive), NULL);

#ifdef PREVIEWS	 
      preview_update_menu
	 = menu_item = gtk_menu_item_new_with_label (_("Update previews"));
      gtk_menu_append (GTK_MENU (menu), menu_item);
      gtk_widget_show (menu_item);
      gtk_signal_connect (GTK_OBJECT(menu_item), "activate",
			  GTK_SIGNAL_FUNC (generate_previews), NULL);
      gtk_widget_set_sensitive (preview_update_menu, !previews_active);

#endif /* PREVIEWS */
      
      gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, event->time);
   }
   return TRUE;
}

/*****************************************************************************

			   build theme tree
  
*****************************************************************************/

static GtkCTreeNode *
append_directory (const char *dirname, GtkCTree *tree,
		  GtkCTreeNode *parent, GtkCTreeNode *sibling)
/*
 *  Append a new theme directory 'dirname' in theme 'tree'.
 *  Position of entry is given by 'parent' and 'sibling'.
 *
 *  Return Value:
 *	new tree node
 */
{
   DIR *dir = opendir (dirname);

   if (!dir)
      return NULL;
   else
   {
      GtkCTreeNode *root;
      GtkCTreeNode *rootsibling = NULL;

      if (parent)
	 root = append_node ((char *) g_basename (dirname), tree, parent,
			     sibling, NO);
      else
	 root = append_node ((char *) dirname, tree, parent, sibling, NO);
      
      /*
       *  Insert themes and subdirs  
       */
      {
	 struct dirent *file;

	 while ((file = readdir (dir)))
	 {
	    if (streq (file->d_name, ".") || streq (file->d_name, ".."))
	       continue;

	    /*
	     * Check whether filename is .themed directory
	     */
	    {
	       char *path   = g_strconcat (dirname, "/", file->d_name, NULL);
	       DIR  *subdir = opendir (path);
	       char *ext;
	       
	       if (subdir)		/* .themed or subdir */
	       {
		  if (!strstr (path, ".themed"))
		  {
		     sibling = append_directory (path, tree, root, sibling);
		     Free (path);
		     continue;
		  }
		  closedir (subdir);
	       }

	       ext = strrchr (path, '.');
	       if (ext && (streq (ext, ".tar") || streq (ext, ".gz")
			   || streq (ext, ".tgz") || streq (ext, ".bz2")))
	       {
		  Free (path);
		  continue;
	       }

	       sibling = append_node (file->d_name, tree, root,
				      rootsibling, YES);
	       Free (path);
	    }
	 }
      }

      closedir (dir);
      
      return root;
   }
}

static GtkCTreeNode *
append_node (char *dirname, GtkCTree *tree,
	     GtkCTreeNode *parent, GtkCTreeNode *sibling, bool_t theme)
/*
 *  Append a new tree node with text 'dirname' in theme 'tree'.
 *  Position of entry is given by 'parent' and 'sibling'.
 *  'theme' defines whether the file is a theme (YES) or dir (NO).
 *
 *  Return Value:
 *	new tree node
 */
{
   char	     	*old;
   guint8     	 spacing;
   GdkPixmap 	*closed, *opened;
   GdkBitmap 	*mask_closed, *mask_opened;
   gboolean   	 is_leaf, expanded;
   GtkCTreeNode *root;
   themeinfo_t  *info = Calloc (1, sizeof (themeinfo_t));

   if (parent)
   {
      themeinfo_t *pinfo = gtk_ctree_node_get_row_data (tree, parent);

      info->fullname = g_strconcat (pinfo->fullname, "/", dirname, NULL);
   }
   else
      info->fullname = g_strdup (dirname);
   
   info->istheme 	  = theme;
   info->parent   = parent;
   info->suffix   = NULL;

   root = gtk_ctree_insert_node (tree, parent, sibling,
				 &dirname, 8, NULL, NULL, NULL, NULL,
				 FALSE, theme ? FALSE : TRUE);
   gtk_ctree_get_node_info (tree, root, &old, &spacing,
			    &closed, &mask_closed, &opened, &mask_opened,
			    &is_leaf, &expanded);
   if (theme)
   {
      char *new = g_strdup (dirname);
      
      if (strlen (new) > strlen (".themed")
	  && streq (new + strlen (new) - strlen (".themed"), ".themed"))
      {
	 info->suffix = g_strdup (".themed");
	 *(new + strlen (new) - strlen (".themed")) = 0;
      }
      
      if (strlen (new) > strlen (".style")
	  && streq (new + strlen (new) - strlen (".style"), ".style"))
      {
	 info->suffix = g_strdup (".style");
	 *(new + strlen (new) - strlen (".style")) = 0;
      }
      
      info->name = g_strdup (new);
      gtk_ctree_set_node_info (tree, root, new, 8,
			       p_array [P_GNUSTEP].pixmap,
			       p_array [P_GNUSTEP].mask,
			       NULL, NULL, FALSE, expanded);
      g_free (new);
   }
   else
   {
      info->name = g_strdup (dirname);
      gtk_ctree_set_node_info (tree, root, dirname, 8,
			       p_array [P_BOOK_CLOSE].pixmap,
			       p_array [P_BOOK_CLOSE].mask,
			       p_array [P_BOOK_OPEN].pixmap,
			       p_array [P_BOOK_OPEN].mask, FALSE, expanded);
   }
   
   gtk_ctree_node_set_row_data_full (tree, root, info, info_destructor);
   
   return root;
}

static void
info_destructor (gpointer data)
/*
 *  Cleanup memory of theme information structure.
 */
{
   themeinfo_t *info = (themeinfo_t *) data;

   if (info->name)
      Free (info->name);
   if (info->suffix)
      Free (info->suffix);
   Free (info);
}

/*****************************************************************************

			miscellaneous operations
  
*****************************************************************************/

static void
expand_recursive (GtkWidget *widget, gpointer ptr)
{
   gtk_ctree_expand_recursive (tree, NULL);
}

static void
collapse_recursive (GtkWidget *widget, gpointer ptr)
{
   gtk_ctree_collapse_recursive (tree, NULL);
}

static gboolean
node_drag_compare (GtkCTree *tree, GtkCTreeNode *source_node,
		   GtkCTreeNode *new_parent, GtkCTreeNode *new_sibling)
/*
 *  Check whether dragged theme or directory can be dragged at current
 *  position.
 *
 *  Return Value:
 *	TRUE if drag is allowed, FALSE otherwise
 */
{
   if (!new_parent)
      return FALSE;
   else
   {
      themeinfo_t *info  = gtk_ctree_node_get_row_data (tree, source_node);
      themeinfo_t *pinfo = gtk_ctree_node_get_row_data (tree, new_parent);

      if (!info->parent)
	 return FALSE;

      if (pinfo->istheme)
	 return FALSE;
   
      if (info->parent != new_parent)
	 return TRUE;
      else
	 return FALSE;
   }
}

GtkCTreeNode *
get_selected_node (GtkCTree *tree)
/*
 *  Get selected tree node.
 *
 *  Return value:
 *	selected node on success
 *	NULL otherwise
 */
{
   GList *selection = GTK_CLIST (tree)->selection;

   if (!GTK_CLIST (tree)->selection || g_list_length (selection) != 1)
      return NULL;
   else
      return g_list_nth_data (selection, 0);
}

static void
select_theme (GtkCTree *tree, const char *text)
/*
 *  Select a theme given by the specified filename (position: middle of screen)
 *
 *  No return value.
 */
{
   GtkCTreeNode *current;

   current = gtk_ctree_find_by_row_data_custom (tree, NULL, (gpointer) text,
						compare_themename);
   if (current)
   {
      gtk_ctree_select (tree, current);
      gtk_ctree_node_moveto (tree, current, 0, 0.5, 0);
   }
}

static gint
compare_themename (gconstpointer a, gconstpointer b)
/*
 *  Check whether the theme date pointer filename is equal to string 'b'.
 *
 *  Return value:
 *	0	  strings are equal
 *      otherwise continue search
 */
{
   themeinfo_t *info = (themeinfo_t *) a;
   const char  *text = (const char *) b;

   return !streq (info->fullname, text);
}

static void
activate_button (GtkWidget *widget, gpointer ptr)
/*
 *  Simulate a button click.
 *
 *  No return value.
 */
{
   gtk_signal_emit_by_name (GTK_OBJECT (ptr), "clicked");
}

static void
set_node_text (GtkCTree *tree, GtkCTreeNode *node, const char *text)
/*
 *  Set text of theme (tree node).
 */
{
   char	     *old;
   guint8     spacing;
   GdkPixmap *closed, *opened;
   GdkBitmap *mask_closed, *mask_opened;
   gboolean   is_leaf, expanded;
   
   gtk_ctree_get_node_info (tree, node, &old, &spacing,
			    &closed, &mask_closed, &opened, &mask_opened,
			    &is_leaf, &expanded);
   gtk_ctree_set_node_info (tree, node, text, spacing,
			    closed, mask_closed, opened, mask_opened,
			    is_leaf, expanded);
}

/*****************************************************************************

			rename theme operation
  
*****************************************************************************/

static void
rename_theme_dialog (GtkWidget *widget, gpointer ptr)
/*
 *  Dialog window to rename theme file or directory.
 *
 *  No return value.
 */
{
   GtkCTree     *tree 	   = (GtkCTree *) ptr;
   GtkCTreeNode *selection = get_selected_node (tree);
   themeinfo_t 	*info;
   
   if (!selection)
      return;

   info = gtk_ctree_node_get_row_data (tree, selection);
	  
   /*
    *  Dialog window
    */
   {
      GtkWidget *window = gtk_dialog_new ();
      GtkWidget *vbox   = gtk_vbox_new (FALSE, 0);
      GtkWidget *entry  = gtk_entry_new ();
      char 	*text;

      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox),
			  vbox, TRUE, TRUE, 0);
      
      /*
       *  Window
       */
      gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
      gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_MOUSE);
      
      gtk_window_set_title (GTK_WINDOW (window), _("Rename theme"));
      gtk_signal_connect_object (GTK_OBJECT (window), "delete_event",
				 GTK_SIGNAL_FUNC (gtk_widget_destroy),
				 GTK_OBJECT (window));

      /*
       *  Label and entry
       */
      if (info->suffix && streq (info->suffix, ".themed"))
	 text = g_strdup_printf (_("Rename theme directory `%s' to:"),
				 info->name);
      else
      {
	 if (info->istheme)
	    text = g_strdup_printf (_("Rename theme file `%s' to:"),
				    info->name);
	 else
	    text = g_strdup_printf (_("Rename directory `%s' to:"),
				    info->name);
      }
      gtk_box_pack_start (GTK_BOX (vbox), gtk_label_new (text), TRUE, TRUE, 0);
      g_free (text);
      gtk_entry_set_text (GTK_ENTRY (entry), info->name);
      gtk_entry_select_region (GTK_ENTRY (entry), 0, -1);
      gtk_object_set_user_data (GTK_OBJECT (entry), info);
      gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0);
      gtk_widget_grab_focus (entry);
   
      /*
       *  Cancel and OK buttons
       */
      {
	 GtkWidget *hbox = gtk_hbutton_box_new ();
	 GtkWidget *button;
	 
	 gtk_button_box_set_spacing (GTK_BUTTON_BOX (hbox), 5);
	 gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
	 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), hbox,
			     FALSE, TRUE, 5);
	 button = gtk_button_new_with_label (_("OK"));
	 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 5);
	 gtk_signal_connect (GTK_OBJECT (button), "clicked",
			     GTK_SIGNAL_FUNC (rename_theme), entry);
	 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
				    GTK_SIGNAL_FUNC (gtk_widget_destroy),
				    GTK_OBJECT (window));
	 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
	 gtk_widget_grab_default (button);
	 gtk_signal_connect (GTK_OBJECT (entry), "activate",
			     GTK_SIGNAL_FUNC (activate_button), button);

	 button = gtk_button_new_with_label (_("Cancel"));
	 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 5);
	 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
				    GTK_SIGNAL_FUNC (gtk_widget_destroy),
				    GTK_OBJECT (window));
	 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
      }
      gtk_grab_add (window);
      gtk_widget_show_all (window);
   }
}

static void
rename_theme (GtkWidget *widget, gpointer ptr)
/*
 *  Rename selected theme.
 *  Computes old and new filename and checks whether file already exists.
 *
 *  No return value.
 */
{
   GtkEntry  	 *entry      = (GtkEntry *) ptr;
   themeinfo_t 	 *info       = gtk_object_get_user_data (GTK_OBJECT (entry));
   GtkCTreeNode  *parent     = info->parent;
   themeinfo_t 	 *parentinfo = gtk_ctree_node_get_row_data (tree, parent);
   char 	 *oldname    = g_strconcat (parentinfo->name, "/",
					   info->name, info->suffix, NULL);
   char 	 *newname    = g_strconcat (parentinfo->name, "/",
					   gtk_entry_get_text (entry),
					   info->suffix, NULL);
   themerename_t *renameinfo = Calloc (1, sizeof (themerename_t));

   renameinfo->oldname = oldname;
   renameinfo->newname = newname;

   if (!streq (oldname, newname))
   {
      if (file_exists (newname))
	 dialog_popup (DIALOG_QUESTION, rename_theme_delete, renameinfo,
		       _("Overwrite existing theme\n`%s'"), newname);
      else
	 rename_theme_backend (renameinfo);
   }
   else
   {
      Free (renameinfo->newname);
      Free (renameinfo->oldname);
      Free (renameinfo);
   }
}

static void
rename_theme_backend (themerename_t *renameinfo)
/*
 *  Actually performs the rename operation. If successful, the tree
 *  list is computed again.
 */
{
   if (!rename_file_or_dir (renameinfo->oldname, renameinfo->newname))
   {
      build_theme_list ();
      select_theme (tree, renameinfo->newname);
   }

   Free (renameinfo->newname);
   Free (renameinfo->oldname);
   Free (renameinfo);
}

static void
rename_theme_delete (GtkWidget *widget, gpointer ptr)
/*
 *  Overwrite old theme.
 */
{
   themerename_t *renameinfo = (themerename_t *) ptr;

   if (!remove_directory (renameinfo->newname))
      rename_theme_backend (renameinfo);
   else
   {
      Free (renameinfo->newname);
      Free (renameinfo->oldname);
      Free (renameinfo);
   }
}

/*****************************************************************************

		    remove themes (files and dirs)
  
*****************************************************************************/

static void
remove_theme_dialog (GtkWidget *button, gpointer ptr)
{
   GList *selection = GTK_CLIST (tree)->selection;

   if (!GTK_CLIST (tree)->selection || g_list_length (selection) < 1)
      return;
   else
   {
      GtkWidget	*window;
      GtkBox	*vbox;

      window = gtk_dialog_new ();
      gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_MOUSE);

      vbox = GTK_BOX (GTK_DIALOG (window)->vbox);
      gtk_window_set_title (GTK_WINDOW (window), _("Remove themes"));

      gtk_signal_connect_object (GTK_OBJECT (window), "destroy",
				 GTK_SIGNAL_FUNC (gtk_widget_destroy),
				 GTK_OBJECT (window));
      {
	 gtk_box_pack_start (GTK_BOX (vbox), gtk_label_new (""),
			     FALSE, TRUE, 0);
	 
	 {
	    GList    *selection = GTK_CLIST (tree)->selection;
	    unsigned  n;

	    if (g_list_length (selection) > 1
		&& g_list_length (selection) < 8)
	    {
	       gtk_box_pack_start (GTK_BOX (vbox),
				   gtk_label_new (_("Remove files and directories of themes")),
				   FALSE, TRUE, 0);

	       for (n = 0; n < g_list_length (selection); n++)
	       {
		  GtkCTreeNode *theme = g_list_nth_data (selection, n);
		  themeinfo_t  *info  = gtk_ctree_node_get_row_data (tree,
								     theme);
		  if (info->parent)
		     gtk_box_pack_start (GTK_BOX (vbox),
					 gtk_label_new (info->name),
					 FALSE, TRUE, 0);
	       }
	    }
	    else if (g_list_length (selection) == 1)
	    {
	       GtkCTreeNode *theme = g_list_nth_data (selection, 0);
	       themeinfo_t  *info  = gtk_ctree_node_get_row_data (tree,
								  theme);
	       char *text;
	       
	       text = g_strdup_printf (_("Remove files and directories "
					 "of theme `%s'"), info->name);
	       gtk_box_pack_start (GTK_BOX (vbox), gtk_label_new (text),
				   FALSE, TRUE, 0);
	       Free (text);
	    }
	    else
	    {
	       gtk_box_pack_start (GTK_BOX (vbox),
				   gtk_label_new (_("Remove files and directories of selected themes")),
				   FALSE, TRUE, 0);
	    }
	 }
	 
	 gtk_box_pack_start (GTK_BOX (vbox), gtk_label_new (""),
			     FALSE, TRUE, 0);
	 gtk_box_pack_start (GTK_BOX (vbox),
			     gtk_label_new (_("Select individual files and directories:")),
			     FALSE, TRUE, 0);
      }
      {
	 GtkWidget *filelist = gtk_clist_new (1);

	 gtk_clist_set_auto_sort (GTK_CLIST (filelist), YES);
	 gtk_clist_set_column_auto_resize (GTK_CLIST (filelist), 0, YES);
	 gtk_clist_column_titles_passive (GTK_CLIST (filelist));
	 gtk_clist_set_selection_mode (GTK_CLIST (filelist),
				       GTK_SELECTION_EXTENDED);
	 {
	    GtkWidget *scrolled = gtk_scrolled_window_new (NULL, NULL);
	       
	    gtk_container_set_border_width (GTK_CONTAINER (scrolled), 5);
	    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
					    GTK_POLICY_AUTOMATIC,
					    GTK_POLICY_AUTOMATIC);
	    gtk_container_add (GTK_CONTAINER (scrolled), filelist);
	    gtk_box_pack_start (vbox, scrolled, TRUE, TRUE, 0);
	    gtk_widget_set_usize (scrolled, 550, 350);
	 }
	 /*
	  *  Buttons
	  */
	 {
	    GtkWidget *hbox = gtk_hbutton_box_new ();
	    GtkWidget *button;
	 
	    gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), hbox,
				TRUE, TRUE, 0);

	    gtk_button_box_set_spacing (GTK_BUTTON_BOX (hbox), 5);
	    gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox),
				       GTK_BUTTONBOX_END);

	    button = gtk_button_new_with_label (_("Remove"));
	    gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 5);
	    gtk_signal_connect (GTK_OBJECT (button), "clicked",
				GTK_SIGNAL_FUNC (remove_theme), filelist);
	    gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
				       GTK_SIGNAL_FUNC (gtk_widget_destroy),
				       GTK_OBJECT (window));
	    GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
    
	    button = gtk_button_new_with_label (_("Cancel"));
	    gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 5);
	    gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
				       GTK_SIGNAL_FUNC (gtk_widget_destroy),
				       GTK_OBJECT (window));
	    GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
	    gtk_widget_grab_default (button);
	 }

	 /*
	  *  List of Files
	  */
	 {
	    unsigned  n;
	    GList    *selection = GTK_CLIST (tree)->selection;
      
	    for (n = 0; n < g_list_length (selection); n++)
	    {
	       GtkCTreeNode *theme = g_list_nth_data (selection, n);
	       themeinfo_t  *info  = gtk_ctree_node_get_row_data (tree, theme);

	       if (!info->parent)
		  continue;
		  
	       gtk_clist_insert (GTK_CLIST (filelist), -1, &info->fullname);
	       if (info->suffix && streq (info->suffix, ".themed"))
	       {
		  DIR  *themedir = opendir (info->fullname);
		  if (themedir)
		  {
		     struct dirent *file;
		  
		     while ((file = readdir (themedir)))
		     {
			if (!streq (file->d_name, ".")
			    && !streq (file->d_name, ".."))
			{
			   char *tmp = g_strconcat (info->fullname, "/",
						    file->d_name,
						    NULL);
			   gtk_clist_insert (GTK_CLIST (filelist), -1, &tmp);
			   Free (tmp);
			}
		     }
		     closedir (themedir);
		  }
	       }
	       else
	       {
		  const char *attr[] = {"WorkspaceBack", "IconBack",
					"FTitleBack", "UTitleBack",
					"PTitleBack", "MenuTitleBack",
					"MenuTextBack"};
		  unsigned n;

		  for (n = 0; n < sizeof (attr) / sizeof (attr [0]); n++)
		  {
		     char *name = theme_image_filename (info->fullname,
							attr [n], NO);

		     if (name)
		     {
			char *pname = preview_name (name);
			char *path  = get_pixmap_path (pname);
			
			gtk_clist_insert (GTK_CLIST (filelist), -1, &name);
			if (path)
			{
			   gtk_clist_insert (GTK_CLIST (filelist), -1, &path);
			   Free (path);
			}
			Free (pname);
			Free (name);
		     }
		  }
	       }
	    }
	 }
	 gtk_clist_select_all (GTK_CLIST (filelist));
	 gtk_widget_show_all (window);
      }
   }
}

static void
remove_theme (GtkWidget *widget, gpointer ptr)
/*
 *  Actually remove theme files
 */
{
   GtkCList *filelist = GTK_CLIST (ptr);
   GList    *list     = g_list_reverse (filelist->selection);

   g_list_foreach (list, remove_file, filelist);
   build_theme_list ();
}

static void
remove_file (gpointer data, gpointer user_data)
/*
 *  Remove a file
 */
{
   GtkCList	*filelist = GTK_CLIST (user_data);
   gint		row       = GPOINTER_TO_INT (data);
   char		*path;
   
   gtk_clist_get_text (filelist, row, 0, &path);
   delete_file_or_dir (path);
}

/*****************************************************************************

		       new directory operation
  
*****************************************************************************/

static void
new_directory_dialog (GtkWidget *widget, gpointer ptr)
{
   GtkCTree     *tree 	   = (GtkCTree *) ptr;
   GtkCTreeNode *selection = get_selected_node (tree);
   themeinfo_t 	*info;
   themeinfo_t 	*pinfo;
   
   if (!selection)
      return;

   info = gtk_ctree_node_get_row_data (tree, selection);

   assert (info->parent);

   pinfo = gtk_ctree_node_get_row_data (tree, info->parent);
   /*
    *  Dialog window
    */
   {
      GtkWidget *window = gtk_dialog_new ();
      GtkWidget *vbox   = gtk_vbox_new (FALSE, 0);
      GtkWidget *entry  = gtk_entry_new ();
      char 	*text;

      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox),
			  vbox, TRUE, TRUE, 0);
      /*
       *  Window
       */
      gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
      gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_MOUSE);
      
      gtk_window_set_title (GTK_WINDOW (window), _("New directory"));
      gtk_signal_connect_object (GTK_OBJECT (window), "delete_event",
				 GTK_SIGNAL_FUNC (gtk_widget_destroy),
				 GTK_OBJECT (window));

      text = g_strdup_printf (_("Make new subdirectory in\n`%s':"),
			      pinfo->fullname);
      gtk_object_set_user_data (GTK_OBJECT (entry), pinfo);
      
      gtk_box_pack_start (GTK_BOX (vbox), gtk_label_new (text), TRUE, TRUE, 0);
      g_free (text);
      gtk_entry_select_region (GTK_ENTRY (entry), 0, -1);
      gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0);
      gtk_widget_grab_focus (entry);
   
      /*
       *  Cancel and OK buttons
       */
      {
	 GtkWidget *hbox = gtk_hbutton_box_new ();
	 GtkWidget *button;
	 
	 gtk_button_box_set_spacing (GTK_BUTTON_BOX (hbox), 5);
	 gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
	 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), hbox,
			     FALSE, TRUE, 5);
	 button = gtk_button_new_with_label (_("OK"));
	 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 5);
	 gtk_signal_connect (GTK_OBJECT (button), "clicked",
			     GTK_SIGNAL_FUNC (new_directory_backend), entry);
	 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
				    GTK_SIGNAL_FUNC (gtk_widget_destroy),
				    GTK_OBJECT (window));
	 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
	 gtk_widget_grab_default (button);
	 gtk_signal_connect (GTK_OBJECT (entry), "activate",
			     GTK_SIGNAL_FUNC (activate_button), button);

	 button = gtk_button_new_with_label (_("Cancel"));
	 gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 5);
	 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
				    GTK_SIGNAL_FUNC (gtk_widget_destroy),
				    GTK_OBJECT (window));
	 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
      }
      gtk_grab_add (window);
      gtk_widget_show_all (window);
   }
}

static void
new_directory_backend (GtkWidget *widget, gpointer ptr)
/*
 *  Actually performs the mkdir operation. If successful, the tree
 *  list is computed again.
 */
{
   GtkEntry    *entry     = (GtkEntry *) ptr;
   themeinfo_t *info      = gtk_object_get_user_data (GTK_OBJECT (entry));
   char        *directory = g_strdup(gtk_entry_get_text (entry));

   if (info)
      directory = g_strconcat (info->fullname, "/", directory, NULL);
   
   if (!make_directory (directory))
   {
      build_theme_list ();
      select_theme (tree, directory);
   }

   if (info)
      Free (directory);
}

static void
select_directory_dialog (GtkWidget *button, gpointer data)
/*
 *  Dialog window to add a new toplevel directory.
 *
 *  No return value.
 */
{
   GtkWidget  *window = gtk_dialog_new ();
   GtkWidget  *vbox   = gtk_vbox_new (FALSE, 0);
   proplist_t pltheme = WMCreatePLString ("ThemePath");
   proplist_t plinfo  = WMCreatePLString ("Info");
   proplist_t keydef  = WMGetFromPLDictionary (wmconfig, pltheme);
   proplist_t array   = WMGetFromPLDictionary (windowmaker, pltheme);
   proplist_t info    = WMGetFromPLDictionary (keydef, plinfo);

   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox),
		       vbox, TRUE, TRUE, 0);
   /*
    *  Window
    */
   gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
   gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_MOUSE);
   gtk_widget_set_usize (window, 550, -1);
   gtk_window_set_title (GTK_WINDOW (window),
			 _("Toplevel theme directory list"));
   gtk_signal_connect_object (GTK_OBJECT (window), "delete_event",
			      GTK_SIGNAL_FUNC (gtk_widget_destroy),
			      GTK_OBJECT (window));

   path_dialog (vbox, pltheme, array, tooltips, info);
   
   /*
    *  Close button
    */
   {
      GtkWidget *hbox = gtk_hbutton_box_new ();
      GtkWidget *button;
	 
      gtk_button_box_set_spacing (GTK_BUTTON_BOX (hbox), 5);
      gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), hbox,
			  FALSE, TRUE, 5);
      button = gtk_button_new_with_label (_("Close"));
      gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, TRUE, 5);
      gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
				 GTK_SIGNAL_FUNC (gtk_widget_destroy),
				 GTK_OBJECT (window));
      GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
      gtk_widget_grab_default (button);
   }
   gtk_widget_show_all (window);
}

/*****************************************************************************

			 theme preview window
  
*****************************************************************************/

static GtkWidget *
theme_preview (void)
{
   GtkWidget *frame = gtk_frame_new (_("Preview"));
   GtkWidget *hbox  = gtk_hbox_new (FALSE, 0);
   GtkWidget *vbox  = gtk_vbox_new (FALSE, 0);
   GtkWidget *ibox;

   gtk_frame_set_label_align (GTK_FRAME (frame), 0.5, 0.5);
   gtk_container_set_border_width (GTK_CONTAINER (frame), 5);
   gtk_container_add (GTK_CONTAINER (frame), vbox);
   gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);

   ibox          = gtk_vbox_new (FALSE, 0);
   workspaceback = make_pixmap (PKGDATADIR "/black.xpm", 160, 120, NULL);
   gtk_box_pack_start (GTK_BOX (hbox), ibox, FALSE, TRUE, 0);
   gtk_box_pack_start (GTK_BOX (ibox), workspaceback, TRUE, TRUE, 0);
   gtk_container_set_border_width (GTK_CONTAINER (ibox), 5);

   ibox     = gtk_vbox_new (FALSE, 0);
   iconback = make_pixmap (PKGDATADIR "/black.xpm", 64, 64, NULL);
   gtk_box_pack_start (GTK_BOX (hbox), ibox, FALSE, TRUE, 0);
   gtk_box_pack_start (GTK_BOX (ibox), iconback, TRUE, TRUE, 0);
   gtk_container_set_border_width (GTK_CONTAINER (ibox), 5);
   
   ibox         = gtk_vbox_new (FALSE, 0);
   titlebarback = make_pixmap (PKGDATADIR "/black.xpm", 234, 22, NULL);
   gtk_box_pack_start (GTK_BOX (ibox),
		       gtk_label_new (_("Focused titlebar")),
		       FALSE, TRUE, 0);
   gtk_box_pack_start (GTK_BOX (vbox), ibox, FALSE, TRUE, 5);
   gtk_box_pack_start (GTK_BOX (ibox), titlebarback, TRUE, TRUE, 0);
   gtk_container_set_border_width (GTK_CONTAINER (ibox), 0);
   
   ibox         	= gtk_vbox_new (FALSE, 0);
   unfocus_titlebarback = make_pixmap (PKGDATADIR "/black.xpm", 234, 22, NULL);
   gtk_box_pack_start (GTK_BOX (ibox),
		       gtk_label_new (_("Unfocused titlebar")),
		       FALSE, TRUE, 0);
   gtk_box_pack_start (GTK_BOX (vbox), ibox, FALSE, TRUE, 5);
   gtk_box_pack_start (GTK_BOX (ibox), unfocus_titlebarback, TRUE, TRUE, 0);
   gtk_container_set_border_width (GTK_CONTAINER (ibox), 0);
   
   ibox                = gtk_vbox_new (FALSE, 0);
   parent_titlebarback = make_pixmap (PKGDATADIR "/black.xpm", 234, 22, NULL);
   gtk_box_pack_start (GTK_BOX (ibox),
		       gtk_label_new (_("Parent titlebar")),
		       FALSE, TRUE, 0);
   gtk_box_pack_start (GTK_BOX (vbox), ibox, FALSE, TRUE, 5);
   gtk_box_pack_start (GTK_BOX (ibox), parent_titlebarback, TRUE, TRUE, 0);
   gtk_container_set_border_width (GTK_CONTAINER (ibox), 0);
   
   ibox      	 = gtk_vbox_new (FALSE, 0);
   menutitleback = make_pixmap (PKGDATADIR "/black.xpm", 234, 22, NULL);
   gtk_box_pack_start (GTK_BOX (ibox),
		       gtk_label_new (_("Menutitle")),
		       FALSE, TRUE, 0);
   gtk_box_pack_start (GTK_BOX (vbox), ibox, FALSE, TRUE, 5);
   gtk_box_pack_start (GTK_BOX (ibox), menutitleback, TRUE, TRUE, 0);
   gtk_container_set_border_width (GTK_CONTAINER (ibox), 0);
   
   ibox     	= gtk_vbox_new (FALSE, 0);
   menuitemback = make_pixmap (PKGDATADIR "/black.xpm", 234, 22, NULL);
   gtk_box_pack_start (GTK_BOX (ibox),
		       gtk_label_new (_("Menuitem")),
		       FALSE, TRUE, 0);
   gtk_box_pack_start (GTK_BOX (vbox), ibox, FALSE, TRUE, 5);
   gtk_box_pack_start (GTK_BOX (ibox), menuitemback, TRUE, TRUE, 0);
   gtk_container_set_border_width (GTK_CONTAINER (ibox), 0);
   
   gtk_widget_show_all (frame);
   
   return frame;
}

static void
selection_made (GtkCTree *tree, GtkCTreeNode *selection,
		gint column, gpointer data)
/*
 *  Update preview after new tree item has been selected.
 *
 *  No return value.
 */
{
   themeinfo_t *info = gtk_ctree_node_get_row_data (tree, selection);

   if (!info)
      return;
   if (!get_selected_node (tree))
      return;
   if (info->istheme)
   {
      char *filename;

      filename = theme_image_filename (info->fullname, "WorkspaceBack", NO);
      if (filename)
      {
	 char *pname = preview_name (filename);
	 char *name  = get_pixmap_path (pname);

	 make_pixmap (name ? name : filename, 160, 120, workspaceback);
	 Free (pname);
	 Free (filename);
	 if (name)
	    Free (name);
      }
      else
	 theme_gradient (info->fullname, "WorkspaceBack", workspaceback);
      
      filename = theme_image_filename (info->fullname, "IconBack", NO);
      if (filename)
      {
	 char *pname = preview_name (filename);
	 char *name  = get_pixmap_path (pname);

	 make_pixmap (name ? name : filename, 64, 64, iconback);
	 Free (pname);
	 Free (filename);
	 if (name)
	    Free (name);
      }
      else
	 theme_gradient (info->fullname, "IconBack", iconback);
      
      filename = theme_image_filename (info->fullname, "FTitleBack", NO);
      if (filename)
      {
	 char *pname = preview_name (filename);
	 char *name  = get_pixmap_path (pname);

	 make_pixmap (name ? name : filename, 234, 22, titlebarback);
	 Free (pname);
	 Free (filename);
	 if (name)
	    Free (name);
      }
      else
	 theme_gradient (info->fullname, "FTitleBack", titlebarback);

      filename = theme_image_filename (info->fullname, "UTitleBack", NO);
      if (filename)
      {
	 char *pname = preview_name (filename);
	 char *name  = get_pixmap_path (pname);

	 make_pixmap (name ? name : filename, 234, 22, unfocus_titlebarback);
	 Free (pname);
	 Free (filename);
	 if (name)
	    Free (name);
      }
      else
	 theme_gradient (info->fullname, "UTitleBack", unfocus_titlebarback);

      filename = theme_image_filename (info->fullname, "PTitleBack", NO);
      if (filename)
      {
	 char *pname = preview_name (filename);
	 char *name  = get_pixmap_path (pname);

	 make_pixmap (name ? name : filename, 234, 22, parent_titlebarback);
	 Free (pname);
	 Free (filename);
	 if (name)
	    Free (name);
      }
      else
	 theme_gradient (info->fullname, "PTitleBack", parent_titlebarback);

      filename = theme_image_filename (info->fullname, "MenuTitleBack", NO);
      if (filename)
      {
	 char *pname = preview_name (filename);
	 char *name  = get_pixmap_path (pname);

	 make_pixmap (name ? name : filename, 234, 22, menutitleback);
	 Free (pname);
	 Free (filename);
	 if (name)
	    Free (name);
      }
      else
	 theme_gradient (info->fullname, "MenuTitleBack", menutitleback);

      filename = theme_image_filename (info->fullname, "MenuTextBack", NO);
      if (filename)
      {
	 char *pname = preview_name (filename);
	 char *name  = get_pixmap_path (pname);

	 make_pixmap (name ? name : filename, 234, 22, menuitemback);
	 Free (pname);
	 Free (filename);
	 if (name)
	    Free (name);
      }
      else
	 theme_gradient (info->fullname, "MenuTextBack", menuitemback);
   }
}

static proplist_t  cache_theme  = NULL;
static char	  *cache_name   = NULL;

static char *
theme_image_filename (const char *name, const char *attribute, bool_t combined)
{
   proplist_t theme;			/* theme file */

   /*
    *  Use cached theme file or replace cache with selected theme
    */
   if (cache_theme && streq (name, cache_name))
      theme = cache_theme;
   else
   {
      if (cache_name && streq (name, cache_name)) /* theme was invalid */
	 return NULL;
      theme = read_proplist (name);
      if (!theme)
      {
	 char *fname2 = g_strconcat (name, "/style", NULL);
	 
	 theme = read_proplist (fname2);
	 Free (fname2);
      }
      if (cache_theme)
	 WMReleasePropList (cache_theme);
      if (cache_name)
	 Free (cache_name);
      cache_name  = g_strdup (name);
      cache_theme = theme;
      if (!theme || !WMIsPLDictionary (theme)) /* invalid theme */
      {
	 if (theme)
	    WMReleasePropList (theme);
	 cache_theme = NULL;
	 
	 return NULL;
      }
   }

   /*
    *  Extract theme images
    */
   {
      proplist_t plattribute = WMCreatePLString ((char *) attribute);
      proplist_t value;
      char       *path = NULL;
      
      value = WMGetFromPLDictionary (theme, plattribute);
      if (value && WMIsPLArray (value) && WMGetPropListItemCount (value) > 2)
      {
	 proplist_t type   = WMGetFromPLArray (value, 0);
	 proplist_t file   = WMGetFromPLArray (value, 1);
	 char	    *ttype = WMGetFromPLString (type);
	       
	 if ((!combined && strcaseeq (ttype + 1, "pixmap"))
	     || (combined && strcaseeq (ttype + 2, "gradient")
		 && *ttype == 't'))
	 {
	    path = get_pixmap_path (WMGetFromPLString (file));
	    if (!path)
	    {
	       char *tmp = g_strconcat (name, "/", WMGetFromPLString (file),
					NULL);
	       path = get_pixmap_path (tmp);
	       Free (tmp);
	    }
	 }
      }
      WMReleasePropList (plattribute);
      return path;
   }
}

static void
theme_gradient (const char *name, const char *attribute, GtkWidget *preview)
{
   proplist_t theme;
   int	      width, height;
   
   width  = GTK_WIDGET (preview)->requisition.width;
   height = GTK_WIDGET (preview)->requisition.height;
   
   /*
    *  Use cached theme file or replace cache with selected theme
    */
   if (cache_theme && streq (name, cache_name))
      theme = cache_theme;
   else
   {
      if (cache_name && streq (name, cache_name))
      {
	 make_pixmap (PKGDATADIR "/black.xpm", width, height, preview);
	 return;
      }
      theme = read_proplist (name);
      if (!theme)
      {
	 char *fname2 = g_strconcat (name, "/style", NULL);
      
	 theme = read_proplist (fname2);
	 Free (fname2);
      }
      if (cache_theme)
	 WMReleasePropList (cache_theme);
      if (cache_name)
	 Free (cache_name);
      cache_name  = g_strdup (name);
      cache_theme = theme;
      if (!theme || !WMIsPLDictionary (theme))
      {
	 cache_theme = NULL;
	 if (theme)
	    WMReleasePropList (theme);
	 make_pixmap (PKGDATADIR "/black.xpm", width, height, preview);
	 return;
      }
   }
   
   /*
    *  Extract theme gradients
    */
   {
      proplist_t plattribute = WMCreatePLString ((char *) attribute);
      proplist_t value       = WMGetFromPLDictionary (theme, plattribute);
      bool_t	 done = NO;
      
#if defined(PREVIEWS) && !defined(CONVERT)
   
      if (value && WMIsPLArray (value) && WMGetPropListItemCount (value) > 1)
      {
	 proplist_t type   = WMGetFromPLArray (value, 0);
	 char	    *ttype = WMGetFromPLString (type);
	       
	 if (strcaseeq (ttype + 1, "gradient")
	     || strcaseeq (ttype + 2, "gradient"))
	 {
	    gtype_e  gtype;
	    unsigned offset = strcaseeq (ttype + 2, "gradient") ? 1 : 0;
	    
	    if (*(ttype + offset) == 'h')
	       gtype = HGRADIENT;
	    else if (*(ttype + offset) == 'v')
	       gtype = VGRADIENT;
	    else
	       gtype = DGRADIENT;
	    if (offset && *ttype == 't')
	       make_textured_gradient (theme_image_filename (name, attribute,
							     YES),
				       value, width, height, gtype, preview);
	    else
	       make_gradient (value, width, height, gtype, preview);
	    done = YES;
	 }
	 else if (strcaseeq (ttype, "solid"))
	 {
	    make_solid (WMGetFromPLString (WMGetFromPLArray (value, 1)),
			width, height, preview);
	    done = YES;
	 }
      }
#endif /* defined(PREVIEWS) && !defined(CONVERT) */

      WMReleasePropList (plattribute);
      if (!done)
	 make_pixmap (PKGDATADIR "/black.xpm", width, height, preview);
   }
}

/*****************************************************************************

			  load or set theme
  
*****************************************************************************/

#ifdef GETSTYLE

static void
load_theme (GtkWidget *button, gpointer ptr)
{
   GtkCTreeNode *selection = get_selected_node (tree);
   themeinfo_t 	*info;

   if (!selection)
      return;

   info = gtk_ctree_node_get_row_data (tree, selection);
   
   if (!load_attributes (info->fullname, NULL))	/* try new style theme */
   {
      char *path   = g_strdup (info->fullname);
      char *fname2 = g_strconcat (info->fullname, "/style", NULL);
      
      if (!load_attributes (fname2, path))
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("Can't open selected theme."));
      
      Free (path);
      Free (fname2);
   }
}

static void
set_theme (GtkWidget *button, gpointer ptr)
{
   load_theme (button, ptr);
   save_config_file (button, ptr);
}

#endif /* GETSTYLE */

/*****************************************************************************

			 theme preview dialog
  
*****************************************************************************/

#ifdef GETSTYLE
#	ifdef SETSTYLE

static void
preview_dialog (GtkWidget *button, gpointer ptr)
/*
 *  Preview selected theme and hide main window.
 */
{
   GList *selection = GTK_CLIST (tree)->selection;

   if (!GTK_CLIST (tree)->selection || g_list_length (selection) < 1)
      return;
   else
   {
      char *tmpthemename = get_temporary_file_name ();

      if (!getstyle_call (tmpthemename))
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("Can't write temporary theme file.\n"
			 "Please check `stderr' for more details."));
      else
      {
	 GList	  *previewlist = NULL;
	 unsigned  n;
	 
	 for (n = 0; n < g_list_length (selection); n++)
	 {
	    GtkCTreeNode *theme = g_list_nth_data (selection, n);
	    themeinfo_t  *info 	= gtk_ctree_node_get_row_data (tree, theme);

	    if (info->istheme)
	       previewlist = g_list_append (previewlist, info->fullname);
	 }
	 previewlist = g_list_append (previewlist, g_strdup (tmpthemename));
	 leave_preview (button, previewlist);
      }
   }
}

static void
leave_preview (GtkWidget *button, gpointer ptr)
/*
 *  Show new theme or restore old theme
 */
{
   GList *list       = (GList *) ptr;
   char  *name	     = list->data;
   char  *quotedname = protect_quotes (g_strdup (name));
   char  *cmdline    = g_strconcat (SETSTYLE, " \"", quotedname, "\"", NULL);
   char  *text;
   
   if (g_list_length (list) > 1)
      text = g_strdup_printf (_("Can't show preview of theme `%s'.\n"
				"Please check `stderr' for more details."),
			      name);
   else
      text = g_strdup_printf (_("Can't revert to old theme.\n"
				"Please check `stderr' for more details."));
   
   gtk_widget_hide (main_window);
   shell_command (cmdline, text);
   Free (text);
   Free (cmdline);
   Free (quotedname);
   
   if (g_list_length (list) == 1)	/* revert */
   {
      delete_file_or_dir (name);
      gtk_widget_show (main_window);
      Free (name);
      g_list_free (list);
   }
   else
   {
      list = g_list_remove (list, list->data);
      dialog (DIALOG_INFO,
	      g_list_length (list) > 1
	      ? _("Show next preview")
	      : _("Leave preview mode"), NULL, leave_preview, list,
	      _("Preview of `%s'"), g_basename (name));
   }
}

#	endif /* SETSTYLE */
#endif /* GETSTYLE */

/*****************************************************************************

			      save theme
  
*****************************************************************************/

#ifdef GETSTYLE

static void
save_theme_dialog (GtkWidget *widget, gpointer ptr)
/*
 *  Save theme dialog window
 */
{
   GtkCTreeNode *selection = get_selected_node (tree);
   themeinfo_t 	*info;
   char 	*dirname;
   
   if (!selection)
      return;

   info = gtk_ctree_node_get_row_data (tree, selection);

   if (!info->parent)
      dirname = info->fullname;
   else
   {
      themeinfo_t *pinfo = gtk_ctree_node_get_row_data (tree, info->parent);
      dirname 		 = pinfo->fullname;
   }
   
   {
      GtkWidget *window = gtk_dialog_new ();
      GtkWidget *entry  = gtk_entry_new ();
      GtkWidget *vbox   = gtk_vbox_new (FALSE, 0);

      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), vbox,
			  TRUE, TRUE, 0);
      gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
      
      gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_MOUSE);
      gtk_window_set_title (GTK_WINDOW (window), _("Save a theme"));
      gtk_signal_connect_object (GTK_OBJECT (window), "delete_event",
				 GTK_SIGNAL_FUNC (gtk_widget_destroy),
				 GTK_OBJECT (window));
      
      /*
       *  Theme name
       */
      {
	 GtkWidget *box   = gtk_vbox_new (FALSE, 0);
	 GtkWidget *frame = gtk_frame_new (_("Theme name"));
	 char      *text  = g_strdup_printf (_("Save current settings to new"
					       " theme in\n`%s':"), dirname);
	 
	 gtk_container_set_border_width (GTK_CONTAINER (box), 5);
	 gtk_frame_set_label_align (GTK_FRAME (frame), 0.5, 0.5);
	 gtk_container_set_border_width (GTK_CONTAINER (frame), 5);
	 gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0);
	 gtk_container_add (GTK_CONTAINER (frame), box);
	 gtk_box_pack_start (GTK_BOX (box), gtk_label_new (text),
			     FALSE, TRUE, 0);
	 Free (text);
	 gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 0);
      }
      
      /*
       *  Theme format
       */
#ifdef TAR
      {
	 GSList	   *group = NULL;
	 GtkWidget *fvbox = gtk_vbox_new (FALSE, 0);
	 GtkWidget *frame = gtk_frame_new (_("Output format"));
	 GtkWidget *button;

	 gtk_frame_set_label_align (GTK_FRAME (frame), 0.5, 0.5);
	 gtk_container_set_border_width (GTK_CONTAINER (frame), 5);
	 gtk_container_add (GTK_CONTAINER (frame), fvbox);
	 gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, TRUE, 0);
	 gtk_container_set_border_width (GTK_CONTAINER (fvbox), 5);

	 button = gtk_radio_button_new_with_label (group, _("Window Maker theme folder (.themed)"));
	 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
	 group  = gtk_radio_button_group (GTK_RADIO_BUTTON (button));
	 gtk_box_pack_start (GTK_BOX (fvbox), button, TRUE, TRUE, 0);
	 
#ifdef GZIP
	 button = gtk_radio_button_new_with_label (group, _("http://wm.themes.org theme archive (.tar.gz)"));
#else  /* not GZIP */
	 button = gtk_radio_button_new_with_label (group, _("http://wm.themes.org theme archive (.tar)"));
#endif /* not GZIP */
	 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
	 group  = gtk_radio_button_group (GTK_RADIO_BUTTON (button));
	 gtk_box_pack_start (GTK_BOX (fvbox), button, TRUE, TRUE, 0);
	 gtk_object_set_user_data (GTK_OBJECT (entry), button);
      }
#else  /* not TAR */      
      gtk_object_set_user_data (GTK_OBJECT (entry), NULL);
#endif /* not TAR */

      gtk_object_set_data (GTK_OBJECT (entry), "path", dirname);
      /*
       *  Buttons
       */
      {
	 GtkWidget *hbox  = gtk_hbutton_box_new ();
	 GtkWidget *button1, *button2;
	 
	 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), hbox,
			     TRUE, TRUE, 5);

	 gtk_button_box_set_spacing (GTK_BUTTON_BOX (hbox), 5);
	 gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox),
				    GTK_BUTTONBOX_END);
	 button1 = gtk_button_new_with_label (_("Save"));
	 gtk_box_pack_start (GTK_BOX (hbox), button1, TRUE, TRUE, 5);
	 gtk_widget_set_sensitive (button1, FALSE);
	 gtk_signal_connect (GTK_OBJECT (button1), "clicked",
			     GTK_SIGNAL_FUNC (save_theme_frontend), entry);
	 gtk_signal_connect_object (GTK_OBJECT (button1), "clicked",
				    GTK_SIGNAL_FUNC (gtk_widget_hide),
				    GTK_OBJECT (window));
	 GTK_WIDGET_SET_FLAGS (button1, GTK_CAN_DEFAULT);

	 gtk_signal_connect (GTK_OBJECT (entry), "changed",
			     GTK_SIGNAL_FUNC (set_themename), button1);
	 gtk_signal_connect (GTK_OBJECT (entry), "activate",
			     GTK_SIGNAL_FUNC (activate_themename), button1);
	 
	 button2 = gtk_button_new_with_label (_("Cancel"));
	 gtk_box_pack_start (GTK_BOX (hbox), button2, TRUE, TRUE, 5);
	 gtk_signal_connect_object (GTK_OBJECT (button2), "clicked",
				    GTK_SIGNAL_FUNC (gtk_widget_hide),
				    GTK_OBJECT (window));
	 GTK_WIDGET_SET_FLAGS (button2, GTK_CAN_DEFAULT);
	 gtk_object_set_user_data (GTK_OBJECT (button1), button2);
	 gtk_widget_grab_default (button2);
      }

      gtk_widget_show_all (window);
      gtk_widget_grab_focus (entry);
      gtk_entry_select_region (GTK_ENTRY (entry), 0, -1);
   }
}

static gint
hide_window (GtkWidget *widget, gpointer ptr)
/*
 *  Hide the specified window, don't destroy it.
 */
{
   gtk_widget_hide (widget);

   return TRUE;
}

static void
set_themename (GtkWidget *widget, gpointer ptr)
/*
 *  Toggle sensitivity of 'Save' button.
 */
{
   if (strlen (gtk_entry_get_text (GTK_ENTRY (widget))) > 0)
   {
      gtk_widget_set_sensitive (GTK_WIDGET (ptr), YES);
      gtk_widget_grab_default (GTK_WIDGET (ptr));
   }
   else
   {
      gtk_widget_set_sensitive (GTK_WIDGET (ptr), NO);
      gtk_widget_grab_default (GTK_WIDGET (gtk_object_get_user_data (GTK_OBJECT (ptr))));
   }
}

static void
activate_themename (GtkWidget *widget, gpointer ptr)
{
   if (strlen (gtk_entry_get_text (GTK_ENTRY (widget))) > 0)
      gtk_signal_emit_by_name (GTK_OBJECT (ptr), "clicked");
   else
      gtk_signal_emit_by_name (GTK_OBJECT (gtk_object_get_user_data (GTK_OBJECT (ptr))), "clicked");
}

static void
save_theme_frontend (GtkWidget *widget, gpointer ptr)
/*
 *  Checks whether the theme file or directory already exists.
 */
{
   GtkEntry   *name 	= GTK_ENTRY (ptr);
   GtkWidget  *button 	= gtk_object_get_user_data (GTK_OBJECT (ptr));
   const char *themename = gtk_entry_get_text (name);
   const char *pathname  = gtk_object_get_data (GTK_OBJECT (ptr), "path");

#ifdef TAR
   if (button && GTK_TOGGLE_BUTTON (button)->active)
   {
      char   *filename1;
      bool_t  exists1 = NO;
      char   *filename2;
      bool_t  exists2 = NO;
      
      filename1 = g_strconcat (pathname, "/", themename, ".tar", NULL);
      exists1 	= file_exists (filename1);
#ifdef GZIP
      filename2 = g_strconcat (pathname, "/", themename, ".tar.gz", NULL);
      exists2 	= file_exists (filename2);
#endif /* GZIP */
      
      if (exists1 && exists2)
      {
	 dialog_popup (DIALOG_QUESTION, save_theme_backend, ptr,
		       _("Overwrite existing files\n`%s'\n`%s'"),
		       filename1, filename2);
	 Free (filename1);
	 Free (filename2);
	 return;
      }
      else if (exists1)
      {
	 dialog_popup (DIALOG_QUESTION, save_theme_backend, ptr,
		       _("Overwrite existing file\n`%s'"),
		       filename1);
	 Free (filename1);
#ifdef GZIP
	 Free (filename2);
#endif /* GZIP */
	 return;
      }
      else if (exists2)
      {
	 dialog_popup (DIALOG_QUESTION, save_theme_backend, ptr,
		       _("Overwrite existing file\n`%s'"),
		       filename2);
	 Free (filename1);
	 Free (filename2);
	 return;
      }
      Free (filename1);
#ifdef GZIP
      Free (filename2);
#endif /* GZIP */
   }
   else
#endif
   {
      char *filename = g_strconcat (pathname, "/", themename, ".themed", NULL);
      if (file_exists (filename))
      {
	 dialog_popup (DIALOG_QUESTION, save_theme_backend, ptr,
		       _("Overwrite existing theme\n`%s'"),
		       filename);
	 Free (filename);
	 return;
      }
      Free (filename);
   }

   save_theme_backend (widget, ptr);
}

static void
save_theme_backend (GtkWidget *widget, gpointer ptr)
/*
 *  Actually writes a theme to disk.
 *  Two formats are supported:
 *  Either a .tar.gz file or a .themed directory are written.
 */
{
   GtkEntry   *name 	= GTK_ENTRY (ptr);
   char	      *pathname  = expand_tilde (gtk_object_get_data (GTK_OBJECT (ptr),
							     "path"));
   GtkWidget  *button 	= gtk_object_get_user_data (GTK_OBJECT (name));
   const char *themename = gtk_entry_get_text (name);
   bool_t      archive 	= FALSE;
   char       *tmpdir 	= NULL;
   char	      *filename;
   GList      *oldfiles = NULL;
   
#ifdef TAR
   archive = button && GTK_TOGGLE_BUTTON (button)->active;
   /*
    *  If archive is set, then generate temporary theme
    *  /tmpfile/Themes/'themename'.themed
    */
   if (archive)
   {
      char *subdir;
      
      tmpdir = make_temporary_directory ();

      subdir = g_strconcat (tmpdir, "/Themes", NULL);
      if (make_directory (subdir))	/* create temporary Themes directory */
      {
	 Free (subdir);
	 return;
      }
      Free (subdir);

      filename = g_strconcat (tmpdir, "/Themes/", themename, ".themed", NULL);

      if (make_directory (filename))	/* create new theme directory */
      {
	 Free (filename);
	 return;
      }
   }
   else
#endif
   {
      filename = g_strconcat (pathname, "/", themename, ".themed", NULL);
      if (file_exists (filename))
	 oldfiles = get_theme_files (filename);
      else
	 if (make_directory (filename))	/* create new theme directory */
	 {
	    Free (filename);
	    return;
	 }
   }

   /*
    *  Write style file to directory 'themename'.themed
    *  Copy all image files to this directory
    */
   {
      unsigned	  n;
      proplist_t pltype     = WMCreatePLString ("Type");
      char	 *stylename = g_strconcat (filename, "/style", NULL);
      proplist_t theme      = WMCreatePLDictionary (NULL, NULL, NULL);
      
      oldfiles = remove_file_from_list (oldfiles, stylename);
      for (n = 0; n < WMGetPropListItemCount (theme_keys); n++)
      {
	 proplist_t key   = WMGetFromPLArray (theme_keys, n);
	 proplist_t entry = WMGetFromPLDictionary (wmconfig, key);

	 if (entry && WMIsPLDictionary (entry))
	 {
	    proplist_t value = WMGetFromPLDictionary (windowmaker, key);
	    proplist_t type  = WMGetFromPLDictionary (entry, pltype);

	    if (strcaseeq (WMGetFromPLString (type), "Texture") && value
		&& WMIsPLArray (value) && WMGetPropListItemCount (value) >= 2)
	    {
	       char *ttype = WMGetFromPLString (WMGetFromPLArray (value, 0));
		  
	       if (strcaseeq (ttype + 1, "pixmap")
		   || (strcaseeq (ttype + 2, "gradient") && *ttype == 't'))
	       {
		  char *name;
		  char *path;

		  name = g_strdup (WMGetFromPLString (WMGetFromPLArray (value, 1)));
		  path = get_pixmap_path (name);

		  if (path)
		  {
		     if (copy_file (filename, path))
		     {
			Free (filename);
			Free (path);
			WMReleasePropList (pltype);
			WMReleasePropList (theme);
			Free (name);
			Free (stylename);

			return;
		     }
		     else
		     {
			char	   *file;
			proplist_t plfile;

			oldfiles = remove_file_from_list (oldfiles, path);
			value 	 = WMDeepCopyPropList (value);
			file 	 = strrchr (path, '/');
			if (!file)
			   plfile = WMRetainPropList (WMCreatePLString (path));
			else
			   plfile = WMRetainPropList (WMCreatePLString (file + 1));

			WMDeleteFromPLArray (value, 1);
			WMInsertInPLArray (value, 1, plfile);
			Free (path);
		     }
		  }
		  else
		  {
		     dialog_popup (DIALOG_ERROR, NULL, NULL,
				   _("Can't open input file\n'%s'"), name);
		     Free (filename);
		     WMReleasePropList (pltype);
		     WMReleasePropList (theme);
		     Free (name);
		     Free (stylename);

		     return;
		  }
		  Free (name);
	       }
	    }
	    WMPutInPLDictionary (theme, key, value);
	 }
      }
      while (oldfiles)
      {
	 char *tmp = oldfiles->data;
	 
	 delete_file_or_dir (tmp);
	 oldfiles = g_list_remove (oldfiles, tmp);
	 Free (tmp);
      }
      if (!WMWritePropListToFile (theme, stylename, YES))
      {
	 dialog_popup (DIALOG_ERROR, NULL, NULL,
		       _("Can't save theme file\n`%s'\n"
			 "Please check `stderr' for more details."),
		       stylename);
	 Free (filename);
	 WMReleasePropList (pltype);
	 WMReleasePropList (theme);
	 Free (stylename);

	 return;
      }
      WMReleasePropList (theme);
      WMReleasePropList (pltype);
      Free (stylename);
   }
   
#ifdef TAR
   if (archive)
   {
      char *cdname = g_strdup (filename);
      char *tmp;
      char *cmd;
      char *tarname;
      char *quotedtar;
      char *quotedcd;
      
      tmp = strrchr (cdname, '/');
      *tmp = 0;
      tmp = strrchr (cdname, '/');
      *tmp = 0;

      tarname 	= g_strconcat (pathname, "/", themename, ".tar", NULL);
      quotedtar = protect_quotes (g_strdup (tarname));
      quotedcd  = protect_quotes (g_strdup (cdname));
      cmd 	= g_strconcat ("cd ", " \"", quotedcd, "\" && ", TAR, 
			       " -c -f \"", quotedtar,"\" * ", NULL);
      if (shell_command (cmd, NULL))		/* create tar archive */
      {
	 remove_directory (tmpdir);
	 Free (tarname);
	 Free (quotedtar);
	 Free (cdname);
	 Free (quotedcd);
	 Free (filename);
	 return;
      }

      if (remove_directory (tmpdir))	/* delete tmp directory */
      {
	 Free (tarname);
	 Free (quotedtar);
	 Free (cdname);
	 Free (quotedcd);
	 Free (filename);
	 return;
      }
      
#ifdef GZIP
      {
	 char *text = g_strdup_printf (_("Can't pack theme file\n`%s'"),
				       tarname);

	 cmd = g_strconcat (GZIP, " -f \"", quotedtar,"\"", NULL);
	 if (shell_command (cmd, text))	/* compress archive */
	 {
	    Free (tarname);
	    Free (quotedtar);
	    Free (cdname);
	    Free (quotedcd);
	    Free (filename);
	    Free (text);
	    return;
	 }
	 Free (text);
      }
#endif /* not GZIP */
      
      Free (tarname);
      Free (quotedtar);
      Free (cdname);
      Free (quotedcd);
   }
#endif /* TAR */
   Free (pathname);
   Free (filename);
   build_theme_list ();
}

static GList *
get_theme_files (const char *themename)
/*
 *  Generate list of files that are part of the given theme
 */
{
   GList *list 	   = NULL;
   DIR   *themedir = opendir (themename);

   if (themedir)
   {
      struct dirent *file;
		  
      while ((file = readdir (themedir)))
      {
	 if (!streq (file->d_name, ".") && !streq (file->d_name, ".."))
	    list = g_list_append (list, g_strconcat (themename, "/",
						     file->d_name, NULL));
      }
      closedir (themedir);
   }

   return list;
}

static GList *
remove_file_from_list (GList *list, const char *file)
/*
 *  Remove list element 'file' from 'list'.
 */
{
   if (list && g_list_length (list) > 0)
   {
      unsigned n;

      for (n = 0; n < g_list_length (list); n++)
      {
	 char *tmp = g_list_nth_data (list, n) ;

	 if (streq (tmp, file))
	 {
	    list = g_list_remove (list, tmp);
	    Free (tmp);
	    break;
	 }
      }
   }
   return list;
}

void
get_theme_attributes (void)
/*
 *  Generate list of attributes that define a Window Maker theme
 *
 *  Side effects:
 *	fills global property array of attributes: 'theme_keys'	
 */
{
   char		*tmpthemename  = get_temporary_file_name ();
   proplist_t	theme;

   if (!getstyle_call (tmpthemename))
   {
      dialog_popup (DIALOG_ERROR, NULL, NULL,
		    _("Can't write temporary theme file.\n"
		      "Please check `stderr' for more details."));
      return;
   }
   theme = read_proplist (tmpthemename);
   if (!theme)
   {
      dialog_popup (DIALOG_ERROR, NULL, NULL, 
		    _("Can't open temporary theme file.\n"
		      "Please check `stderr' for more details."));
      delete_file_or_dir (tmpthemename);
      return;
   }
   theme_keys = WMGetPLDictionaryKeys (theme);
   WMReleasePropList (theme);
   delete_file_or_dir (tmpthemename);
}

void
cleanup_themes (void)
{
   WMReleasePropList (theme_keys);
}

static void
free_memory (gpointer data, gpointer user_data)
{
   Free (data);
}

static bool_t
getstyle_call (const char *themename)
/*
 *  Start wmaker's getstyle program
 */
{
   char *quotedname = protect_quotes (g_strdup (themename));
   char	*cmdline    = g_strconcat (GETSTYLE, " -t \"", quotedname, "\"", NULL);
   bool_t success   = !system (cmdline);

   Free (cmdline);
   Free (quotedname);
   
   return success;
}

#endif /* GETSTYLE */

/*****************************************************************************

			  theme installation
  
*****************************************************************************/

static void
install_theme_dialog (GtkWidget *widget, gpointer ptr)
/*
 *  Install theme dialog window
 */
{
   GtkWidget 	*install_button;
   GtkWidget 	*filelist;
   GtkWidget 	*window;
   GtkCTreeNode *selection = get_selected_node (tree);
   themeinfo_t 	*info;
   char 	*dirname;
   
   if (!selection)
      return;

   info = gtk_ctree_node_get_row_data (tree, selection);

   if (!info->parent)
      dirname = info->fullname;
   else
   {
      themeinfo_t *pinfo = gtk_ctree_node_get_row_data (tree, info->parent);
      dirname 		 = pinfo->fullname;
   }
#if 0
   if (strlen (dirname) > strlen ("/Themes")
       && streq (dirname + strlen (dirname) - strlen ("/Themes"), "/Themes"))
      *(dirname + strlen (dirname) - strlen ("/Themes")) = 0;
#endif
   {
      GtkBox 	*vbox;
      GtkWidget *box = gtk_vbox_new (FALSE, 0);

      gtk_container_set_border_width (GTK_CONTAINER (box), 5);
      
      filelist = gtk_clist_new (1);
      window   = gtk_dialog_new ();
      vbox     = GTK_BOX (GTK_DIALOG (window)->vbox);

      gtk_object_set_data (GTK_OBJECT (filelist), "path", dirname);
      
      gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_MOUSE);
      gtk_object_set_user_data (GTK_OBJECT (window), filelist);
      
      gtk_window_set_title (GTK_WINDOW (window), _("Install themes"));
      gtk_signal_connect_object (GTK_OBJECT (window), "delete_event",
				 GTK_SIGNAL_FUNC (gtk_widget_destroy),
				 GTK_OBJECT (window));

      gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, TRUE, 0);
      {
	 char *text = g_strdup_printf (_("Install themes in\n`%s':"), dirname);

	 gtk_box_pack_start (GTK_BOX (box), gtk_label_new (text),
			     FALSE, TRUE, 0);
	 Free (text);
      }

      /*
       *  List of themes
       */
      {
	 GtkWidget *hbox   = gtk_vbox_new (FALSE, 0);
	 GtkWidget *button = gtk_button_new_with_label (_("Browse..."));
	 GtkWidget *bbox   = gtk_hbutton_box_new ();
	 GtkWidget *frame  = gtk_frame_new (_("List of themes"));

	 gtk_frame_set_label_align (GTK_FRAME (frame), 0.5, 0.5);
	 gtk_container_set_border_width (GTK_CONTAINER (frame), 5);
	 gtk_container_add (GTK_CONTAINER (frame), hbox);
	 gtk_box_pack_start (vbox, frame, FALSE, TRUE, 0);

	 gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);

	 init_dnd (filelist);
	 
	 gtk_clist_set_column_auto_resize (GTK_CLIST (filelist), 0, YES);
	 gtk_clist_column_titles_passive (GTK_CLIST (filelist));
	 gtk_clist_set_selection_mode (GTK_CLIST (filelist),
				       GTK_SELECTION_EXTENDED);
	 {
	    GtkWidget *scrolled = gtk_scrolled_window_new (NULL, NULL);
	    
	    gtk_container_set_border_width (GTK_CONTAINER (scrolled), 5);
	    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
					    GTK_POLICY_AUTOMATIC,
					    GTK_POLICY_AUTOMATIC);
	    gtk_container_add (GTK_CONTAINER (scrolled), filelist);
	    gtk_box_pack_start (GTK_BOX (hbox), scrolled, TRUE, TRUE, 0);
	    gtk_widget_set_usize (scrolled, 550, 150);
	 }

	 gtk_box_pack_start (GTK_BOX (hbox), bbox, FALSE, TRUE, 5);
	 gtk_box_pack_start (GTK_BOX (bbox), button, FALSE, TRUE, 5);
	 gtk_signal_connect (GTK_OBJECT (button), "clicked",
			     GTK_SIGNAL_FUNC (open_theme_browser), filelist);

	 gtk_tooltips_set_tip (tooltips, button,
			       _("Select themes with a file selection dialog "
				 "(or use drag and drop with your "
				 "file manager and WWW browser)."), NULL);
      }
      
      /*
       *  Buttons
       */
      {
	 GtkWidget *hbox  = gtk_hbutton_box_new ();
	 GtkWidget *button2;
	 
	 gtk_button_box_set_spacing (GTK_BUTTON_BOX (hbox), 5);
	 gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox),
				    GTK_BUTTONBOX_END);
	 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), hbox,
			     FALSE, TRUE, 5);
	 install_button = gtk_button_new_with_label (_("Install"));
	 gtk_widget_set_sensitive (install_button, FALSE);
	 gtk_box_pack_start (GTK_BOX (hbox), install_button, FALSE, TRUE, 5);
	 gtk_signal_connect (GTK_OBJECT (install_button), "clicked",
			     GTK_SIGNAL_FUNC (install_themes), filelist);
	 gtk_signal_connect_object (GTK_OBJECT (install_button), "clicked",
				    GTK_SIGNAL_FUNC (gtk_widget_destroy),
				    GTK_OBJECT (window));
	 gtk_signal_connect (GTK_OBJECT (filelist), "select_row",
			     GTK_SIGNAL_FUNC (update_selection),
			     install_button);
	 gtk_signal_connect (GTK_OBJECT (filelist), "unselect_row",
			     GTK_SIGNAL_FUNC (update_selection),
			     install_button);
	 GTK_WIDGET_SET_FLAGS (install_button, GTK_CAN_DEFAULT);

	 button2 = gtk_button_new_with_label (_("Cancel"));
	 gtk_box_pack_start (GTK_BOX (hbox), button2, FALSE, TRUE, 5);
	 gtk_signal_connect_object (GTK_OBJECT (button2), "clicked",
				    GTK_SIGNAL_FUNC (gtk_widget_destroy),
				    GTK_OBJECT (window));
	 GTK_WIDGET_SET_FLAGS (button2, GTK_CAN_DEFAULT);
	 gtk_widget_grab_default (button2);
	 gtk_object_set_user_data (GTK_OBJECT (install_button), button2);
      }
   }

   if (g_list_length (GTK_CLIST(filelist)->selection) > 0)
   {
      gtk_widget_set_sensitive (GTK_WIDGET (install_button), TRUE);
      gtk_widget_grab_default (GTK_WIDGET (install_button));
   }
   else
   {
      gtk_widget_set_sensitive (GTK_WIDGET (install_button), FALSE);
      gtk_widget_grab_default (GTK_WIDGET (gtk_object_get_user_data (GTK_OBJECT (install_button))));
   }

   gtk_widget_show_all (window);

   if (ptr)
      insert_dnd_elements (GTK_CLIST (filelist), ptr);
}

static void
open_theme_browser (GtkWidget *button, gpointer data)
/*
 *  Open file browser to select themes. 
 */
{
   static GtkWidget *filesel = NULL; 

   if (!filesel)
   {
      GtkFileSelection *fs;
      GtkWidget	       *clist = (GtkWidget *) data;

      filesel = gtk_file_selection_new (_("Choose themes to install"));
      fs      = GTK_FILE_SELECTION (filesel);
      gtk_file_selection_set_select_multiple (fs, TRUE);

      gtk_file_selection_hide_fileop_buttons (fs);
      gtk_window_set_position (GTK_WINDOW (fs), GTK_WIN_POS_MOUSE);
      gtk_signal_connect_object (GTK_OBJECT (fs), "destroy",
				 GTK_SIGNAL_FUNC (hide_window),
				 GTK_OBJECT (filesel));
      gtk_signal_connect (GTK_OBJECT (fs->ok_button), "clicked",
			  GTK_SIGNAL_FUNC (insert_theme_files), clist);
      gtk_object_set_user_data (GTK_OBJECT (fs->ok_button), filesel);

      gtk_signal_connect_object (GTK_OBJECT (fs->cancel_button), "clicked",
				 GTK_SIGNAL_FUNC (hide_window),
				 GTK_OBJECT (filesel));
      gtk_signal_connect_object (GTK_OBJECT (fs->ok_button), "clicked",
				 GTK_SIGNAL_FUNC (hide_window),
				 GTK_OBJECT (filesel));
      gtk_file_selection_set_filename (fs, "~/");
   }
   if (GTK_WIDGET_VISIBLE (filesel))
      gtk_widget_hide (filesel);
   else
      gtk_widget_show_all (filesel);
}

static void
install_themes (GtkWidget *widget, gpointer ptr)
/*
 *  Install selected list of themes
 */
{
   GtkWidget *vbox;
   GtkWidget *progress_window;
   GtkWidget *progress_label;
   GtkWidget *progress_bar;
   GtkCList  *install_list = GTK_CLIST (ptr);
   GList     *files        = install_list->selection;
   unsigned   n;
   char	     *themedir  = gtk_object_get_data (GTK_OBJECT (ptr), "path");
   char      *qthemedir;
   gint	      timer;
   pid_t      pid;
   
   gtk_widget_hide (gtk_widget_get_toplevel (widget));
   if (strlen (themedir) == 0)
   {
      dialog_popup (DIALOG_ERROR, NULL, NULL,
		    _("No installation prefix given."));
      return;
   }
   themedir  = expand_tilde (themedir);
   qthemedir = protect_quotes (g_strdup (themedir));
   
   /*
    *  Generate a progressbar and window
    */
   progress_window = gtk_dialog_new ();
   gtk_window_set_position (GTK_WINDOW (progress_window), GTK_WIN_POS_MOUSE);
   gtk_window_set_title (GTK_WINDOW (progress_window), _("Install themes"));
   gtk_widget_set_usize (progress_window, 250, -1);
   
   vbox = gtk_vbox_new (FALSE, 5);
   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (progress_window)->vbox),
		       vbox, TRUE, TRUE, 0);
   gtk_container_set_border_width (GTK_CONTAINER (vbox), 10);
   progress_label = gtk_label_new ("");
   gtk_box_pack_start (GTK_BOX (vbox), progress_label, FALSE, TRUE, 0);

   {
      GtkAdjustment *adj;
	 
      adj = (GtkAdjustment *) gtk_adjustment_new (0, 1, 300, 0, 0, 0);
	 
      progress_bar = gtk_progress_bar_new_with_adjustment (adj);
      gtk_progress_set_activity_mode (GTK_PROGRESS (progress_bar), TRUE);
      gtk_progress_bar_set_activity_step (GTK_PROGRESS_BAR (progress_bar), 5);
      gtk_progress_bar_set_activity_blocks (GTK_PROGRESS_BAR (progress_bar), 4);
      
      timer = gtk_timeout_add (100, progress_timer, progress_bar);
   }

   gtk_box_pack_start (GTK_BOX (vbox), progress_bar, FALSE, TRUE, 5);
   {
      GtkWidget *bbox   = gtk_hbutton_box_new ();
      GtkWidget *cancel = gtk_button_new_with_label (_("Skip"));
      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (progress_window)->action_area),
			  bbox, FALSE, TRUE, 5);
      gtk_box_pack_start (GTK_BOX (bbox), cancel, FALSE, TRUE, 0);
      gtk_signal_connect (GTK_OBJECT (cancel), "clicked",
			  GTK_SIGNAL_FUNC (cancel_installation), &pid);
      gtk_signal_connect (GTK_OBJECT (progress_window), "delete_event",
			  GTK_SIGNAL_FUNC (delete_button), cancel);
   }
      
   gtk_widget_show_all (progress_window);
      
   for (n = 0; n < g_list_length (files); n++)
   {
      char *orig_filename      = NULL;
      char *install_filename   = NULL;
      char *qinstall_filename  = NULL;
      char *unpacked_filename  = NULL;
      char *qunpacked_filename = NULL;
      char *tmp_filename       = NULL;
      
      gtk_clist_get_text (install_list,
			  GPOINTER_TO_INT (g_list_nth_data (files, n)),
			  0, &orig_filename);
      {
	 char *basename = strrchr (orig_filename, '=');

	 if (!basename)
	    basename = strrchr (orig_filename, '/');
	 gtk_label_set_text (GTK_LABEL (progress_label),
			     basename ? basename + 1: orig_filename);
	 gtk_label_set_text (GTK_LABEL (progress_label),
			     basename ? basename + 1: orig_filename);
      }

      while (gtk_events_pending())
	 gtk_main_iteration();

      /*
       *  Get theme via WWW
       */
      if (!strneq (orig_filename, "file:/", strlen ("file:/")))
#ifdef WWWREQUEST      
      {
	 char *tmpthemename = get_temporary_file_name ();

	 pid = fork ();
	 if (pid)			/* parent */
	 {
	    int status;

	    while (waitpid (pid, &status, WNOHANG) == 0)
	       gtk_main_iteration ();
	    if (WIFSIGNALED (status))
	       continue;
	 }
	 else				/* child */
	 {
	    execl (PERL, PERL, PKGDATADIR "/getfile.pl",
		   orig_filename, tmpthemename, NULL);
#ifdef HAVE__EXIT
	    _exit (1);
#else  /* HAVE__EXIT */
	    exit (1);
#endif /* HAVE__EXIT */
	 }
	 tmp_filename = install_filename = g_strdup (tmpthemename);
      }
#else  /* not WWWREQUEST */
      continue;			/* skip this file */
#endif /* not WWWREQUEST */
      else
	 install_filename = g_strdup (orig_filename + strlen ("file:"));

      /*
       *  Now lets install the new theme file
       */
      
      qinstall_filename = protect_quotes (g_strdup (install_filename));

      {
	 bool_t	gzip = NO;

#ifdef GUNZIP
	 {
	    char *tmp = g_strdup_printf ("%s -t \"%s\"",
					 GUNZIP, qinstall_filename);
	    if (!system (tmp))
	    {
	       Free (tmp);
	       gzip = YES;
	       qunpacked_filename = g_strconcat (qinstall_filename,
						 ".tar", NULL);
	       tmp = g_strdup_printf ("%s -c < \"%s\" > \"%s\"", GUNZIP,
				      qinstall_filename, qunpacked_filename);
	       if (system (tmp))	/* no success ? */
	       {
		  dialog_popup (DIALOG_ERROR, NULL, NULL,
				_("Can't unpack GNU zipped theme file\n`%s'\n"
				  "to destiation file\n`%s'\n"
				  "Please check `stderr' for more details."),
				qinstall_filename, qunpacked_filename);
		  Free (qunpacked_filename);
		  Free (tmp);
		  continue;
	       }
	       unpacked_filename = g_strconcat (install_filename,
						".tar", NULL);
	    }
	    Free (tmp);
	 }
#endif /* GUNZIP */

#ifdef BUNZIP2
	 if (!gzip)
	 {
	    char *tmp = g_strdup_printf ("%s -t \"%s\"",
					 BUNZIP2, qinstall_filename);
	    if (!system (tmp))
	    {
	       Free (tmp);
	       qunpacked_filename = g_strconcat (qinstall_filename,
						 ".tar", NULL);
	       tmp = g_strdup_printf ("%s < \"%s\" > \"%s\"", BUNZIP2,
				      qinstall_filename, qunpacked_filename);
	       if (system (tmp))	/* no success ? */
	       {
		  dialog_popup (DIALOG_ERROR, NULL, NULL,
				_("Can't unpack BZIP2 compressed theme file\n"
				  "`%s'\nto destiation file\n`%s'\n"
				  "Please check `stderr' for more details."),
				qinstall_filename, qunpacked_filename);
		  Free (qunpacked_filename);
		  Free (tmp);
		  continue;
	       }
	       unpacked_filename = g_strconcat (install_filename,
						".tar", NULL);
	    }
	    Free (tmp);
	 }
#endif /* BUNZIP2 */
	 
	 if (unpacked_filename)		/* unpacked file has been created ? */
	 {
	    if (tmp_filename)
	       delete_file_or_dir (tmp_filename);
	    tmp_filename = unpacked_filename;
	 }
	 else				/* still the same */
	 {
	    unpacked_filename  = g_strdup (install_filename); 
	    qunpacked_filename = g_strdup (qinstall_filename); 
	 }

	 {
#ifdef TAR
	    char *tmp = g_strdup_printf ("%s -t -f \"%s\"",
					 TAR, qunpacked_filename);
	    if (!system (tmp))
	    {
	       char *text = g_strdup_printf (_("Can't unpack theme\n`%s'"),
					     unpacked_filename);
	       Free (tmp);
	       tmp = g_strdup_printf ("cd \"%s\"; %s -x -f \"%s\"",
				      qthemedir, TAR, qunpacked_filename);
	       if (shell_command (tmp, text))
	       {
		  Free (tmp);
		  Free (text);
		  continue;
	       }
	       Free (text);
	       Free (tmp);
	    }
	    else
#endif /* TAR */
	    {
	       if (copy_file (themedir, unpacked_filename))
	       {
		  continue;
	       }
	    }
	 }
	 if (tmp_filename)
	    delete_file_or_dir (tmp_filename);
	 Free (unpacked_filename);
	 Free (install_filename);
	 Free (qunpacked_filename);
	 Free (qinstall_filename);
      }
   }
   gtk_timeout_remove (timer);
   gtk_widget_destroy (progress_window);
   build_theme_list ();
   Free (themedir);
   Free (qthemedir);
   gtk_clist_clear (GTK_CLIST (install_list));
}

static void
cancel_installation (GtkWidget *widget, gpointer ptr)
{
   pid_t pid = * (pid_t *) ptr;

   kill (pid, 15);
}

static gint
insert_theme_files (GtkWidget *widget, gpointer ptr)
{
   GtkFileSelection	*fs;
   GtkCList             *themelist;
   gchar	       **list;
   int                   i;

   fs 	= GTK_FILE_SELECTION (gtk_object_get_user_data (GTK_OBJECT (widget)));
   list = gtk_file_selection_get_selections (fs);
   
   themelist = GTK_CLIST (ptr);
   for (i = 0; list[i]; i++)
   {
      char       *tmp  = g_strconcat ("file:", list[i], NULL);

      gtk_clist_insert (themelist, -1, &tmp);
      gtk_clist_select_row (themelist, themelist->rows - 1, 0);
      Free (tmp);
   }
   g_strfreev (list);
   
   return TRUE;
}

static void
update_selection (GtkCList *themelist, gint row, gint column, GdkEvent *event,
		  gpointer data)
{
   if (g_list_length (themelist->selection) > 0)
   {
      gtk_widget_set_sensitive (GTK_WIDGET (data), TRUE);
      gtk_widget_grab_default (GTK_WIDGET (data));
   }
   else
   {
      gtk_widget_set_sensitive (GTK_WIDGET (data), FALSE);
      gtk_widget_grab_default (GTK_WIDGET (gtk_object_get_user_data (GTK_OBJECT (data))));
   }
}

enum {
   TARGET_STRING,
   TARGET_ROOTWIN,
   TARGET_URL
};

static GtkTargetEntry target_table[] = {
   { "STRING",     0, TARGET_STRING },
   { "text/plain", 0, TARGET_STRING },
   { "text/uri-list", 0, TARGET_URL }
};

static guint n_targets = sizeof (target_table) / sizeof (target_table [0]);

static void
init_dnd (GtkWidget *widget)
{
   gtk_drag_dest_set (widget, GTK_DEST_DEFAULT_ALL, target_table, n_targets, 
		      GDK_ACTION_COPY | GDK_ACTION_MOVE);
   gtk_signal_connect (GTK_OBJECT (widget), "drag_data_received",
		       GTK_SIGNAL_FUNC (drag_data_received), NULL);
}

static void  
drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
		    GtkSelectionData *data, guint info, guint time)
{
   if (data->length >= 0 && data->format == 8)
   {
      char *text = (char *) data->data;

      if (widget == GTK_WIDGET (tree))
	 install_theme_dialog (widget, text);
      else
	 insert_dnd_elements (GTK_CLIST (widget), text);
      gtk_drag_finish (context, TRUE, FALSE, time);
   }
   else
      gtk_drag_finish (context, FALSE, FALSE, time);
}

static void
insert_dnd_elements (GtkCList *clist, const char *start)
{
   while (strneq (start, "http://", strlen ("http://"))
	  || strneq (start, "ftp://", strlen ("ftp://"))
	  || strneq (start, "file:/", strlen ("file:/")))
   {
      char *end;
	 
      if ((end = strstr (start + 1, "file:/")) ||
	  (end = strstr (start + 1, "http://")) ||
	  (end = strstr (start + 1, "ftp://")))
      {
	 char		*tmp;
	 const char	*last = end;
	    
	 while (isspace (*--last))
	    ;
	 tmp = g_strndup (start, last - start + 1);
	 gtk_clist_insert (clist, -1, &tmp);
	 Free (tmp);
	 start = end;
      }
      else
      {
	 char		*tmp;
	 const char	*last = start + strlen (start);
	    
	 while (isspace (*--last))
	    ;
	 tmp = g_strndup (start, last - start + 1);
	 gtk_clist_insert (clist, -1, &tmp);
	 Free (tmp);
	 start++;
      }
      gtk_clist_select_row (clist, clist->rows - 1, 0);
   }
}

/*****************************************************************************

		      theme preview computation
  
*****************************************************************************/

static void
compute_preview (GtkWidget *progress_bar, GtkWidget *progress_label,
		 unsigned n, unsigned nelem,
		 const char *name, unsigned width, unsigned height)
{
   char *pname;
   char *path;
   char *filename;
   
   if (!name)
      return;
   else
      filename = g_strdup (name);
   
   pname = preview_name (name);
   path  = get_pixmap_path (pname);

   gtk_progress_bar_update (GTK_PROGRESS_BAR (progress_bar), n / (double) nelem);
   gtk_label_set_text (GTK_LABEL (progress_label), g_basename (name));
   while (gtk_events_pending ())
      gtk_main_iteration ();

   if (!path)
   {
      DIR *dir = opendir (g_dirname (pname));

      if (!dir)				/* Make ~/.wmakerconf directory */
      {
	 if (make_directory (g_dirname (pname)))
	 {
	    Free (pname);
	    return;
	 }
      }
      else
	 closedir (dir);
      
      filename = protect_quotes (filename);
      pname    = protect_quotes (pname);
      {
#ifdef CONVERT
	 char *cmd = g_strdup_printf ("%s -comment \"\" -geometry %dx%d "
				      "\"%s\" ppm:\"%s\"", CONVERT, width, height,
				      filename, pname);
#else  /* not CONVERT */
	 char *cmd = g_strdup_printf ("%s \"%s\" \"%s\" %d %d",
				      BINDIR "/mkpreview",
				      filename, pname, width, height);
#endif /* not CONVERT */
	 if (system (cmd))		/* Write failure */
	 {
	    warning (_("Can't generate preview `%s'.\n"
		       "Please check `stderr' for more details."),
		     pname);
	 }
	 Free (cmd);
      }
   }
   else
      Free (path);
   Free (pname);
   Free (filename);
}

static proplist_t
get_color (proplist_t texture)
/* setstyle.c from Window Maker utils directory */
{
   char	*str;
   proplist_t  type  = WMGetFromPLArray (texture, 0);
   proplist_t  value = 0;

   if (!type)
      return NULL;

   str = WMGetFromPLString (type);
   if (streq (str, "solid"))
      value = WMGetFromPLArray (texture, 1);
   else if (streq (str + 1, "gradient"))
   {
      if (WMGetPropListItemCount (texture) == 3)
      {
	 GdkColor	color1, color2;
	 char		*c1name = WMGetFromPLString (WMGetFromPLArray (texture, 1));
	 char		*c2name = WMGetFromPLString (WMGetFromPLArray (texture, 2));
	 /*
	  *  Compute default color = (color1 + color2) / 2
	  */
	 if (!make_color (c1name, &color1))
	    color1.red = color1.green = color1.blue = 65535; /* white */
	 if (!make_color (c2name, &color2))
	    color2.red = color2.green = color2.blue = 65535; /* white */

	 color1.red   = (color1.red + color2.red) >> 1;
	 color1.green = (color1.green + color2.green) >> 1;
	 color1.blue  = (color1.blue + color2.blue) >> 1;

	 {
	    char colstr [MAXSTRLEN];

	    sprintf (colstr, "#%02x%02x%02x", /* new color */
		     (unsigned) (color1.red   / 256 + 0.5),
		     (unsigned) (color1.green / 256 + 0.5),
		     (unsigned) (color1.blue  / 256 + 0.5));
	    value = WMCreatePLString (colstr);
	 }
      }
   }
   else if (streq (str + 2, "gradient")) 
      value = WMGetFromPLArray (texture, 1);
   else if (streq (str + 1, "pixmap"))
      value = WMGetFromPLArray (texture, 2);

   return value;
}

static gint timer_index = 0;

static void
tree_moved (GtkCTree *ctree, GtkCTreeNode *node, GtkCTreeNode *new_parent, 
	    GtkCTreeNode *new_sibling)
{
   if (node && new_parent)
   {
      themeinfo_t *info     = gtk_ctree_node_get_row_data (tree, node);
      themeinfo_t *pinfo    = gtk_ctree_node_get_row_data (tree, new_parent);
      char 	  *fullname = g_strconcat (pinfo->fullname, "/",
					   info->name, info->suffix, NULL);
      
      info->parent = new_parent;
      rename_file_or_dir (info->fullname, fullname);
      timer_index = gtk_timeout_add (20, (GtkFunction) update_themes_timeout,
				     fullname);
   }
}

static void
update_themes_timeout (char *filename)
{
   build_theme_list ();
   select_theme (tree, filename);
   Free (filename);
   gtk_timeout_remove (timer_index);
}

