#!/usr/local/bin/perl -w

# Filter this script to pod2man to get a man page:
#   pod2man -c "Fvwm Utility" fvwm-themes-config | nroff -man | less -e

use strict;
use Getopt::Long;

my $prefix = $ENV{'prefix'} || '/usr/X11R6';
my $datadir = "${prefix}/share";
my $ROOT_PREFIX = $ENV{'ROOT_PREFIX'} || '';

my $version = '0.4.1';
my $fvwmVersion = '2.4.5';
my $fvwmDefaultImagePath = '/usr/include/X11/bitmaps:/usr/include/X11/pixmaps';
my $versionInfo = 'FVWM Themes version 0.4.1 from May 05 2002 at 09:30:11';

my $scriptName = ($0 =~ m:([^/]+)$:, $1);
my $scriptFile = "${prefix}/bin/$scriptName";
my $rcFile = "themes-rc";
my $rcFile2 = "$rcFile-2";
my $userHome = $ENV{'HOME'} || "./.";
my $userDir = $ENV{'FVWM_USERDIR'} || "$userHome/.fvwm";
my @searchPath = ($userDir, "${datadir}/fvwm");
my ($workDir, $siteDir);
my $themesSubDir = 'themes';
my $currentThemeName = 'current';
my $currentThemeSubDir = "$themesSubDir/$currentThemeName";
my $imagesSubDir = 'images';
my $themeCfgFile = 'theme.cfg';
my $mainDirFile = 'main';
my $defaultReadCommand = 'Read "%f"';

my $idKey = 'file';
my $themeKey = 'theme';
my $componentKey = 'component';
my $componentGroupKey = 'group';
my $pipe = 0;  # produce fvwm commands for fvwm's PipeRead if set
my $useRestart = $ENV{'FT_USE_RESTART'}? 1: 0;

# ----------------------------------------------------------------------------

sub showHelp {
	print "The fvwm-themes management utility.\n";
	print "Usage: $scriptName [OPTIONS]\n";
	print "Options:\n";
	print "\t--help             show this help and exit\n";
	print "\t--version          show the version and exit\n";
	print "\t--site             use site config dir for output, not user's\n";
	print "\t--com-mode         run under the communication mode\n";
	print "\t--com-name name    name for communication with fvwm-themes-com\n";
	print "\t--show-themes      show all themes list\n";
	print "\t--show-components  show components in all themes\n";
	print "\t--show-dir         show all theme directory full paths\n";
	print "\t--theme theme      limit --show-* only to this/these theme(s)\n";
	print "\t--show-value key   shows value for component given in --component\n";
	print "\t--component comp   defines a working component\n";
	print "\t--only-site        limit --show-* only to the site directory\n";
	print "\t--only-user        limit --show-* only to the user directory\n";
	print "\t--fvwmscript       format output of --show-* differently\n";
	print "\t--expand-rc file   expand fvwm configuration file to stdout\n";
	print "\t--fresh            refresh (regenerate) current theme configs\n";
	print "\t--reset            reset all components to default theme\n";
	print "\t--load [c]\@t       load the given theme component(s)\n";
	print "\t--drop c[\@t]       unload the current theme component\n";
	print "\t--option cmp:opt=v change the component option value.\n";
	print "\t--variant cmp=v    change the component variant\n";
	print "\t--set-locked cmp=v set or unset the component's lock\n";
	print "\t--pipe             generate fvwm commands\n";
	print "\t--install theme..  install theme.tar.gz or theme.tar.bz2 files\n";
	exit 0;
}

sub showVersion {
	print "$version\n";
	exit 0;
}

sub wrongUsage {
	print STDERR "Try '$scriptName --help' for more information.\n";
	exit -1;
}

sub errDie ($) {
	my $msg = shift;
	$msg .= "\n" unless $msg =~ /\n$/s;
	if ($pipe) {
		$msg =~ s/\n$//s;
		$msg =~ s/'/`/g;
		print qq(Exec exec xmessage -g 400x100 -xrm "*form*background:rgb:c0/50/50" -xrm "*form*okay*background:rgb:90/40/40" -xrm "*textSink*font:lucidasans-14" -xrm "*form*message*background:rgb:f0/60/60" -title '$scriptName error' -center '$msg'\n);
		exit -1;
	} else {
		die $msg;
	}
}

sub errWarn ($) {
	my $msg = shift;
	$msg .= "\n" unless $msg =~ /\n$/s;
	if ($pipe) {
		$msg =~ s/\n$//s;
		$msg =~ s/'/`/g;
		print qq(Exec exec xmessage -g 400x100 -xrm "*form*background:rgb:90/90/50" -xrm "*form*okay*background:rgb:70/70/40" -xrm "*textSink*font:lucidasans-14" -xrm "*form*message*background:rgb:b0/b0/60" -title '$scriptName warning' -center '$msg'\n);
	} else {
		warn $msg;
	}
}

#' <- a fix for my xemacs (olicha)

sub sysDie ($) {
	my $msg = shift;
	$msg =~ s/\s+$//s;
	errDie("$msg: [$!]");
}

sub isArrayElement ($$) {
	my $array = shift;
	my $element = shift;
	return int(grep { /^\Q$element\E$/ } @$array);
}

sub getArrayElementIndex ($$) {
	my $array = shift;
	my $element = shift;
	my $i;
	foreach ($i = 0; $i < @$array; $i++) {
		return $i if $array->[$i] eq $element;
	}
	return undef;
}

sub conjunctArrays ($$) {
	my $array1 = shift;
	my $array2 = shift;
	return [ grep { isArrayElement($array2, $_) } @$array1 ];
}

sub dumpPerlValue ($;$$) {
	my ($value, $level, $inline) = @_;
	$level ||= 0;
	$inline ||= 0;
	my $ref = ref($value);
	my $str = $ref;
	my @subValues = ();

	if (!$ref) {
		$str = $value;
		$str = '(undef)' unless defined $str;
	} elsif ($ref eq 'ARRAY') {
		@subValues = @$value;
	} elsif ($ref eq 'HASH') {
		@subValues = map {
			"$_\t" . &dumpPerlValue($value->{$_}, $level + 1, 1)
		} sort keys %$value;
	} elsif ($ref eq 'SCALAR') {
		@subValues = ($$value);
	} else {
		#errDie("Unsupported perl type $ref");
	}
	$str = ("\t" x $level) . "$str\n" unless $inline;
	my $str2 = "";
	foreach (@subValues) { $str2 .= &dumpPerlValue($_, $level + 1); }
	if ($inline && $str2 =~ /^(.*)\n$/s) { $str .= "\n"; $str2 = $1; }
	return "$str$str2";
}

sub clonePerlValue ($) {
	my $value = shift;
	my $ref = ref($value);

	if (!$ref) {
		return $value;
	} elsif ($ref eq 'ARRAY') {
		my $array = [ map { &clonePerlValue($_) } @$value ];
		return $array;
	} elsif ($ref eq 'HASH') {
		my $hash = {};
		foreach (keys %$value) { $hash->{$_} = &clonePerlValue($value->{$_}); }
		return $hash;
	} elsif ($ref eq 'SCALAR') {
		my $scalar = $$value;
		return \$scalar;
	} else {
		#errDie("Unsupported perl type $ref");
		return $value;
	}
}

# ----------------------------------------------------------------------------

sub loadFile ($) {
 	my $fileName = shift;

	open(FILE, "<$fileName") || sysDie("Can't open $fileName");
	my $fileContent = join("", <FILE>);
	close(FILE) || sysDie("Can't close $fileName");
	return \$fileContent;
}

sub saveFile ($$;$$) {
	my ($fileName, $fileContentRef, $createDirs, $perm) = @_;

	if ($createDirs) {
		my $dirName = $fileName; $dirName =~ s:(^|/)[^/]*$::;
		makePath($dirName, $perm) unless -d $dirName;
	}
	open(FILE, ">$fileName") || sysDie("Can't open $fileName");
	print FILE $$fileContentRef;
	close(FILE) || sysDie("Can't close $fileName");
}

sub makePath ($;$) {
	my $dirName = shift;
	my $perm = shift || 0775;
	return if -d $dirName;

	my $parentDir = $dirName; $parentDir =~ s:(^|/)[^/]+/?$::;
	&makePath($parentDir, $perm) unless -d $parentDir;
	mkdir($dirName, $perm) || sysDie("Can't mkdir $dirName");
}

# ----------------------------------------------------------------------------

sub getExpandedRc ($) {
	my $file = shift;
	### should detect infinitive loops?
	if (!-f $file) {
		foreach (@searchPath) {
			if (-f "$_/$file") { $file = "$_/$file"; last; }
		}
	}
	if (!-f $file) {
		return "#| File '$file' is not found\n";
	}

	my $output = "";
	foreach (`cat $file`) {
		chomp; $_ .= "\n";
		/^read\s+['`"]?([^\s'`"]+)/i && do {
			$output .= "#.---- start: $_";
			$output .= &getExpandedRc($1);
			$output .= "#`====== end: $_\n";
			next;
		};
		$output .= $_;
	}
	return $output;
}

sub searchThemeCfgIncludeFile ($$) {
	my ($file, $theme) = @_;
	my @subDirs = ($theme);
	if ($file =~ /^\.\.\/(.*)/) {
		$file = $1;
		unshift @subDirs, ".";
	}
	my $dir;
	foreach $dir (@searchPath) {
		foreach (@subDirs) {
			my $file = "$dir/$themesSubDir/$_/$file";
			return $file if -f $file;
		}
	}
	return undef;
}

# unfortunately fvwm is inconsistent, so the second parameter.
sub escapeMenuName ($;$) {
	my $name = shift;
	$name = "unknown" unless defined $name;  # maybe die?
	my $hasUnderline = shift;
	my $escapeStr = $hasUnderline? '\\': '&';
	$name =~ s/\\/\\\\/g;
	$name =~ s/&/$escapeStr&/g;
	$name;
}

sub decodeCfgEntry ($) {
	my $str = shift;
	my $entry = {};

	$str =~ s/\s+$//s;
	$str =~ s/\r/\n/sg;
	foreach (split(/\n/s, $str)) {
		s/^\s+//s;
		next if /^#/;
		next if $_ eq '';

		my ($key, $value) = split(/=/, $_, 2);
		errDie("Incorrect cfg line: $_\n") unless $key && defined $value;
		## key1.key2:key3+key=value
		## $entry->{key1}->[-1]->{key2}->{key3}->[-1]->{key}=value
		my $hash = $entry;
		$key =~ /^(.*?)([^\+\.\:]+\+?)$/;
		$key = $2;
		my $lastKey = "_";
		foreach (split(/([\+\.\:]+)/, $1)) {
			/^:/ and do {
				$hash = ($hash->{$lastKey} ||= {});
				next;
			};
			/^[\+\.]/ and do {
				my $array = ($hash->{$lastKey} ||= []);
				/^\+/ and push @$array, {};
				$hash = $array->[-1];
				next;
			};
			errDie("Incorrect line key $_, not enough +'s")
				unless defined $hash;
			$lastKey = $_;
		}
		if ($key =~ /^(.*)\+$/) {
			$key = $1;
			$hash->{$key} = [] unless exists $hash->{$key};
			push @{$hash->{$key}}, $value;
		} else {
			$hash->{$key} = $value;
		}
	}
	return $entry;
}

sub encodeCfgEntry ($) {
	my $entry = shift;
	my $str = "";

	foreach (sort keys %$entry) {
		my ($key, $value) = ($_, $entry->{$_});
		if (!ref($value)) {
			$str .= "$key=$value\n";
		} elsif (ref($value) eq 'ARRAY') {
			# ARRAY in HASH
			next unless @$value;
			my $ref = ref($value->[0]);
			if (!$ref) {
				foreach (@$value) {
					$str .= "$key+=$_\n";
				}
			} elsif ($ref eq 'ARRAY') {
				errDie("ARRAY in ARRAY is not supported");
			} elsif ($ref eq 'HASH') {
				# ARRAY of HASH's in HASH
				foreach (@$value) {
					my $subStr = &encodeCfgEntry($_);
					my ($d, $c) = ('+', '.');
					$subStr =~ s/^(.*)$/my $a = "$key$d$1"; $d = $c; $a/mge;
					$str .= $subStr;
				}
			} else {
				errDie("Unsupported perl type ($ref) in ARRAY");
			}
		} elsif (ref($value) eq 'HASH') {
			# HASH in HASH
			my $subStr = &encodeCfgEntry($value);
			my $d = ':';
			$subStr =~ s/^(.*)$/$key$d$1/mg;
			$str .= $subStr;
		} else {
			errDie("Unsupported perl type ($value) in HASH");
		}
	}
	return $str;
}

use vars qw($cfgFileCache);
BEGIN { $cfgFileCache = {}; }
sub loadThemeCfg ($) {
	my $theme = shift;
	my $cfgFile = searchThemeCfgIncludeFile($themeCfgFile, $theme);
	$cfgFile ||= searchThemeCfgIncludeFile($themeCfgFile, "default");
	errDie("No $themeCfgFile for $theme found") unless defined $cfgFile;

	return $cfgFileCache->{$theme} if exists $cfgFileCache->{$theme};
	my $cfg = [{}, {}];

	my $strRef = loadFile($cfgFile);
	while ($$strRef =~ s/^!include(-quiet)?\s+(.*)\s*$/
		my $subCfgFile = searchThemeCfgIncludeFile($2, $theme);
		errDir("No include $2 in $cfgFile") unless $1 || defined $subCfgFile;
		defined $subCfgFile? ${loadFile($subCfgFile)}: ""
	/meg) {}

	while (1) {
		last unless $$strRef =~ /(?:^|\n)\[(\w+)\](.*?)(|\n\[.*)$/s;
		$$strRef = $3;
		my $entryTag = lc($1);
		my $entry = decodeCfgEntry($2);
		if ($entryTag eq 'theme') {
			$cfg->[0] = $entry;
		} elsif ($entryTag eq 'component') {
			my $key = $entry->{$idKey};
			errDie("No '$idKey' value in entry [$entryTag] in $cfgFile") unless $key;
			$cfg->[1]->{$key} = $entry;
		} else {
			print STDERR "Warning: unknown entry [$entryTag], ignoring...\n";
			next;
		}
	}

#	# leave only real components
#	my $components = $cfg->[0]->{$componentKey};
#	errDie("No '$componentKey' in entry [theme]") unless ref($components) eq 'ARRAY';
#	my @realComponents = ();
#	foreach (@$components) {
#		my $themeDir = getThemeDir($cfg->[1]->{$_}->{$themeKey});
#		my $file = "$themeDir/$_";
#		push @realComponents, $_ if -e $file;
#	}
#	$cfg->[0]->{$componentKey} = [sort @realComponents];

	$cfgFileCache->{$theme} = $cfg;
	return $cfg;
}

sub saveThemeCfg ($$) {
	my ($theme, $cfg) = @_;
	errDie("Parameter should be ARRAY") unless ref($cfg) eq 'ARRAY';
	my ($themeCfg, $componentCfgs) = @$cfg;
	errDie("Parameter should be [HASH, HASH]")
		unless ref($themeCfg) eq 'HASH' && ref($componentCfgs) eq 'HASH';

	my $str = "";
	$str .= "[theme]\n" . encodeCfgEntry($themeCfg) . "\n";
	foreach (sort keys %$componentCfgs) {
		$str .= "[component]\n" . encodeCfgEntry($componentCfgs->{$_}) . "\n";
	}

	my $cfgFile = "$workDir/$themesSubDir/$theme/$themeCfgFile";
	saveFile($cfgFile, \$str, 1);
}

sub parseComponentName ($) {
	my $name = shift;
	return ($2, $1) if $name =~ /^(.*?)@(.*)$/;
#	errDie("Incorrect component name $name, should be component\@theme");
	return (undef, $name);
}

sub getThemeDir ($) {
	my $theme = shift;
	my $dir;
	foreach $dir (@searchPath) {
		my $dir = "$dir/$themesSubDir/$theme";
		return $dir if -d $dir;
	}
	errDie("No theme '$theme' found");
}

sub getThemeComponents ($) {
	my $theme = shift;

	my $themeDir = getThemeDir($theme);
	my @allComponents = keys %{loadThemeCfg($theme)->[1]};
	return [ sort @allComponents ] if $theme eq $currentThemeName;

	my @components = ();
	foreach (@allComponents) {
		next if m:/:; ### for now
		my $file = "$themeDir/$_";
		push @components, $_ if -e $file || -d "$file.d";
	}
	return [sort @components];

#	my @components = keys %{loadThemeCfg($theme)->[1]};
#	return \@components;
}

sub getThemeComponentsAndGroups ($) {
	my $theme = shift;

	my $components = getThemeComponents($theme);
	my $groups = clonePerlValue(loadThemeCfg($theme)->[0]->{$componentGroupKey});
	# use reasonable default if not given
	$groups = [ { 'name' => "all", $componentKey => ['*'] } ] unless ref($groups) eq 'ARRAY';
	foreach (@$groups) {
		my $groupComponents = $_->{$componentKey};
		# for technical reasons [ "" ] represents all components to load
		$_->{$componentKey} =
			(ref($groupComponents) ne 'ARRAY' || @$groupComponents == 1 && $groupComponents->[0] =~ /^\*?$/)?
			[ "" ]: conjunctArrays($components, $groupComponents);
	}
	return ($components, $groups);
}

sub getAllThemes (;$$) {
	my $onlySite = shift || 0;
	my $onlyUser = shift || 0;
	my @dirList = ();
	push @dirList, $siteDir if $onlySite;
	push @dirList, $userDir if $onlyUser;
	@dirList = @searchPath unless @dirList;
	my $themes = {};
	my $dir;
	foreach $dir (@dirList) {
		my $dir = "$dir/$themesSubDir";
		opendir(DIR, $dir);
		foreach (readdir(DIR)) {
			next if /^\./ || $_ =~ /^$currentThemeName/;
			next unless -d "$dir/$_";
			next if exists $themes->{$_};
			$themes->{$_} = 1;
		}
		closedir(DIR);
	}
	return [sort keys %$themes];
}

sub showThemeComponents ($$$$$$) {
	my ($themes, $withComponents, $component, $script, $onlySite, $onlyUser) = @_;
	my $output = "";
	my $delim0 = $script? "    ": "\t";
	my $delim1 = $script? "|": "\n";
	$themes = getAllThemes($onlySite, $onlyUser) unless @$themes;

	my $theme;
	foreach $theme (@$themes) {
		my $components = getThemeComponents($theme);
		next if defined $component && !isArrayElement($components, $component);
		$output .= $theme . $delim1;
		if ($withComponents) {
			foreach (@$components) {
				$output .= $delim0 . $_ . $delim1;
			}
		}
		$output =~ s/\Q$delim1\E$/\n/ if $script;
	}
	print $output;
	exit(0);
}

sub showThemeDirs ($) {
	my $themes = shift;
	$themes = getAllThemes() unless @$themes;
	my $theme;
	foreach $theme (@$themes) {
		print getThemeDir($theme) . "\n";
	}
	exit(0);
}

sub showThemeComponentValues ($$) {
	my ($component, $keys) = @_;
	my $theme;
	($theme, $component) = parseComponentName($component);
	errDie("No component name in '$component' given") unless $component;
	$theme ||= 'current';

	my $cc = loadThemeCfg($theme)->[1]->{$component};
	errDie("Can't find component '$component' in theme '$theme'") unless $cc;
	my $key;
	foreach $key (@$keys) {
		my $value = $cc->{$key};
#		errDie("No key '$key' defined in component '$component\@$theme' cfg")
		$value = "*undefined*"
			unless defined $value;
		print dumpPerlValue($value);
	}
	exit(0);
}

sub createCurrentImageDirLinks ($$) {
	my ($theme, $subdirs) = @_;
	my $srcDir = getThemeDir($theme) . "/$imagesSubDir";
	my $dstDir = "$workDir/$currentThemeSubDir/$imagesSubDir";
	makePath($dstDir) unless -d $dstDir;
	foreach (@$subdirs) {
		my $srcFile = "$srcDir/$_";
		my $dstLink = "$dstDir/$_";
		unlink($dstLink); # || sysDie("Can't unlink $dstLink")
			#if -e $dstLink;
		symlink($srcFile, $dstLink) || sysDie("Can't symlink $srcFile $dstLink");
	}
}

sub getHashIdArrayIndex ($$) {
	my ($array, $id) = @_;
	my ($index, $i) = -1;
	for ($i = 0; $i < @$array; $i++) {
		if ($id eq $array->[$i]->{$idKey}) {
			$index = $i + 1; last;
		}
	}
	$index = $1 if $index < 0 && $id =~ /^\s*(\d+)\s*$/;
	return $index;
}

# ----------------------------------------------------------------------------

package Fvwm::ThemeCfg;

sub AUTOLOAD ($@) {
	my $func = $Fvwm::ThemeCfg::AUTOLOAD;
	$func =~ s/.*://g;
	$func = "main::$func";
	no strict 'refs';
	&$func(@_);
}

sub DESTROY ($) {}

sub new ($$;$$) {
	my $this  = shift;
	my $class = ref($this) || $this;
	my $theme = shift;
	my $loadTheme = shift || $theme;
	my $fresh = shift || 0;

	my $self = {
		$idKey => $theme,
		'name' => ucfirst($theme),
		'cc'   => {},
	};
	bless($self, $class);
	$self->setModified($fresh || $theme ne $loadTheme? 1: 0);

	my ($themeCfg, $componentCfgs) = @{loadThemeCfg($loadTheme)};
	my ($key, $value);
	while (($key, $value) = each %$themeCfg) {
		$self->{$key} = ($key eq $componentKey
			&& $loadTheme !~ /^$currentThemeName/)?
			getThemeComponents($loadTheme): $value;
	}
	errDie("No '$componentKey' key in theme '$loadTheme' cfg") unless exists $self->{$componentKey};
	my $dir = getThemeDir($loadTheme);
	my @components = ('_core', @{$self->{$componentKey}});

	my $subComponentParentTheme = {};  # to handle newly added subcomponents
	my $cindex = -1;  # because of '_core'
	my $component;
	while ($component = shift(@components)) {
		my $componentCfg = $componentCfgs->{$component};
		my $realTheme = $loadTheme;
		my $cc;
		if ($fresh) {
			$realTheme = $componentCfg->{$themeKey}
				|| $subComponentParentTheme->{$component} || "*unknown*";
			my $origComponentCfg = loadThemeCfg($realTheme)->[1]->{$component};
			unless ($origComponentCfg) {
				errWarn("Theme '$realTheme' has no component '$component' anymore, auto-dropping");
				splice(@{$self->{$componentKey}}, $cindex, 1);
				next;
			}
			$dir = getThemeDir($realTheme);
			$cc = clonePerlValue($origComponentCfg);

			$cc->{'memory'} = $componentCfg->{'memory'}
				if exists $componentCfg->{'memory'};
			$cc->{'locked'} = $componentCfg->{'locked'}
				if exists $componentCfg->{'locked'};
			$cc->{'current'} = $componentCfg->{'current'}
				if exists $componentCfg->{'current'};
			if (ref($componentCfg->{'option'}) eq 'ARRAY') {
				my $i;
				for ($i = 0; $i < @{$componentCfg->{'option'}}; $i++) {
					my $currIndex = $componentCfg->{'option'}->[$i]->{'current'};
					$cc->{'option'}->[$i]->{'current'} = $currIndex if $currIndex;
				}
			}
		} else {
			$cc = clonePerlValue($componentCfg);
		}
		$cc->{$themeKey} = $realTheme unless $cc->{$themeKey};
		if (ref($cc->{'contains'}) eq 'ARRAY') {
			push @components, map {
				my $component0 = "$component/$_";
				$subComponentParentTheme->{$component0} = $cc->{$themeKey};
				$component0
			} @{$cc->{'contains'}};
		}
		
		my $readFile;
		unless (exists $cc->{'read-file'}) {
			if (ref($cc->{'contains'}) eq 'ARRAY') {
				$readFile = "";
			} elsif ($component eq '_core') {
				$readFile = "*virtual*";
			} else {
				$readFile = "$dir/$component";
				$readFile .= ".d" if -d "$readFile.d" && !-e $readFile;
				$readFile .= "/$mainDirFile" if -d $readFile;
			}
			$cc->{'read-file'} = $readFile;
		}
		$cc->{'locked'} = 0 unless exists $cc->{'locked'};
		$cc->{'used'} = 1 unless exists $cc->{'used'};
		errDie("Duplicated component '$component' for theme '$theme'")
			if exists $self->{'cc'}->{$component} &&
				$self->{'cc'}->{$component}->{'used'};
		$self->{'cc'}->{$component} = $cc;
		$cindex++;
	}
	return $self;
}

sub save ($) {
	my $self = shift;

	my ($themeCfg, $componentCfgs) = ( {}, {} );

	my ($key, $value, $comp, $cfg);
	while (($key, $value) = each %$self) {
		next if $key =~ /^_/ || $key eq 'cc' || $key eq 'mc';
		$themeCfg->{$key} = $value;
	}
	while (($comp, $cfg) = each %{$self->{'cc'}}) {
		$componentCfgs->{$comp} = {};
		while (($key, $value) = each %$cfg)
			{ $componentCfgs->{$comp}->{$key} = $value; }
	}
	saveThemeCfg($self->{$idKey}, [$themeCfg, $componentCfgs]);
}

sub isModified ($) {
	my $self = shift;
	return $self->{'_isModified'};
}

sub setModified ($;$) {
	my $self = shift;
	$self->{'_isModified'} = @_? shift: 1;
}

sub hasComponent ($$) {
	my $self = shift;
	my $component = shift;
	return isArrayElement($self->{$componentKey}, $component);
}

#olicha: maybe not useful??
sub hasComponentCfg ($$) {
	my $self = shift;
	my $component = shift;
	return 1 if $self->{'cc'}->{$component};
	return 0;
}

sub getComponentCfg ($$) {
	my $self = shift;
	my $component = shift;
	my $cfg = $self->{'cc'}->{$component};
	errDie("No component '$component' cfg in theme $self->{'name'}")
		unless defined $cfg;
	return $cfg;
}

sub setComponentCfg ($$$) {
	my $self = shift;
	my $component = shift;
	$self->{'cc'}->{$component} = shift;
}

sub storeThemeComponentMemory ($$$) {
	my $self = shift;
	my ($theme, $component) = @_;
	my $cc = $self->getComponentCfg($component);
	my $cm = {};

	if (ref($cc->{'variant'}) eq 'ARRAY' && $cc->{'current'}) {
		$cm->{'current'} = $cc->{'current'};
	}
	my $options = $cc->{'option'};
	if (ref($options) eq 'ARRAY' && @$options) {
		my $o;
		$cm->{'option'} = [];
		my $isSet = 0;
		for ($o = 0; $o < @$options; $o++) {
			my $index = $options->[$o]->{'current'};
			### we should probably store names not indexes (or both)
			push @{$cm->{'option'}}, { 'current' => ($index || "") };
			$isSet = 1 if $index;
		}
		delete $cm->{'option'} unless $isSet;
	}

	return unless keys %$cm;
	$cm->{'time'} = time();

	my $_c = $self->getComponentCfg("_core");
	my $memory = ( $_c->{'memory'} ||= {} );
	my $themeMemory = ( $memory->{$theme} ||= {} );
	$themeMemory->{$component} = $cm;
}

sub applyThemeComponentMemory ($$$) {
	my $self = shift;
	my ($theme, $component) = @_;

	my $_c = $self->getComponentCfg("_core");
	my $memory = $_c->{'memory'};
	return unless ref($memory) eq 'HASH';
	my $themeMemory = $memory->{$theme};
	return unless ref($themeMemory) eq 'HASH';
	my $cm = $themeMemory->{$component};
	return unless ref($cm) eq 'HASH';

	# delete component memory if older then 6 months
	if (time() - $cm->{'time'} > 6 * 30 * 24 * 60 * 60) {
		delete $themeMemory->{$component};
		return;
	}

	my $cc = $self->getComponentCfg($component);
	if ($cm->{'current'} && ref($cc->{'variant'}) eq 'ARRAY') {
		$cc->{'current'} = $cm->{'current'};
	}
	my $mOptions = $cm->{'option'};
	my $options = $cc->{'option'};
	if (ref($mOptions) eq 'ARRAY' && ref($options) eq 'ARRAY') {
		if (@$mOptions != @$options) {
			delete $themeMemory->{$component};
			return;
		}
		my $o;
		for ($o = 0; $o < @$options; $o++) {
			my $currIndex = $mOptions->[$o]->{'current'};
			$options->[$o]->{'current'} = $currIndex if $currIndex;
		}
	}
}

# olicha: I've done a lot of modif in this function:
# subcomponent are automaticaly (un)loaded and option and
# variant are saved here. I've used a new "cfg command" called
# 'used' for unloading the 'cc' component since I've got some
# difficulty to do the unloading by freeing memory ...
sub useNewComponents ($$$;$) {
	my $self = shift;
	my $loadComponents = shift;
	my $dropComponents = shift;
	my $isSubComponent = shift || 0;
	return unless @$loadComponents || @$dropComponents;
	$self->setModified();

	foreach (@$dropComponents) {
		my ($theme, $component) = parseComponentName($_);
		errDie("Wrong component '$_' to drop specified")
			unless $component;
		my $index = getArrayElementIndex($self->{$componentKey}, $component);
		errDie("Can't find component '$component' to drop")
			unless (defined $index || $isSubComponent);
		my $cc = $self->getComponentCfg($component);
		my $origTheme = $cc->{'theme'};
		errDie("Can't find component '$component\@$theme' to drop")
			if $theme && $theme ne $origTheme;

		### not finished, should check dependancies before deleting
		### olicha: still not finished but subcomponent are automatically
		### unloaded
		splice(@{$self->{$componentKey}}, $index, 1) if defined $index;

		if ($self->hasComponentCfg($component)) {
			$self->storeThemeComponentMemory($origTheme, $component);
			$cc->{'used'} = 0;
			my $contains = $cc->{'contains'};
			if (ref($contains) eq 'ARRAY') {
				my @dropSubComponents =
					map { "$component/$_" } @$contains;
				$self->useNewComponents([], \@dropSubComponents, 1);
			}
		} else {
			errDie("Internal error; component $component.");
		}
	}

	### not very correct implementation temporarily
	### olicha: still not correct but there is a support for sub components
	foreach (@$loadComponents) {
		my ($theme, $component) = parseComponentName($_);
		if ($theme && $component eq "") {
			my @components = grep {
				my $cc = $self->{'cc'}->{$_}; !$cc || !$cc->{'locked'}
			} @{getThemeComponents($theme)};
			foreach (@components) { $_ .= "\@$theme"; }
			errDie("All components in \@$theme are locked, unlock first")
				unless @components;
			$self->useNewComponents(\@components, []);
			next;
		}
		errDie("Wrong component '$_' to load specified")
			unless $component && $theme;

		$self->useNewComponents([], [$component], 0)
			if $self->hasComponent($component);
		push @{$self->{$componentKey}}, $component
			unless $isSubComponent;

		my $themeCfg = new Fvwm::ThemeCfg($theme);
		my $cc = $themeCfg->getComponentCfg($component);
		$cc->{'used'} = 1;
		$self->setComponentCfg($component, $cc);
		$self->applyThemeComponentMemory($theme, $component);

		my $imageDirs = $cc->{'local-imagepath'};
		createCurrentImageDirLinks($theme, $imageDirs)
			if ref($imageDirs) eq 'ARRAY';

		my $contains = $cc->{'contains'};
		if (ref($contains) eq 'ARRAY') {
			my $load = [];
			@$load = map { "$component/$_\@$theme" } @{$cc->{'contains'}};
			$self->useNewComponents($load, [], 1);
		}

	}
}

sub setNewComponentValues ($$$$) {
	my $self = shift;
	my ($options, $variants, $setLocks) = @_;
	return unless keys %$options || keys %$variants || keys %$setLocks;
	$self->setModified();

	my ($key, $value);
	while (($key, $value) = each %$options) {
		my ($component, $option) = split(':', $key);
		errDie("Bad option format '$key'")
			unless defined $component && defined $option;
		my $cc = $self->getComponentCfg($component);
		my $options = $cc->{'option'};
		my $index = getHashIdArrayIndex($options, $option);
		errDie("Unexisting option '$option' in component '$component'")
			if $index <= 0 || $index > @$options;
		my $optionEntry = $options->[$index - 1];
		my $values = $optionEntry->{'value'};
		my $index2 = getHashIdArrayIndex($values, $value);
		$index2 = ($optionEntry->{'default'} || 1) if $index2 == 0;
		errDie("Unexisting option value '$index2' in option '$component:$option'")
			if $index2 <= 0 || $index2 > @$values;
		$optionEntry->{'current'} = $index2;
	}

	my ($component, $variant);
	while (($component, $variant) = each %$variants) {
		my $cc = $self->getComponentCfg($component);
		my $variants = $cc->{'variant'};
		my $index = getHashIdArrayIndex($variants, $variant);
		$index = $cc->{'default'} if $index == 0;
		errDie("Unexisting variant '$variant' in component '$component'")
			if $index <= 0 || $index > @$variants;
		$cc->{'current'} = $index;
	}

	while (($component, $value) = each %$setLocks) {
		errDie("The locked value should be 0 or 1, not '$value'")
			if $value !~ /^[01]$/;
		my $cc = $self->getComponentCfg($component);
		$cc->{'locked'} = $value;
	}
}

sub getAllThemeSubMenusRc ($) {
	my $self = shift;
	my $currentComponents = $self->{$componentKey};
	my $allThemes = getAllThemes();
	my $listRc = "";
	my $menuRc = "";

	my $theme;
	foreach $theme (@$allThemes) {
		my ($components, $groups) = getThemeComponentsAndGroups($theme);
		my $used = 0;

		$menuRc .= qq(DestroyMenu "MenuFvwmTheme-$theme"\n);
		$menuRc .= qq(AddToMenu   "MenuFvwmTheme-$theme" "Load components" Title\n);
		foreach (@$groups) {
			my $name = escapeMenuName($_->{'name'});
			my $groupComponents = $_->{$componentKey};
			my $groupAction = !@$groupComponents? "Nop":
				'FuncFvwmThemesConfigAndUpdate "' . join(' ',
					map { "--load $_\@$theme" } @$groupComponents) . '"';
			$menuRc .= qq(+ "[ $name ]"\t$groupAction\n);
		}
		$menuRc .= qq(+ "" Nop\n);
		$menuRc .= join('', map {
			my $used0 = isArrayElement($currentComponents, $_) &&
				($self->getComponentCfg($_)->{$themeKey} || "") eq $theme;
			$used ||= $used0;
			my $label = "$_"; $label .= "\t(used)" if $used0;
#			m:/:? "":  # ignore contained components for now
			qq(+ "$label"\tFuncFvwmThemesLoad "$_\@$theme"\n)
		} @$components);

		my $readmeFile = getThemeDir($theme) . "/README";
		if (-r $readmeFile) {
			$menuRc .= qq(+ "" Nop\n);
			$menuRc .= qq(+ "README"\tFuncFvwmViewFile "$readmeFile"\n);
		}
		my $label = "$theme"; $label .= "\t(used)" if $used;
		$listRc .= qq(+ "$label"\tPopup "MenuFvwmTheme-$theme"\n);
	}
	return ($listRc, $menuRc);
}

sub getOwnThemeSubMenusRc ($) {
	my $self = shift;
	my $currentComponents = [sort keys %{$self->{cc}}];  # $self->{$componentKey};
	my $name = $self->{'name'};
	my $id = $self->{$idKey};
	my $selfMenuName = "MenuFvwmTheme-$id";
	my $listRc = qq(+ "$id"\tPopup "$selfMenuName"\n);
	my @menuRc = ("", "", "");  # 3 parts of the component menu
	my $menusRc = "";
	my $dropExtraArgs = "";

	$menuRc[0] .= qq(DestroyMenu "$selfMenuName"\n);
	$menuRc[0] .= qq(AddToMenu   "$selfMenuName" "$name Theme" Title\n);
	foreach (@$currentComponents) {
		next if /^_/;
		my $cc = $self->getComponentCfg($_);
		#olicha 1
		next unless $cc->{'used'};
		$dropExtraArgs .= " --drop $_" if /-extra$/;

		my $name = escapeMenuName($cc->{'name'} || $_);
		my $currMenu = "$selfMenuName/$_";
		$menusRc .= qq(DestroyMenu "$currMenu"\n);
		$menusRc .= qq(AddToMenu   "$currMenu" "$name" Title\n);
		my $m = 2;
		my $contains = $cc->{'contains'};
		if (ref($contains) eq 'ARRAY') {
			my $subComponent;
			foreach $subComponent (@$contains) {
				my $name = $self->getComponentCfg("$_/$subComponent")->{'name'};
				$name = escapeMenuName($name || $subComponent);
				$menusRc .= qq(+ "$name"\tPopup "$currMenu/$subComponent"\n);
			}
			$m = $cc->{'priority'}? 0: 1;
		} else {
			my $options = $cc->{'option'};
			if (ref($options) eq 'ARRAY') {
				my $o;
				my $inline = $cc->{'inline'};
				my $subRc1 = "";
				my $subRc2 = "";
				for ($o = 0; $o < @$options; $o++) {
					my $optionEntry = $options->[$o];
					my $name = escapeMenuName($optionEntry->{'name'});
					my $optionFile = $optionEntry->{'file'};
					my $values = $optionEntry->{'value'};

					$optionEntry->{'current'} ||= ($optionEntry->{'default'} || 1);
					my $index = $optionEntry->{'current'} - 1;
					if ($index < 0 || $index >= @$values) {
						$index = 0;  # maybe die on this error?
						$optionEntry->{'current'} = $index + 1;
					}
					if ($inline || $name eq "") {
						$menusRc .= $name ne ""? qq(+ "$name" Title\n):
							$menusRc =~ / Title\n$/? "": qq(+ "" Nop\n);
					} else {
						$subRc1 .= qq(+ "$name"\tPopup "$currMenu-$optionFile"\n);
						$subRc2 .= qq(DestroyMenu "$currMenu-$optionFile"\n);
						$subRc2 .= qq(AddToMenu   "$currMenu-$optionFile"\n);
					}
					my $i;
					for ($i = 0; $i < @$values; $i++) {
						my $isCurrent = $i eq $index;
						my $name = escapeMenuName($values->[$i]->{'name'}, !$isCurrent);
						my $star = $isCurrent? "": "&";
						my $icon = $isCurrent? "choice-yes": "empty";
						my $n = $i + 1;
						my $menuItem = qq(+ "%menu/$icon.xpm%$star$name"\tFuncFvwmThemesOption $_:$optionFile=$n\n);
						($inline? $menusRc: $subRc2) .= $menuItem;
					}
					#$menusRc .= qq(+ "" Nop\n);
				}
				$menusRc .= "$subRc1$subRc2" . "AddToMenu   $currMenu\n"
					unless $inline;
				$m = 1;
			}
			my $variants = $cc->{'variant'};
			if (ref($variants) eq 'ARRAY') {
				$cc->{'current'} ||= $cc->{'default'};
				my $index = $cc->{'current'} - 1;
				if ($index < 0 || $index >= @$variants) {
					$index = 0;  # maybe die on this error?
					$cc->{'current'} = $index + 1;
				}
				my $readFile = $cc->{'read-file'};
				$readFile =~ s:/[^/]+$:/$variants->[$index]->{'file'}:;
				if ($readFile ne $cc->{'read-file'}) {
					$cc->{'read-file'} = $readFile;
					$self->setModified();
				}
				my $i;
				for ($i = 0; $i < @$variants; $i++) {
					my $isCurrent = $i eq $index;
					my $name = escapeMenuName($variants->[$i]->{'name'}, !$isCurrent);
					my $icon = $isCurrent? "choice-yes": "empty";
					my $star = $isCurrent? "": "&";
					my $n = $i + 1;
					$menusRc .= qq(+ "%menu/$icon.xpm%$star$name"\tFuncFvwmThemesVariant $_=$n\n);
				}
				$m = 1;
			}
		}
		$menusRc .= qq(+ "" Nop\n) unless $m == 2;
		### Temporarily hardcoded
		my $dropCommand = !/^(settings|colors|menus)/ || /-extra$/?
			qq(FuncFvwmThemesDrop "$_"):
			'Exec xmessage "Dropping of this component is not supported"';
		my $lockLabel = $cc->{'locked'}? "Unlock": "Lock";
		my $nonLocked = 1 - $cc->{'locked'};
		my $lockCommand = qq(FuncFvwmThemesSetLocked "$_=$nonLocked");
		my $infoCommand = 'Exec exec xmessage "Help is not available yet"';
		$menusRc .= qq(+ "%menu/choice-no.xpm%&Drop this component"\t$dropCommand\n);
		$menusRc .= qq(+ "%menu/wm-lock.xpm%&$lockLabel this component"\t$lockCommand\n);
		$menusRc .= qq(+ "%menu/information.xpm%&Info for $_\@$cc->{'theme'}"\t$infoCommand\n);

		my $lockIcon = $cc->{'locked'}? "wm-lock": "empty";
		# use $name instead of $_?
		$menuRc[$m] .= qq(+ "%menu/$lockIcon.xpm%$_"\tPopup "$selfMenuName/$_"\n) unless m:/:;
	}
	if ($dropExtraArgs) {
		push @menuRc, qq(+ "%menu/choice-no.xpm%&Drop all extra"\tFuncFvwmThemesConfigAndUpdate "$dropExtraArgs"\n);
	}
	my $menuRc = join(qq(+ "" Nop\n), @menuRc) . "\n$menusRc";
	$menuRc =~ s/(\+ "" Nop\n){2,}/$1/sg;
	return ($listRc, $menuRc);
}

# sorts all current components to be read according to their dependancies
sub getSortedComponentsToRead ($) {
	my $self = shift;
	my @currentComponents;

	my $precedes = {};
	my $requires = {};
	my $provides = {};
	my $component;

	# prepare components to be sorted
	foreach $component (sort keys %{$self->{'cc'}}) {
		my $cc = $self->getComponentCfg($component);
		next if $component =~ /^_/;
		#olicha 1
		next unless $cc->{'used'};		
		next unless $cc->{'read-file'};
		push @currentComponents, $component;
		$precedes->{$component} = {};
	}

	# process 'precedes' and 'follows' dependances; prepare to the next step
	foreach $component (@currentComponents) {
		my $cc = $self->getComponentCfg($component);
		my $precedes0 = {};
		if (ref($cc->{'precedes'}) eq 'ARRAY') {
			foreach (@{$cc->{'precedes'}}) {
				$precedes0->{$_} = 1;
			}
		}
		if (ref($cc->{'follows'}) eq 'ARRAY') {
			foreach (@{$cc->{'follows'}}) {
				$precedes0->{$_} = -1;
			}
		}
		if (keys %$precedes0) {
			my ($c2, $cmp);
			while (($c2, $cmp) = (each %$precedes0)) {
				$precedes->{$component}->{$c2} = +$cmp;
				$precedes->{$c2}->{$component} = -$cmp;
			}
		}

		$requires->{$component} = $cc->{'requires'} || [];

		if (ref($cc->{'provides'}) eq 'ARRAY') {
			$provides->{$component} = {};
			foreach (@{$cc->{'provides'}}) {
				$provides->{$component}->{$_} = 1;
			}
		}
		#$provides->{$component}->{$component} = 1;
	}

	# process 'provides' and 'requires' dependances
	my $cnum = @currentComponents;
	my ($i, $j);
	for ($i = 0; $i < $cnum - 1; $i++) {
		my $c1 = $currentComponents[$i];
		for ($j = $i + 1; $j < $cnum; $j++) {
			my $c2 = $currentComponents[$j];
			my $cmp = undef;
			foreach (@{$requires->{$c1}}) {
				$cmp = -1 if $_ eq $c2
					|| exists $provides->{$c2} && $provides->{$c2}->{$_};
			}
			foreach (@{$requires->{$c2}}) {
				$cmp = +1 if $_ eq $c1
					|| exists $provides->{$c1} && $provides->{$c1}->{$_};
			}
			next unless defined $cmp;
			$precedes->{$c1}->{$c2} = +$cmp;
			$precedes->{$c2}->{$c1} = -$cmp;
		}
	}

	for ($i = 0; $i < $cnum - 1; $i++) {
		my $d = 1;
		CURRENT_COMPONENT: while (1) {
			my $c1 = $currentComponents[$i];
			for ($j = $i + $d; $j < $cnum; $j++) {
				my $c2 = $currentComponents[$j];
				my $cmp = $precedes->{$c1}->{$c2};
				next unless defined $cmp;
				if ($cmp < 0) {
					splice(@currentComponents, $j, 1);
					splice(@currentComponents, $i, 0, $c2);
					$d++;
					redo CURRENT_COMPONENT;
				}
			}
			last;
		}
	}

	return \@currentComponents;
}

# returns component key value, which can be overridden by the current variant
# or options key values. If it is array, all found values are joined together.
sub getComponentNamedValue ($$$) {
	my $self = shift;
	my $component = shift;
	my $name = shift;

	my $cc = $self->getComponentCfg($component);
	return undef unless $cc->{'used'};
	my $value = $cc->{$name};

	my $variants = $cc->{'variant'};
	if (ref($variants) eq 'ARRAY') {
		my $index = defined $cc->{'current'}?
			$cc->{'current'}: $cc->{'default'};
		$index = $index - 1;
		$index = 0 if ($index < 0 || $index >= @$variants);
		my $variant = $variants->[$index];
		my $newValue = $variant->{$name};
		if (defined $newValue) {
			errDie("Mixed '$name' types (" . (ref($value) || "scalar") . " and "
				. (ref($newValue) || "scalar") . ") for component $component")
				if defined $value && ref($value) ne ref($newValue);
			if (ref($newValue) eq 'ARRAY') {
				$value ||= [];
				push @$value, @$newValue;
			} else {
				$value = $newValue;
			}
		}
	}
	### should probably handle the named value of all current options here

	return $value;
}

sub getAdditionalImagePath ($) {
	my $self = shift;
	my $currentComponents = [keys %{$self->{cc}}];
	my @imagePathDirs = ([], []);  # one before and one after '+'

	foreach (@$currentComponents) {
		my $dirs = $self->getComponentNamedValue($_, 'external-imagepath');
		next unless defined $dirs;
		errDie("external-imagepath ($dirs) in component '$_' is not ARRAY")
			unless ref($dirs) eq 'ARRAY';
		foreach (@$dirs) {
			my $i = /wm-icons$/? 0: 1;  # we are wm-icons compatible
			next if isArrayElement($imagePathDirs[$i], $_);
			push @{$imagePathDirs[$i]}, $_;
		}
	}
	my $imagePath = "+";
	$imagePath = join(':', @{$imagePathDirs[0]}, $imagePath);
	$imagePath = join(':', $imagePath, @{$imagePathDirs[1]});
	return $imagePath;
}

sub getAllHooksRc ($) {
	my $self = shift;
	my $currentComponents = [keys %{$self->{cc}}];
	my $startStopItems = [];

	my $startHooksRc =
		"DestroyFunc FuncFvwmStartAllHooks\n" .
		"AddToFunc   FuncFvwmStartAllHooks\n";
	my $stopHooksRc =
		"DestroyFunc FuncFvwmStopAllHooks\n" .
		"AddToFunc   FuncFvwmStopAllHooks\n";
	foreach (@$currentComponents) {
		my $items = $self->getComponentNamedValue($_, 'start-stop');
		next unless defined $items;
		errDie("start-stop ($items) in component '$_' is not ARRAY")
			unless ref($items) eq 'ARRAY';
		foreach (@$items) {
			next if isArrayElement($startStopItems, $_);
			push @$startStopItems, $_;
			$startHooksRc .= "+ I FuncFvwmStart$_\n";
			$stopHooksRc  .= "+ I FuncFvwmStop$_\n";
		}
	}
	return "$startHooksRc\n$stopHooksRc\n";
}

sub getMenusAndHooksAndReadsRc ($) {
	my $self = shift;
	my $currentComponents = [keys %{$self->{cc}}];  # $self->{$componentKey};
	my $rc = "";

	my ($ownThemeListRc, $ownThemeMenuRc) = $self->getOwnThemeSubMenusRc();
	my ($allThemeListRc, $allThemeMenuRc) = $self->getAllThemeSubMenusRc();

	# add main menu entries
	$rc .= qq(DestroyMenu "MenuFvwmThemes"\n);
	$rc .= qq(AddToMenu   "MenuFvwmThemes" "Theme Management" Title\n);
	$rc .= $ownThemeListRc;
	$rc .= qq(+ "" Nop\n);
	$rc .= $allThemeListRc;
	$rc .= qq(+ "" Nop\n);
	$rc .= qq(+ "&Reset all to the default" FuncFvwmThemesReset\n);
	$rc .= qq(+ "Re&fresh the current theme" FuncFvwmThemesFresh\n);
	$rc .= "\n";

	# add own theme menus
	$rc .= "$ownThemeMenuRc\n";

	# add all theme menus
	$rc .= "$allThemeMenuRc\n";

	# add current theme hooks
	$rc .= $self->getAllHooksRc();

	# include components themselves (Read)
	$rc .= "### We will decide later whether to use full paths here.\n";
	foreach (@{$self->getSortedComponentsToRead()}) {
		my $cc = $self->getComponentCfg($_);
		my $readFile = $cc->{'read-file'};
		my $optionReadAfterward = $cc->{'option-read-afterward'};
		my $options = $cc->{'option'};
		my $optionExports = [];
		my $o;
		my ($readRc1, $readRc2) = ("", "");
		for ($o = 0; ref($options) eq 'ARRAY' && $o < @$options; $o++) {
			my $optionEntry = $options->[$o];
			my $optionFile = $optionEntry->{'file'};

			$optionFile = ((ref($cc->{'variant'}) eq 'ARRAY'
				&& $readFile =~ /^(.*\/)[^\/]+$/
				|| $readFile =~ /^(.*\/)$mainDirFile$/)?
				$1: "${readFile}:") . $optionFile;

			my $current = $optionEntry->{'current'};
			my $index = $current - 1;
			my $valueFile0 = $optionEntry->{'value'}->[$index]->{'file'};
			my $valueFile = $optionFile . (-d $optionFile? "/": "=")
				. $valueFile0;
			$valueFile =~ s/^$ROOT_PREFIX//;

			push @$optionExports, { 'f' => $valueFile, 'c' => $current };
			my $command = $optionEntry->{'read-command'};
			next if defined $command && $command eq "";

			$command ||= $defaultReadCommand;
			$command =~ s/%f/$valueFile/sg;
			$command =~ s/%F/$valueFile0/sg;
			$command =~ s/%d/getThemeDir($self->{$idKey})/seg;
			my $readAfterward = $optionEntry->{'read-afterward'};
			$readAfterward = $optionReadAfterward unless defined $readAfterward;
			($readAfterward? $readRc2: $readRc1) .= "$command\n";
#			if (($optionEntry->{'read-file'} || "") ne $valueFile) {
#				$optionEntry->{'read-file'} = $valueFile;
#				$self->setModified();
#			}
		}
		my $command = $cc->{'read-command'} || $defaultReadCommand;
		$readFile =~ s/^$ROOT_PREFIX//;
		$command =~ s/%f/$readFile/sg;
		$command =~ s/%d/getThemeDir($self->{$idKey})/seg;
		$command =~ s/%o([\d]+)(\w)/$optionExports->[$1-1]->{$2} || ""/seg;
		$rc .= "$readRc1$command\n$readRc2";
	}
	return $rc;
}

sub generateThemesRc ($) {
	my $self = shift;

	my $verStr = sprintf("%-7s", $fvwmVersion);
	my $curDateStr = `date +%d-%b-%Y`; chomp($curDateStr);
	my $userIdent = ($ENV{'USER'} || 'unknown') . '@' .
		($ENV{'HOST'} || $ENV{'HOSTNAME'} || 'somewhere');

	my $header = q{
# Auto-generated by $scriptName for $userIdent.
#
#         .================================================.
#         |                ____ _  _ _    _     | The best |
#         |  Designed for (  __X \/ X \/\/ )\/\ `----------|
#         |                ) _) \  / \    /    \           |
#         |-------------. (__) * \/ * \/\(_/\/\_) - $verStr|
#         | $curDateStr |                                  |
#         `================================================'
#  _______________________________________________________________
# (   _________________________   ________________________________)
#  ) (__  _  _  _    _  .      ) (  __ __  ____       .  ____* ___
# (   __)( \/ )( \/\/ )/\/\ * (   )(  )  )(  __)* /\/\  (  __)/ __)
#  ) ( .  \  /* \    //    \ . ) (  ) _ ( *) _). /    \* ) _).\__ \
# (___)  * \/  . \/\/(_/\/\_) (___)(__(__)(____)(_/\/\_)(____)(___/
};

	$header =~ s/\$(\w+)/eval "\$$1"/eg;

	my $contents = "# fvwm/$rcFile $version$header";
	$contents .= q{
DestroyFunc FuncFvwmResetInitFunctions
AddToFunc   FuncFvwmResetInitFunctions
+ I DestroyFunc StartFunction
+ I DestroyFunc InitFunction
+ I DestroyFunc RestartFunction
+ I DestroyFunc SessionInitFunction
+ I DestroyFunc SessionRestartFunction
+ I AddToFunc StartFunction
+ I + I FuncFvwmStartAllHooks

DestroyFunc FuncFvwmRestartFvwmTheme
AddToFunc   FuncFvwmRestartFvwmTheme
+ I KillModule FvwmTheme
+ I DestroyModuleConfig FvwmTheme: *
+ I ModuleSynchronous FvwmTheme

DestroyFunc FuncFvwmShowVersionInfo
AddToFunc   FuncFvwmShowVersionInfo
+ I FuncFvwmShowMessage "FVWM Version" "$v^n@$versionInfo@"

FuncFvwmResetInitFunctions
#FuncFvwmRestartFvwmTheme

DestroyFunc FuncFvwmThemesConfigAndUpdate
AddToFunc   FuncFvwmThemesConfigAndUpdate
+ I FvwmScript FvwmScript-NoteMessage "Theme switching"
+ I Wait FvwmScript-NoteMessage
+ I PipeRead `@$scriptFile@ $0 --pipe`
+ I FuncFvwmResetInitFunctions
+ I FuncFvwmStopAllHooks
+ I FuncFvwmRemoveBindings
+ I FuncFvwmRemoveButtonsBindings
+ I FuncFvwmRemoveAllButtons
#+ I FuncFvwmRestartFvwmTheme
#+ I DestroyModuleConfig FvwmTheme: *
+ I Read @$rcFile2@
+ I FuncFvwmStartAllHooks
+ I All (FvwmScript-NoteMessage) Delete

DestroyFunc FuncFvwmThemesReset
AddToFunc   FuncFvwmThemesReset
+ I FuncFvwmThemesConfigAndUpdate --reset

DestroyFunc FuncFvwmThemesFresh
AddToFunc   FuncFvwmThemesFresh
+ I FuncFvwmThemesConfigAndUpdate --fresh

DestroyFunc FuncFvwmThemesLoad
AddToFunc   FuncFvwmThemesLoad
+ I FuncFvwmThemesConfigAndUpdate "--load=$0"

DestroyFunc FuncFvwmThemesDrop
AddToFunc   FuncFvwmThemesDrop
+ I FuncFvwmThemesConfigAndUpdate "--drop=$0"

DestroyFunc FuncFvwmThemesOption
AddToFunc   FuncFvwmThemesOption
+ I FuncFvwmThemesConfigAndUpdate "--option=$0"

DestroyFunc FuncFvwmThemesVariant
AddToFunc   FuncFvwmThemesVariant
+ I FuncFvwmThemesConfigAndUpdate "--variant=$0"

DestroyFunc FuncFvwmThemesSetLocked
AddToFunc   FuncFvwmThemesSetLocked
+ I FuncFvwmThemesConfigAndUpdate "--set-locked=$0"


# ---------------------------------------------------
# Some global functions, extending FVWM functionality

DestroyFunc FuncFvwmStopModule
AddToFunc   FuncFvwmStopModule
+ I KillModule $0

DestroyFunc FuncFvwmStopModuleByAlias
AddToFunc   FuncFvwmStopModuleByAlias
+ I KillModule $0 $1

DestroyFunc FuncFvwmRestartModule
AddToFunc   FuncFvwmRestartModule
+ I FuncFvwmStopModule $0
+ I Module $0

DestroyFunc FuncFvwmRestartModuleByAlias
AddToFunc   FuncFvwmRestartModuleByAlias
+ I FuncFvwmStopModuleByAlias $0 $1
+ I Module $0 $1 $2

DestroyFunc FuncFvwmRemoveAllButtons
AddToFunc   FuncFvwmRemoveAllButtons
+ I Style "*" NoButton 1, NoButton 3, NoButton 5, NoButton 7, NoButton 9
+ I Style "*" NoButton 2, NoButton 4, NoButton 6, NoButton 8, NoButton 0
+ I TitleStyle Height 5

Read @$rcFile2@
};

	$contents =~ s/@\$(\w+)@/eval "\$$1"/eg;
	# use Restart to switch themes if asked
	my $f = "FuncFvwmThemesConfigAndUpdate";
	$contents =~ s{(DestroyFunc\s+$f\nAddToFunc\s+$f\n)((?:[^\n]+\n)*(\+ I PipeRead[^\n]+\n)(?:[^\n]+\n)*)}
		{$1$2\n$1$3\+ I Restart\n}s if $useRestart;
	saveFile("$workDir/$rcFile", \$contents);

	my $imagePath = "$workDir/$currentThemeSubDir/images:$workDir/images";
	$imagePath .= ":$siteDir/images" if $workDir ne $siteDir;
	$imagePath .= ":$fvwmDefaultImagePath" if $fvwmDefaultImagePath;
	$imagePath =~ s=(^|:)\Q$userDir\E(/|:|$)=$1\$FVWM_USERDIR$2=g;
	$imagePath =~ s=(^|:)\Q$userHome\E(/|:|$)=$1\$HOME$2=g;
	$imagePath =~ s=(^|:)\Q$siteDir\E(/|:|$)=$1\$FT_DATADIR$2=g;
	my $addImagePath = $self->getAdditionalImagePath();
	$imagePath .= "\nImagePath $addImagePath" if $addImagePath =~ /\+/;
	my $menusAndHooksRc = $self->getMenusAndHooksAndReadsRc();

	$contents = "# fvwm/$rcFile2 $version$header";
	$contents .= qq{
SetEnv FT_DATADIR '$siteDir'
ImagePath $imagePath

FuncFvwmRestartFvwmTheme

$menusAndHooksRc
Mouse 2 A CM Menu MenuFvwmThemes
};
	saveFile("$workDir/$rcFile2", \$contents);
}

# ----------------------------------------------------------------------------

package main;

my $site = 0;
my $showThemes = 0;
my $showComponents = 0;
my $showDir = 0;
my $themes = [];
my $showValues = [];
my $component = undef;
my $expandFile = undef;
my $fresh = 0;
my $reset = 0;
my $loadComponents = [];
my $dropComponents = [];
my $options = {};
my $variants = {};
my $setLockeds = {};
my $comMode = 0;
my $comName = "config";
my $comPid = 0;
my $install = 0;
my $fvwmscript = 0;
my $onlySite = 0;
my $onlyUser = 0;
my $configCenter = 0;

GetOptions(
	"help|h"      => \&showHelp,
	"version"     => \&showVersion,
	"site|s"      => \$site,
	"theme=s@"    => $themes,
	"show-themes!"     => \$showThemes,
	"show-components!" => \$showComponents,
	"show-dir!"        => \$showDir,
	"component=s"      => \$component,
	"show-value=s@"    => $showValues,
	"expand-rc:s" => \$expandFile,
	"fresh|f"     => \$fresh,
	"reset|r"     => \$reset,
	"load|l=s@"   => $loadComponents,
	"drop|d=s@"   => $dropComponents,
	"option=s%"   => $options,
	"variant=s%"  => $variants,
	"set-locked=s%" => $setLockeds,
	"pipe"        => \$pipe,
	"install|i"   => \$install,
	"com-mode"    => \$comMode,
	"com-name=s"  => \$comName,
	"fvwmscript"  => \$fvwmscript,
	"only-site"	  => \$onlySite,
	"only-user"	  => \$onlyUser,
	"config-center" => \$configCenter,
) || wrongUsage();

shift @searchPath if $site;
$workDir = $searchPath[0];
$siteDir = $searchPath[-1];

if ($install) {
	my $themesDir = "$workDir/$themesSubDir";
	my $file;
	foreach $file (@ARGV) {
		errDie("No such file $file") unless -f $file;
		my ($dir, $theme, $ext) = $file =~ m:^(.*/|)([^/]+)\.tar\.(gz|bz2)$:;
		errDie("File '$file' is not a .tar.gz or .tar.bz2")
			unless defined $dir || defined $theme || defined $ext;
		errDie("Theme name '$theme' contains unacceptable symbols")
			unless $theme =~ /^[\w\d-]+$/;
		my $zcatProg = $ext eq 'gz'? "gzip -cd": "bzip2 -cd";
		open(TAR, "$zcatProg $file| tar xvf - -C '$themesDir' $theme|")
			|| sysDie("Can't open untar for $file");
		my $output = join('', <TAR>);
		close(TAR) || errDie("\nErrors while installing $file");
		print "Theme $theme is successfully installed in $themesDir\n";
	}
	exit(0);
}

errDie("Unexpected parameters @ARGV") if @ARGV;

if (defined $expandFile) {
	$expandFile ||= $rcFile;
	unshift @searchPath, ".";
	print getExpandedRc($expandFile);
	exit(0);
}

showThemeComponents($themes, $showComponents, $component, $fvwmscript,
	$onlySite, $onlyUser) if $showThemes || $showComponents;
showThemeDirs($themes) if $showDir;
showThemeComponentValues($component, $showValues) if $component && @$showValues;

wrongUsage() unless
	$fresh || $reset ||
	@$loadComponents || @$dropComponents ||
	keys %$options || keys %$variants ||
	keys %$setLockeds || $comMode || $configCenter;

my $cfg = Fvwm::ThemeCfg->new('current', $reset? 'default': 'current', $fresh);
if ($comMode) {
	$comPid = $comName;
	$comPid =~ s/config-//;
	$comPid = 0 if ($comPid !~ /^\d+$/);
	$cfg->comLoop($comName);
	# we never return here
}
if ($configCenter) {
	$cfg->showInfoForConfigCenter();
	# we never return here
}
$cfg->useNewComponents($loadComponents, $dropComponents);
$cfg->setNewComponentValues($options, $variants, $setLockeds);
$cfg->generateThemesRc();
$cfg->save() if $cfg->isModified();

exit(0);

#-----------------------------------------------------------------------------
#
# build info for the config center
#
#-----------------------------------------------------------------------------

sub showInfoForConfigCenter {
	my $self = shift;
	my $return = "";
	my $user = "";
	my $site = "";
	my $cc = $self->{'cc'};

	my @components = ("globalfeel","bindings");
	my $allThemes = getAllThemes();
	my $userThemes = getAllThemes(0, 1);

	### all this hardcoding is unacceptable, should be defuned in .cfg
	### olicha: How? This is just a tmp solution it is not unacceptable: 
	### Currently fvwm-themes-config can only reload everythings ... 
	### So I do that so that the Config Center can be usable.

	$return .= "$cc->{'styles'}->{'read-file'}\n"
		if defined $cc->{'styles'}->{'read-file'};
	$return .= "$cc->{'extra-styles'}->{'read-file'}\n"
		if defined $cc->{'extra-styles'}->{'read-file'};
	my $file = "";
	$file = $cc->{'modules/main'}->{'read-file'}
		if defined $cc->{'modules/main'}->{'read-file'};
	$file = $cc->{'modules'}->{'read-file'}
		if defined $cc->{'modules'}->{'read-file'} && $file eq "";
	if ($file ne "") {
		$file = "$file:styles";
		$return .= "$file\n" if -f $file;
	}
	$file = "";
	$file = $cc->{'settings/autoraise'}->{'read-file'}
		if defined $cc->{'settings/autoraise'}->{'read-file'};
	$return .= "$file\n" if $file =~ /modules$/ && -f $file;
	$return .= "END\n";

	foreach (@$userThemes) {
		$return .= "$_\n";
	}
	my $c;
	foreach $c (@components) {
		$return .= "configuration of $c for the Config Center\n";
		my $theme = $cc->{$c}->{'theme'} || "";
		$return .= "$theme\n";
		foreach $theme (@$allThemes) {
			my ($comps, $groups) = getThemeComponentsAndGroups($theme);
			next unless isArrayElement($comps, $c);
			$return .= "$theme\n";
			my $themeCfg = new Fvwm::ThemeCfg($theme);
			my $dd = $themeCfg->getComponentCfg($c);
			my $file = $dd->{'read-file'} || "";
			$return .= "$file\n";
			my $options  = $cc->{$c}->{'option'};
			if (ref($options) eq 'ARRAY') {
				$return .= "OPTIONS\n";
				my $opt;
				foreach $opt (@$options) {
					$return .= "$opt->{'file'}:$opt->{'current'}\n";
				}
				$return .= "END\n";
			}
		}
	}

	print $return;
	exit(0);
}

#-----------------------------------------------------------------------------
#
# communication loop
#
#-----------------------------------------------------------------------------
# All that follows need a clean up.

sub comLoop {
	my $self = shift;
	my $outFifo = ".tmp-com-out-" . $comName;
	my $inFifo = ".tmp-com-in-"  . $comName;
	my $lockFifo = ".tmp-com-lock-" . $comName;
	my $command = "";
	my $return = "";

	my $maxLength = 21;
	my $maxLengthOpt = 29;
	my %componentToLoad = ();
	my %optionToSet = ();
	my %variantToSet = ();
	my %componentToLock = ();
	my %componentToDrop = ();

	my $allThemes = getAllThemes();
	my $tmp = $self->{$componentKey};
	my @currentComponents = sort @$tmp;
	my $CC = $self->{cc};

	my $settingsConfig = getAllSubComponents($self,"default","settings",0,0);

	my ($currentSession,$sessionList,$uptime) =  sessionInfo();

	chdir($userDir) || die "No FvwmConfigHome $userDir";
	unlink($lockFifo);
	unlink($inFifo);
	myMakeFifo($lockFifo);

	while(1) {

		eval {
			local $SIG{ALRM} = \&checkScript;
			alarm(10);
			# block until com want to communicate
			open(LOCK,">$lockFifo") || die "cannot write fifo $lockFifo";
			alarm(0);
			close(LOCK);
		};
		if ($@ =~ /^cannot/) {
			print STDERR "$comName: cannot write fifo $lockFifo\n";
			unlink("$lockFifo");
			exit(1);
		}
		if ($@ =~ /^NoScript/) {
			print STDERR "$comName: No more FvwmScript-ThemesCenter: exit!\n";
			unlink("$lockFifo");
			exit(1);
		}
		if ($@ =~ /^Script/) {
			next;
		}

		# read the command.
		eval {
			local $SIG{ALRM} = sub { die "Timeout" };
			alarm(10);
			# block unless com is ready to write on $outFifo
			open(IN,"$outFifo") || die "cannot open $outFifo";
			alarm(0);
			($command)=(<IN>);
			close(IN);
		};
		if ($@ =~ /^cannot/) {
			print STDERR "$comName: cannot read fifo $lockFifo\n";
			unlink("$lockFifo");
			exit(1);
		}
		if ($@ =~ /^Timeout/) {
			print STDERR "$comName: com give an unvalide unlock!\n";
			next;
		}

		# build the answer
		chomp($command);
		my $return = "";
		# -----------------------------------------
		if ($command =~ /^themes-list/) {
			my $theme;
			foreach $theme (@$allThemes) {
				my ($components, $groups) = getThemeComponentsAndGroups($theme);
				my $used = 0;
				my $set = 0;
				my $useFlag = "";
				foreach (@$components) {
					my $used0 = 0;
					$used0 = 1 if isArrayElement(\@currentComponents, $_) &&
						defined $self->{'cc'}->{$_} &&
						($self->getComponentCfg($_)->{$themeKey} || "") eq $theme;
					$used ||= $used0;
				}
				my $key;
				foreach $key (keys %componentToLoad) {
					$set = 1 if $componentToLoad{$key} eq $theme;
				}
				my $l = $maxLength - length($theme);
				$l = 1 if $l < 1;
				$useFlag .= " " x $l . "(" if $used || $set;
				$useFlag .= "used" if $used;
				$useFlag .= "/" if $used && $set;
				$useFlag .= "set" if $set;
				$useFlag .= ")" if $used || $set;
				$return .= "$theme$useFlag|";
			}
			$return =~ s/\|$//;
		}
		# -----------------------------------------
		elsif ($command =~ /^theme-components\s+(\d+)$/) {
			my $index = $1 - 1;
			my $theme = $$allThemes[$index];
			my ($components, $groups) = getThemeComponentsAndGroups($theme);
			foreach (@$components) {
				next if $_ eq "settings";
				my $useFlag = "";
				my $set = 0;
				my $unset = 0;
				my $used = isArrayElement(\@currentComponents, $_) &&
					defined $self->{'cc'}->{$_} &&
					($self->getComponentCfg($_)->{$themeKey} || "") eq $theme;
				$set = 1 if defined $componentToLoad{$_} &&
					$componentToLoad{$_} eq $theme;
				$unset = 1 if defined $componentToLoad{$_} && !$set;
				my $l = $maxLength - length($_);
				$l = 1 if $l < 1;
				$useFlag .= " " x $l . "(" if $used || $set;
				$useFlag .= "used" if $used && !$unset;
				$useFlag .= "unset" if $used && $unset;
				$useFlag .= "/" if $used && $set;
				$useFlag .= "set" if $set;
				$useFlag .= ")" if $used || $set;
				$return .= "$_$useFlag|";
			}
			$return =~ s/\|$//;
			$return .= "\n$theme\n" . getThemeDir($theme);
		}
		# -----------------------------------------
		elsif ($command =~ /^current-config$/) {
			foreach (@currentComponents) {
				next if $_ eq "settings";
				my $dd;
				my $usedString;
				my $set = 0;
				my $theme;
				my $cc;
				if (defined $self->{'cc'}->{$_}) { 
					$cc = $self->getComponentCfg($_);
					$theme = $cc->{'theme'};
					$usedString = ($cc->{'locked'}) ? "Locked: " : "Used: ";
					$usedString .= "$theme";
				} else {
					$usedString = "Not Used";
					$set = -1;
				}
				my $stateString = "";
				my $setOrLock = "Set: ";
				if (defined $componentToLoad{$_}) {
					$set = 1;
					$theme = $componentToLoad{$_};
					$setOrLock = "Lock: " 
						if (defined $componentToLock{$_} && 
							$componentToLock{$_} == 1);
				} elsif (defined $componentToDrop{$_}) {
					$set = -1;
				} elsif (defined $componentToLock{$_}) {
					$set = 2;
					$setOrLock = ($componentToLock{"$_"}) ? "Lock it" : "Unlock it";
					$setOrLock = "" if ($componentToLock{"$_"} == $cc->{'locked'})
				}
				$usedString = "(" . $usedString . ")" if $set == 1;
				my $l = $maxLength - length($usedString);
				$l = 1 if $l < 1;
				$stateString = " " x $l . $setOrLock . "$componentToLoad{$_}" 
					if $set == 1;
				$stateString = " " x $l . "Drop it!" 
					if $set == -1;
				$stateString = " " x $l . $setOrLock
					if $set == 2;
				my $l1 = $maxLength - 3 - length($stateString) + $l;
				$l1 = 1 if $l1 < 1;
				if ($set == 1) {
					my $themeCfg = new Fvwm::ThemeCfg($componentToLoad{$_});
					$dd = $themeCfg->getComponentCfg($_);
					$theme = $dd->{'theme'};
				} elsif ($set == 0 || $set == 2) {
					$dd = $self->getComponentCfg($_);
				}
				my $contains = $dd->{'contains'};
				my $options  = $dd->{'option'};
				my $variants = $dd->{'variant'};
				my $propertiesStr = "";
				my $t = "";
				if (ref($options) eq 'ARRAY') {
					$propertiesStr = " " x $l1 ."Opt";
					my $opt;
					foreach $opt (@$options) {
						my $optFile = $opt->{'file'};
						$t = "(S)" if defined $optionToSet{"$theme/$_:$optFile"};
					}
				}
				if (ref($variants) eq 'ARRAY') {
					$propertiesStr = " " x $l1 . ($propertiesStr? "V/O": "Var");
					my $v;
					foreach $v (@$variants) {
						$t = "(S)" if defined $variantToSet{"$theme/$_"};
					}
				}
				elsif (ref($contains) eq 'ARRAY') {
					$propertiesStr = " " x $l1 ."Sub";
					my $c;
					# dropped ?
					foreach $c (@$contains) {
						$t = "(S)" if defined $variantToSet{"$theme/$_/$c"};
					}
				}
				$propertiesStr .= $t;
 				$l = $maxLength - length($_);
				$l = 1 if $l < 1;
				$return .= "$_" . " " x $l . $usedString . $stateString .
					$propertiesStr . "|";
			}
			$return =~ s/\|$//;
		}
		# -----------------------------------------
		elsif ($command =~ /^current-comp-name\s+(\d+)$/) {
			my $dd = [];
			my $index = $1;
			my $i = 1;
			my ($comp,$theme,$hasProperties,$drop,$set,$lock) = 
				("","","0","0","0","0");
			foreach (@currentComponents) {
				next if $_ eq "settings";
				if ($i == $index) {
					$comp = "$_";
					if (defined $componentToLoad{$_}) {
						$set = 1;
						$theme = "$componentToLoad{$_}";
					} else {
						my $cc = $self->getComponentCfg($_);
						$theme = "$cc->{'theme'}";
						$lock = "$cc->{'locked'}";
					}
					if (defined $componentToLock{$_}) {
						$lock = $componentToLock{$_};
					}
				}
				$i++;
			}
			if ($set) {
				my $themeCfg = new Fvwm::ThemeCfg($theme);
				$dd = $themeCfg->getComponentCfg($comp);
			} else {
				$dd = $self->getComponentCfg($comp);
			}
			my $contains = $dd->{'contains'};
			my $options  = $dd->{'option'};
			my $variants = $dd->{'variant'};
			if (ref($options) eq 'ARRAY') {
				$hasProperties = 1;
				my $opt;
				foreach $opt (@$options) {
					my $optFile = $opt->{'file'};
					$set = 1 if defined $optionToSet{"$theme/$comp:$optFile"};
				}
			}
			if (ref($variants) eq 'ARRAY') {
				$hasProperties = 1;
				my $c;
				foreach $c (@$variants) {
					$set = 1 if defined $variantToSet{"$theme/$comp"};
				}
			}
			elsif (ref($contains) eq 'ARRAY') {
				$hasProperties = 1;
				my $c;
				# dropped ?
				foreach $c (@$contains) {
					$set = 1 if defined $variantToSet{"$theme/$comp/$c"};
				}
			}
			### fixe me: How to know if the component is drop-able??
			$drop = 1;
			$return = "$comp\n$theme\n$hasProperties$set$drop$lock";
		}
		# -------------------------------------------------
		elsif ($command =~ /^restore\s+(.+)$/) {
			my $comp = $1;
			my $theme = "";
			my $cc;
			delete($componentToLock{$comp}) 
					if (defined $componentToLock{$comp});
			if (defined $componentToLoad{$comp}) {
				$theme=$componentToLoad{$comp};
				delete($componentToLoad{$comp});
				my $i = 0;
				my @tmp = @currentComponents;
				foreach (@tmp) {
				 	splice @currentComponents, $i, 1
						if (! defined $self->{'cc'}->{$_} && $_ eq $comp);
					$i++;
				}
				my $themeCfg = new Fvwm::ThemeCfg($theme);
				$cc = $themeCfg->getComponentCfg($comp);
			} else {
				delete $componentToDrop{$comp}
					if (defined $componentToDrop{$comp});
				$cc = $self->getComponentCfg($comp);
				$theme = $cc->{'theme'};
			}
			my $contains = $cc->{'contains'};
			my $options  = $cc->{'option'};
			my $variants = $cc->{'variant'};
			if (ref($options) eq 'ARRAY') {
				foreach (keys %optionToSet) {
					delete $optionToSet{$_} if /^$theme\/$comp:/;
				}
			}
			if (ref($variants) eq 'ARRAY') {
				foreach (keys %variantToSet) {
					delete $variantToSet{$_} if /^$theme\/$comp/;
				}
			}
			if (ref($contains) eq 'ARRAY') {
				foreach (keys %variantToSet) {
					delete $variantToSet{$_} if /^$theme\/$comp\//;
				}				
			}
		}
		#----------------------------------------
		elsif ($command =~ /^lock\s+(.+)$/) {
			my $comp = $1;
			my $cc;
			if (defined $self->{'cc'}->{$comp}) { 
				my $cc = $self->getComponentCfg($comp);
		 		if (! defined $componentToLoad{$comp}) {
					if (defined $componentToLock{$comp}) {
						delete $componentToLock{$comp};
					} else {
						$componentToLock{$comp}= ($cc->{'locked'}) ? 0:1;
					}
				} else {
					if (defined $componentToLock{$comp}) {
						delete $componentToLock{$comp};
					} else {
						$componentToLock{$comp}= 1;
					}
				}
			} elsif (defined $componentToLock{$comp} && 
				defined $componentToLoad{$comp}) {
				delete $componentToLock{$comp};
			} elsif (defined $componentToLoad{$comp}) { 
				$componentToLock{$comp} = 1;
			}
		}
		#----------------------------------------
		elsif ($command =~ /^load-all\s+(.+)$/) {
			my ($components, $groups) = getThemeComponentsAndGroups($1);
			foreach (@$components) {
				next if ((defined $self->{'cc'}->{$_} && 
					$self->{'cc'}->{$_}->{'locked'} && 
					! defined $componentToLoad{$_}) || 
					(defined $componentToLock{$_} && $componentToLock{$_})); 
				$componentToLoad{$_} = $1;
				updateCurrentComponents(\@currentComponents,$_);
			}
		}
		#----------------------------------------------
		elsif ($command =~ /^load-main-look\s+(.+)$/) {
			my ($components, $groups) = getThemeComponentsAndGroups($1);
			my $g;
			foreach $g (@$groups) {
				my $name = $g->{'name'};
				if ($name eq "basic look") {
					my $groupComponents = $g->{$componentKey};
					foreach (@$groupComponents) {
						delete $componentToLock{$_} if defined $componentToLock{$_};
						delete $componentToDrop{$_} if defined $componentToDrop{$_};
						$componentToLoad{$_} = $1;
						updateCurrentComponents(\@currentComponents,$_);
					}
				}
			}
		}
		# ----------------------------------------------
		elsif ($command =~ /^load-one\s+(.+)\s+(.+)$/) {
			$componentToLoad{$2} = $1;
			delete $componentToLock{$2} if defined $componentToLoad{$2};
			delete $componentToDrop{$2} if defined $componentToDrop{$2};
			updateCurrentComponents(\@currentComponents,$2);
		}
		# -----------------------------------------------------
		elsif ($command =~ /^component-name\s+(.+)\s+(\d+)$/) {
			my ($components, $groups) = getThemeComponentsAndGroups($1);
			my $i = 1;
			foreach (@$components) {
				next if $_ eq "settings";
				$return = $_ if $i == $2;
				$i++;
			}
		}
		# -----------------------------------------
		elsif ($command =~ /^apply-ts-cmd-opts$/) {
			my @compLoad = ();
			my @compDrop = ();
			my %compLock = ();
			my %optLoad = ();
			my %variantLoad = ();
			my @deleteOpt = ();
			my @deleteVariant = ();
			my @deleteLock = ();
			foreach (keys %componentToLoad) {
				$return .= "--load $_\@$componentToLoad{$_} ";
				push @compLoad, "$_\@$componentToLoad{$_}";
			}
			foreach (keys %componentToDrop) {
				$return .= "--load $_\@$componentToDrop{$_} ";
				push @compDrop, "$_\@$componentToDrop{$_}";
			}
			$self->useNewComponents(\@compLoad, \@compDrop);
			foreach (keys %componentToLock) {
				if (defined $CC->{$_}) {
					$return .= "--set-locked $_=$componentToLock{$_} ";
					$compLock{$_}=$componentToLock{$_};
					push @deleteLock, $_;
				}
			}
			foreach (keys %optionToSet) {
				my $opt = substr($_,index($_,"/")+1);
				my $theme = substr($_,0,index($_,"/"));
				my $comp = substr($opt,0,rindex($opt,":"));
				if (defined $CC->{$comp} && $CC->{$comp}->{'theme'} eq $theme) {
					$return .= "--option $opt=$optionToSet{$_} ";
					$optLoad{$opt} = "$optionToSet{$_}";
					push @deleteOpt, $_;
				}
			}
			foreach (keys %variantToSet) {
				my $comp = substr($_,index($_,"/")+1);
				my $theme = substr($_,0,index($_,"/"));
				if (defined $CC->{$comp} && $CC->{$comp}->{'theme'} eq $theme) {
					$return .= "--variant $comp=$variantToSet{$_} ";
					$variantLoad{$comp} = "$variantToSet{$_}";
					push @deleteVariant, $_;
				}
			}
			$self->setNewComponentValues(\%optLoad, \%variantLoad, \%compLock);
			%componentToLoad = ();
			%componentToDrop = ();
	  		$tmp = $self->{$componentKey};
			@currentComponents = sort @$tmp;
			foreach (@deleteOpt) { delete $optionToSet{$_} }
			foreach (@deleteVariant) { delete $variantToSet{$_} }
			foreach (@deleteLock) { delete $componentToLock{$_} }
			$settingsConfig = getAllSubComponents($self,"default","settings",0,0);
		}
		# -----------------------------------------
		elsif ($command =~ /^options-variants\s+(.+)\s+(.+)\s+(\d+)$/) {
			my $comp = $1;
			my $theme = $2;
			my $o = $3-1;
			my $themeCfg = new Fvwm::ThemeCfg($theme);
			my $cc = $themeCfg->getComponentCfg($comp);
			my $contains = $cc->{'contains'};
			my $options  = $cc->{'option'};
			my $variants = $cc->{'variant'};
			if (ref($options) eq 'ARRAY' && defined $options->[$o]) {
				my $optionEntry = $options->[$o];
				my $i = getOptionIndex($self,$theme,$comp,$optionEntry,$o);
				my $optFile = $optionEntry->{'file'};
				my $isSet = -1;
				$isSet = $optionToSet{"$theme/$comp:$optFile"} 
					if defined $optionToSet{"$theme/$comp:$optFile"};
				my $values = $optionEntry->{'value'};
				$return = 
					variantsOptionsScriptList($values,$i,$isSet,$maxLengthOpt);
			}
			elsif (ref($variants) eq 'ARRAY') {
				my $i = getVariantIndex($self,$theme,$comp,@$variants);
				my $isSet = -1;
				$isSet = $variantToSet{"$theme/$comp"} 
					if defined $variantToSet{"$theme/$comp"};
				$return = 
					variantsOptionsScriptList($variants,$i,$isSet,$maxLengthOpt);
			}
			elsif (ref($contains) eq 'ARRAY' && defined $contains->[$o]) {
				my $c = $contains->[$o];
				my $dd = $themeCfg->getComponentCfg("$comp/$c");
				my $sVariants = $dd->{'variant'};
				if (ref($sVariants) eq 'ARRAY') {
					my $i = getVariantIndex($self,$theme,"$comp/$c",@$sVariants);
					my $isSet = -1;
					$isSet = $variantToSet{"$theme/$comp/$c"}
						if defined $variantToSet{"$theme/$comp/$c"};
					$return = 
						variantsOptionsScriptList($sVariants,$i,$isSet,$maxLengthOpt);
				}
				$return .= "Drop This component";
			}
			$return =~ s/\|$//;
		}
		# --------------------------------------------
		elsif ($command =~ /^options\s+(.+)\s+(.+)$/) {
			my $comp = $1;
			my $theme = $2;
			my $themeCfg = new Fvwm::ThemeCfg($theme);
			my $cc = $themeCfg->getComponentCfg($comp);
			my $contains = $cc->{'contains'};
			my $options  = $cc->{'option'};
			my $variants = $cc->{'variant'};
			my $i = 0;
			my $type = "";
			if (ref($options) eq 'ARRAY' && ref($variants) eq 'ARRAY') {
				$type = "Options & Variants";
			}
			if (ref($options) eq 'ARRAY') {
				my $opt;
				$type = "Options" if $type eq "";
				foreach $opt (@$options) {
					$i++;
					$return .= "$opt->{'name'}|";
				}
			}
			if (ref($variants) eq 'ARRAY') {
				$type = "Variants" if $type eq "";
				$i++;
				$return .= "$comp variants"
			}
			elsif (ref($contains) eq 'ARRAY') {
				$type = "Sub Components";
				my $c;
				foreach $c (@$contains) {
					$i++;
					my $name = $themeCfg->getComponentCfg("$comp/$c")->{'name'};
					$name = $name || $c;
					$return .= "$name|";
				}
			}
			$return = "$i\n$type\n$return";
			$return =~ s/\|$//;
		}
		# --------------------------------------------
		elsif ($command =~ /^set-options\s+(.+)\s+(.+)\s+(\d+)\s+(\d+)$/) {
			my $comp = $1;
			my $theme = $2;
			my $o = $3-1;
			my $value = $4;
			my $themeCfg = new Fvwm::ThemeCfg($theme);
			my $cc = $themeCfg->getComponentCfg($comp);
			my $contains = $cc->{'contains'};
			my $options  = $cc->{'option'};
			my $variants = $cc->{'variant'};
			if (ref($options) eq 'ARRAY' && defined $options->[$o]) {
				my $optionEntry = $options->[$o];
				my $optionFile = $optionEntry->{'file'};
				$optionToSet{"$theme/$comp:$optionFile"} = $value;
			} elsif (ref($variants) eq 'ARRAY') {
				$variantToSet{"$theme/$comp"}="$value";
			} elsif (ref($contains) eq 'ARRAY' && defined $contains->[$o]) {
				my $c = $contains->[$o];
				my $dd = $themeCfg->getComponentCfg("$comp/$c");
				my $subVariants = $dd->{'variant'};
				if (ref($subVariants) eq 'ARRAY') {
					$variantToSet{"$theme/$comp/$c"}="$value";
				} else {
					# Need To Drop ..
				}
			}
		}
		elsif ($command =~ /^update\s+(\d+)$/) {
			my $fresh = $1;
			$cfgFileCache = {};
			$self = Fvwm::ThemeCfg->new('current', 'current', $fresh);
			$allThemes = getAllThemes();
			$tmp = $self->{$componentKey};
			@currentComponents = sort @$tmp;
			$CC = $self->{cc};
			$settingsConfig = getAllSubComponents($self,"default","settings",0,0);
			($currentSession,$sessionList,$uptime) = sessionInfo();
		}
		#-----------------------------------------------------------------------
		# GS
		# -----------------------------------------
		elsif ($command =~ /^settings-config$/) {
			my $config;
			foreach $config (@$settingsConfig) {
				my $l = 26 - length($config->{'name'});
				$l = 1 if $l <= 0;
				$return .= $config->{'name'} . " "x$l . "U: " .
					$config->{'current'};
				if (defined $variantToSet{"default/$config->{comp}"}) {
					my $j = $variantToSet{"default/$config->{comp}"};
					my $themeCfg = new Fvwm::ThemeCfg('default');
					my $dd = $themeCfg->getComponentCfg("$config->{comp}");
					my $variants = $dd->{'variant'};
					my $set = $variants->[$j-1]->{'name'};
					$l = 28 - length($config->{'current'});
					$l = 1 if $l <= 0;
					$return .= " "x$l . "S: $set";
				}
				$return .=	"|";
			}
			$return =~ s/\|$//;
		}
		# -----------------------------------------
		elsif ($command =~ /^settings-comp\s+(\d+)$/) {
			$return = "$settingsConfig->[$1-1]->{'name'}\n";
			$return .= "$settingsConfig->[$1-1]->{'comp'}\n";
			my $isSet = 0;
			$isSet = 1 
				if defined $variantToSet{"default/$settingsConfig->[$1-1]->{comp}"};
			$return .= $isSet;
			
		}
		#-----------------------------------------------------------------------
		# SM
		elsif ($command =~ /^session-info$/) {
			$return = "$currentSession\n$uptime\n";
			my $configList = "Default|";
			foreach(@$sessionList) {
				$return .= "$_";
				$configList .= "$_|";
				$return .= " "x10 . "(Current)" if $_ eq $currentSession;
				$return .= "|";
			}
			$return =~ s/\|$//;
			$configList =~ s/\|$//;
			$return .= "\n$configList";
		}
		# --------------------------------------
		elsif ($command =~ /^session-uptime$/) {
			my $current = "$userDir/themes/current";
			$return = upTime($current,1);
		}
		# --------------------------------------
		elsif ($command =~ /^session-name\s+(\d+)$/) {
			my $index = $1-1;
			$return = $$sessionList[$index] if defined $$sessionList[$index] &&
				$index >= 0;
		}
		# --------------------------
		elsif ($command =~ /^edit-session\s+(.+)$/) {
			my $opt = $1;
			my @opt = split(":",$opt); # orig_session:new_session_name:int
												# int is the value of Widget 52
			$return = "Err1" if $#opt != 2;
			$return = "Err1" if $opt[0] eq "";
			foreach (@$sessionList) {
				next if $opt[1] eq $opt[0];
				$return = "Err2" if $opt[1] eq $_;
			}
			if ($return eq "") {
				# $themesSubDir = themes
				# $currentThemeName = current
				# $currentThemeSubDir = themes/current
				my $orig = "$currentThemeSubDir-$opt[0]";
				my $dest = "$currentThemeSubDir-$opt[1]";
				my $i = 1;
				my $dirConfig = "";
				foreach (@$sessionList) {
						$i++;
						$dirConfig = "$currentThemeSubDir-$$sessionList[$i-2]" 
							if $i == $opt[2];
				}
				if ($dest ne $orig) {
					rename($orig, $dest);
				}
				# change the symlinks if needed
				if ($currentSession eq $opt[0] && $dest ne $orig) {
					chdir("$themesSubDir");
					unlink("$currentThemeName") ||
						sysDie("Can't unlink $userDir/$currentThemeSubDir");
					symlink("$currentThemeName-$opt[1]","$currentThemeName");
					chdir("$userDir");
					unlink("themes-rc-2") ||
						sysDie("Can't unlink $userDir/themes-rc-2");
					symlink("$currentThemeSubDir-$opt[1]/themes-rc-2","themes-rc-2");
				}
				if ($dirConfig ne $orig) {
					system("rm -rf themes/current-$opt[1]/images");
					#unlink("themes/current-$opt[1]/theme.cfg");
					#unlink("themes/current-$opt[1]/$rcFile2");
					if ($dirConfig eq "") {
						# need to use the default config
						system("cp -f $datadir/fvwm/$rcFile2 $dest/; " .
							"cp -f $datadir/fvwm/themes/current/theme.cfg $dest/");
					} else {
						chdir("$dirConfig");
						system("cp -af * ../../" . $dest);
						chdir("$userDir");
					}
				}
				($currentSession,$sessionList,$uptime) = sessionInfo();
				 $i = 0;
				 foreach (@$sessionList) {
				 	$i++;
					$return = $i if $_ eq $opt[1];
				 }
			}
		}
		# --------------------------
		elsif ($command =~ /^add-session\s+(.+)$/) {
			my $opt = $1;
			my @opt = split(":",$opt);
			$return = "Err1" if $#opt != 1;
			$return = "Err1" if $opt[0] eq "";
			foreach (@$sessionList) {
				$return = "Err2" if $opt[0] eq $_;
			}
			
			if ($return eq "") {
				my $dirConfig = "";
				my $i = 1;
				foreach (@$sessionList) {
						$i++;
						$dirConfig = "themes/current-$$sessionList[$i-2]" 
							if $i == $opt[1];
				}
				makePath("$userDir/$currentThemeSubDir-$opt[0]");
				if ($dirConfig ne "") {
					chdir("$dirConfig");	
					system("cp -rdp * ../../$currentThemeSubDir-$opt[0]");
					chdir("$userDir");
				}
				($currentSession,$sessionList,$uptime) = sessionInfo();
				$i = 0;	
				foreach (@$sessionList) {
					$i++;
					$return = $i if $_ eq $opt[0];
				}
			}
		}
		# --------------------------
		elsif ($command =~ /^remove-session\s+(.+)$/) {
			my $session = $1;
			my $i = 0;
			my $index = -1;
			foreach (@$sessionList) {
				$index = $i if $session eq $_;
				$i++;
			}
			if ($index == -1) {
				$return = "$session\nErr3";
			} else {
				system("rm -rf themes/current-$session");
				($currentSession,$sessionList,$uptime) = sessionInfo();
				if (! defined $$sessionList[$index]) {
					$index--;
				}
				$return = "$$sessionList[$index]\n";
				$index++;
				$return .= $index;
			}
		}
		# --------------------------
		elsif ($command =~ /^apply-sm\s+(.+)$/) {
			my $newSession = $1;
			my $create = 0;
			chdir("$userDir/$themesSubDir");
			unlink("$currentThemeName") || 
				sysDie("Can't unlink $userDir/$currentThemeSubDir");
			makePath("$userDir/$themesSubDir/current-$newSession") 
				if ! -d "current-$newSession";
			symlink("current-$newSession","$currentThemeName");
			chdir("$userDir");
			if (! -f "$currentThemeSubDir-$newSession/$rcFile2") {
				system("touch $currentThemeName-$newSession/$rcFile2");
				$create = 1;
			}
			unlink("themes-rc-2");
			symlink("$currentThemeSubDir-$newSession/themes-rc-2","themes-rc-2");
			if ($create) {
				system("fvwm-themes-config --reset");
				system("fvwm-themes-config --load \@personal 2>/dev/null");
			}
			($currentSession,$sessionList,$uptime) = sessionInfo();
			$cfgFileCache = {};
			$self = Fvwm::ThemeCfg->new('current', 'current', 1);
			$allThemes = getAllThemes();
			$tmp = $self->{$componentKey};
			@currentComponents = sort @$tmp;
			$CC = $self->{cc};
			$settingsConfig = getAllSubComponents($self,"default","settings",0,0);
			# discard the change in ts
			%componentToLoad = ();
			%componentToDrop = ();
			%componentToLock = ();
			%optionToSet = ();
			%variantToSet = ();
		}
		# --------------------------
		elsif ($command eq "exit") {
			unlink($lockFifo);
			exit(0);
		}
		else {
			print STDERR "$comName: unknown command $command\n";
			$return = "0";
		}
		
		# answer
		myMakeFifo($inFifo);
		eval {
			local $SIG{ALRM} = sub { die "Timeout" };
			alarm(10);
			# this line block until com take the answer
			open(OUT,">$inFifo") || die "cannot write fifo $inFifo";
			alarm(0);
			print OUT "ok\n" . $return;
			close(OUT);
			unlink($inFifo);
		};
		if ($@ =~ /cannot/) {
			print STDERR "$comName: cannot write on fifo $inFifo\n";
			unlink($lockFifo);
			unlink($inFifo);
			exit(1);
		}
		if ($@ =~ /Timeout/) {
			print STDERR "$comName: com do not read my answer!\n";
		}

	}
}

#----------------------------------------------------------------------------
# useful functions which may be useful not only for the com loop

#--------------------------------------
sub getOptionIndex {
	my $self = shift;
	my $theme = shift;
	my $comp = shift;
	my $optionEntry = shift;
	my $o = shift;

	my $themeCfg = new Fvwm::ThemeCfg($theme);
	my $cc = $themeCfg->getComponentCfg($comp);
	my $CC = $self->{cc};
	my $_core = $self->getComponentCfg("_core");

	my $values = $optionEntry->{'value'};
	my $index = 
	  $optionEntry->{'current'} || ($optionEntry->{'default'} || 1);
	# see if $comp@$theme is "current"
	if (defined $CC->{$comp}->{'theme'} && 
		 $CC->{$comp}->{'theme'} eq $theme) {
		my $OptE = $CC->{$comp}->{'option'}->[$o];
		$index = $OptE->{'current'} || ($OptE->{'default'} || 1);
		# if not see if we have a memeory
	} elsif (defined $_core->{'memory'}->{$theme}->{$comp}) {
		my $cm = $_core->{'memory'}->{$theme}->{$comp};
		my $mOptions = $cm->{'option'};
		if (time() - $cm->{'time'} <= 6 * 30 * 24 * 60 * 60 &&
			 ref($mOptions) eq 'ARRAY' && 
			 defined $mOptions->[$o]->{'current'}) {
			$index = $mOptions->[$o]->{'current'}
		}
	}
	$index--;
	$index = 0 if $index < 0 || $index >= @$values;

	return $index;
}

#--------------------------------------
sub getVariantIndex {
	my $self = shift;
	my $theme = shift;
	my $comp = shift;
	my $val = shift;

	my $themeCfg = new Fvwm::ThemeCfg($theme);
	my $cc = $themeCfg->getComponentCfg($comp);
	my $CC = $self->{cc};
	my $_core = $self->getComponentCfg("_core");

	my $index = $cc->{'current'} || $cc->{'default'};
	if (defined $CC->{"$comp"}->{'theme'} && 
		 $CC->{"$comp"}->{'theme'} eq $theme) {
		$index = $CC->{"$comp"}->{'current'} || $index;
		# if not see if we have a memory
	} elsif (defined $_core->{'memory'}->{"$theme"}->{"$comp"}) {
		my $cm = $_core->{'memory'}->{$theme}->{"$comp"};
		if (time() - $cm->{'time'} <= 6 * 30 * 24 * 60 * 60 &&
			 $cm->{'current'}) {
			$index = $cm->{'current'};
		}
	}
	$index--;
	$index = 0 if $index < 0 || $index >= $val;
	return $index;
}

#--------------------------------------
sub getAllSubComponents {
	my $self = shift;
	my $theme = shift;
	my $component = shift;
	my $themeCfg = shift || new Fvwm::ThemeCfg($theme);
	my $name = shift;
	my $config = [];
	my $tmp = $themeCfg->getComponentCfg($component);
	my $contains = $tmp->{'contains'};

	return -1 if (ref($contains) ne 'ARRAY');
	my $c;
	foreach $c (@$contains) {
		my $dd = $themeCfg->getComponentCfg("$component/$c");
		my $subConfig = getAllSubComponents($self,$theme,"$component/$c",
			$themeCfg,$dd->{'name'});
		if (ref($subConfig) eq 'ARRAY') {
			push @$config, @$subConfig;
		} else {
			my $i = $#$config + 1;
			my $dd = $themeCfg->getComponentCfg("$component/$c");
			$config->[$i]->{'name'} = $name . " " if $name ne "0";
			$config->[$i]->{'name'} .= $dd->{'name'};
			$config->[$i]->{'comp'} = "$component/$c";
			my $variants = $dd->{'variant'};
			if (ref($variants) eq 'ARRAY') {
				my $j = getVariantIndex($self,$theme,"$component/$c",@$variants);
				#print STDERR "!!!!!! $j\n";
				$config->[$i]->{'index'} = $j;
				$config->[$i]->{'current'} = 	$variants->[$j]->{'name'};
			}
		}
	}
	return $config;
}
#--------------------------------------
sub sessionInfo {
	my @sessionList;
	my $dir = "$userDir/themes";
	my $current = "$userDir/themes/current";
	my $currentSession = readlink($current);
	my $uptime = upTime($current,1);
	$currentSession =~ s/^current-//;
	opendir(DIR,"$dir");
	foreach (readdir(DIR)) {
		if (/^current-(.+)/) { push @sessionList, $1; }
	}
	close(DIR);
	@sessionList = sort @sessionList;
	return ($currentSession, \@sessionList, $uptime);
}

# ----------------------
sub upTime {
	my $file = shift;
	my $isSymbolic = shift || 0;
	my @stat = $isSymbolic ? lstat($file) : stat($file);
	my $uptime = time - $stat[9];
	$uptime = secToUptime($uptime);
	return $uptime;
}

# ----------------------
sub secToUptime {
	my $time = shift;
	my $day = int($time/(60*60*24));
	$time = $time%(60*60*24);
	my $hours = int($time/(60*60));
	$time = $time % (60*60);
	my $min = int($time/60);
	$min = "0$min" if length($min) == 1;
	my $ret = "";
	$ret .= "$day days " if $day > 1;
	$ret .= "$day day " if $day == 1;
	$ret .= "$hours h $min min";
	return $ret;
}
#----------------------------------------------------------------------------
# useful functions for the com loop

sub variantsOptionsScriptList {
	my $variants = shift;
	my $index = shift;
	my $isSet = shift;
	my $max = shift;

	my $return = "";
	my $i;
	for ($i = 0; $i < @$variants; $i++) {
		my $isCurrent = $i eq $index;
		my $set = 0;
		my $used = "";
		my $name = $variants->[$i]->{'name'};
		$set = 1 if $isSet == $i+1;
		my $l = $max - length($name);
		$l = 1 if $l < 1;
		$used = " " x $l . "(" if $isCurrent || $set;
		$used .= $isCurrent? "Used": "";
		$used .= "/" if  $isCurrent && $set;
		$used .= "Set" if  $set;
		$used .= ")" if $isCurrent || $set;
		$return .= "$name$used|";
	}
	return $return;
}

sub updateCurrentComponents {
	my $currentComponents = shift;
	my $comp = shift;
	my $test = 1;

	foreach (@$currentComponents) {
		$test = 0 if $_ eq $comp;
	}
	if ($test) {
		push @$currentComponents, $comp;
		@$currentComponents = sort @$currentComponents;
	}
}

#----------------------------------------------------------------------------
# An alarm handler (called from eval block):
sub checkScript {

	die "Script" unless ($comPid);

	my $test = 0;
	my $lockFifo = ".tmp-com-lock-" . $comName;

	$test = 1 if kill 0 => $comPid;

	if ($test) { die "Script"; }
	else { unlink($lockFifo);die "NoScript"; }
}

#-----------------------------------------------------------------------------
# 
sub myMakeFifo {
	my ($fifo) = @_;
	system("mkfifo '$fifo'");  # not portable: mknod '$fifo' p
}

#-----------------------------------------------------------------------------
# For killing FvwmScript-ThemesCenter if an error happen in this script!
END {
	if ($comMode) {
		if ($?) {
			my $lockFifo = ".tmp-com-lock-" . $comName;
			my $inFifo = ".tmp-com-in-" . $comName;
			my $message = "fvwm-themes-config: internal error $?";

			unlink($lockFifo);
			unlink($inFifo);
		  	if ($comPid) {
				kill(9, $comPid);
				$message .= ", killing FvwmScript-ThemesCenter";
			}
			print STDERR "$message\n";
		}
	}
}


__END__

# ---------------------------------------------------------------------------

=head1 NAME

fvwm-themes-config - fvwm-themes manager and configurator

=head1 SYNOPSIS

B<fvwm-themes-config>
[ B<--help>|B<-h> ]
[ B<--version>|B<-v> ]
[ B<--site> ]
[ B<--pipe> ]
[ B<--show-themes> ]
[ B<--show-components> ]
[ B<--show-dir> ]
[ B<--theme>|B<-t> theme ]
[ B<--show-value> key ]
[ B<--component> component ]
[ B<--only-site> ]
[ B<--only-user> ]
[ B<--fvwmscript> ]
[ B<--expand-rc>|B<-e> [file] ]
[ B<--fresh>|B<-f> ]
[ B<--reset>|B<-r> ]
[ B<--load>|B<-l> component@theme ]
[ B<--drop>|B<-u> component@theme ]
[ B<--option>|B<-o> component:option=value ]
[ B<--variant>|B<-v> component=variant ]
[ B<--set-locked> component=0|1 ]
[ B<--install} file ... ]
[ B<--com-mode> ]
[ B<--com-name> name ]

=head1 DESCRIPTION

This scripts creates and changes fvwm configuration to use with fvwm-themes
accordingly to theme component definitions and user choices.

It builds I<themes-rc> in $FVWM_USERDIR, which is a replacement for .fvwm2rc.

=head1 OPTIONS

B<--help>    - show the help and exit

B<--version> - show the version and exit

B<--site> - use site configuration directory for output. The default is to
use the user's directory.

B<--pipe> - generate fvwm commands suitable to use within fvwm's PipeRead
(instead of error messages, for example).

B<--show-themes> - shows list of all themes (or ones specified by B<--theme>).

B<--show-components> - shows all themes (or ones specified by B<--theme>)
with all their components (components are TAB justified).

B<--show-dir> - shows the theme directory of all themes (or ones specified
by B<--theme>). These directories sit in themes/ parent directory of either
user or site place.

B<--theme> theme - only theme name(s) given by this parameter(s) are queried,
if given. By default all themes are queried.

B<--show-value> key - shows a value by the key for a component given in
C<--component> parameter.

B<--component> component - a working component for other parameters,
may be of form component@theme.

B<--only-site> - when specified together with B<--show-themes>
or B<--show-components> causes to take into account only the site directory.

B<--only-user> - when specified together with B<--show-themes>
or B<--show-components> causes to take into account only the user directory.

B<--fvwmscript> - when specified together with B<--show-themes>
or B<--show-components> causes the output to be formatted for FvwmScript.

Example: fvwm-themes-config --component colors --show-value theme --show-value
read-file

B<--expand-rc> [file] - gets an FVWM configuration file and expands all
includes in one very long file, printed to standard output. If the file is
not given $FVWM_USERDIR/themes-rc is taken.
This parameter can't be used with others.

B<--fresh> - refresh (regenerate) the fvwm configuration files needed to
load themes, this includes files in the user's directory:
$FVWM_USERDIR/themes-rc, $FVWM_USERDIR/themes-rc-2 and
$FVWM_USERDIR/themes/current/theme.cfg.

B<--reset> - forget all the currently used components and use the ones from
the default theme.

B<--load> I<component> - multiple C<--load> parameters may be given. If the
parameter is of form component@theme, this specific theme component is used,
if it is of form @theme, all components of the given theme will be used.

The process of "loading" components consists of adding new components or
replacing existing ones in the B<current> theme. It is possible that
there will be conflicts during this operation. In this case, nothing is
changed, negative status is returned and the error message is printed.

B<--drop> I<component> - the opponent for C<--load>, these parameters
may be mixed. Tries to unload the given component without breaking
dependancies. [@theme] part of I<component> name may be omitted.

B<--option> I<component:option=value> - set another component option value.
The I<option> may be either the option name or its index in the option list
starting from 1 (use 0 to represent the default option).
The I<value> may be either the value name or its index in the option value list
starting from 1 (use 0 to represent the default option value).

B<--variant> I<component=variant> - set another component variant if a given
I<component> has variants. The I<variant> may be either the variant name or
its index in the variant list starting from 1 (use 0 to represent the
default component variant).

B<--set-locked> I<component=value> - set (if I<value> is 1) or unset (if
I<value> is 0) a locked state of the given component. When the component
from the current theme is locked, C<--load @theme> will not replace it,
it can only be replaced by explicit C<--load component@theme>.

B<--install> I<theme.tar.{gz,bz2} ...> - install the specified tarballs
into the site (if B<--site> is alo given) or into the user's I<themes>
directory by verifying and unpacking the contents of the tarballs.

Four last parameters may be combined together and multiple parameters are
possible. If B<--load> and B<--drop> parameters are given, first it will
be unloaded all given components and then loaded all given components, not
vice versus. After that B<--variant> and B<--option> parameters will take
place, i.e. it is possible to load a component and immediately change its
options.

B<--com-mode> Run fvwm-themes-config under the "communication mode". See,
the fvwm-themes-com and fvwm-themes-menuapp manual pages for more information
on this option.
You need to read the code to know the communication commands.

B<--com-name> name - use name as name for communication with fvwm-themes-com.
By default, "config" is used, but you should use  "config-pid" as name
where pid is the pid of the program that 
want to talk to fvwm-themes-config so that fvwm-themes-config can 
exit if this program exit and so that fvwm-themes-config can kill the program
if an internal error happen in fvwm-themes-config. On the other hand,
if you want to talk with fvwm-themes-config in, say, a terminal you must
not give an name as "config-an_integer" as name.

=head1 USAGE

Usually you don't need to run this script manually, it is called using
different interfaces (menus and more).

To start with fvwm-themes, run this:

  fvwm-themes-config --reset

This command automatically called in B<fvwm-themes-start> when needed,
it will create the "current" theme in the user space, equivalent to the
"default" one. If C<--site> parameter is also given, it will be created
in the system space instead.

Info examples:

  fvwm-themes-config --show-themes  # shows a list of all themes
  fvwm-themes-config --show-components  # show all themes+components
  fvwm-themes-config --show-themes --component windowlook
  fvwm-themes-config --show-components --theme migo --theme default

  fvwm-themes-config --component colors --show-value read-file
  fvwm-themes-config --component colors@cde --show-value option
  fvwm-themes-config --component _core --show-value memory

Other examples:

  fvwm-themes-config --load @afterstep  # load theme "afterstep"
  fvwm-themes-config --drop modules@afterstep  # unload component
  fvwm-themes-config --variant settings/stroke=2  # turn on stroke
  fvwm-themes-config --variant settings/stroke=0  # use default (1)
  fvwm-themes-config --option bindings:switch-mouse-2-3=no
  fvwm-themes-config --set-locked colors=1 --set-locked globalfeel=0

  fvwm-themes-config --install --site metallic.tar.gz wooden.tar.gz

=head1 AUTHORS

Mikhael Goikhman <migo@homemail.com>, 31 Dec 1999.

Olivier Chapuis <olivier.chapuis@free.fr> (some small things and
the communication loop implementation).

=head1 COPYING

The script is distributed by the same terms as fvwm-themes itself.
See GNU General Public License for details.

=head1 BUGS

Report bugs to fvwm-themes-devel@lists.sourceforge.net.

=cut

# ===========================================================================
