#!/usr/bin/perl -w

use strict;
#use SimpleGtk2;
use lib `fvwm-perllib dir`;
use FVWM::Module::SimpleGtk2;
use File::Spec;
use File::Copy qw(copy);

#-----------------------------------------------------------------------
# File:         FNS-WindowsBehaviour
my $Version =   '2.0.4';
# Licence:      GPL 2
#
# Description:  Perl script to configure some windows behaviour of FNS
#
# Author:       Thomas Funk <t.funk@web.de>    
#
# Created:      10/29/2014
# Changed:      02/16/2015
#-----------------------------------------------------------------------

my $module = new FVWM::Module::SimpleGtk2(
);

########################################################################
# Global values
########################################################################
SimpleGtk2::use_gettext("fns-win-behaviour", "$ENV{FVWM_USERDIR}/locale:$ENV{FNS_SYSTEMDIR}/locale:+");

# FNS systemdir
my $FNS_SYSTEMDIR = "$ENV{FNS_SYSTEMDIR}";

# FNS userdir
my $FVWM_USERDIR = "$ENV{FVWM_USERDIR}";

# user config
my $user_cfg = ".user";

# FNS config
my $fns_cfg = "config";

# values were changed
my $values_changed = 0;

# new values not applied
my $not_applied = 1;

# new values not saved
my $not_saved = 1;

# Focus policy changed
my $focus_policy_changed = 0;

# config state constants
use constant {
    DEFAULT => 0,
    CONFIG  => 3,
    USER    => 2,
    SET     => 1
};

# default config. Hash: Style/Cmd => Array: style_in_cfg|value, state, default_value)
my %config = (
                "FocusPolicy" => ['FocusFollowsMouse', DEFAULT, 'FocusFollowsMouse'],
                "FPGrabFocus" => ['', DEFAULT, 0],
                "FPGrabFocusTransient" => ['', DEFAULT, 0],
                "FPIgnoreFocusClickMotion" => [0, DEFAULT, 0],
                "FPFocusByProgram" => [0, DEFAULT, 0],
                "MouseFocusClickRaises" => [0, DEFAULT, 0],
                "StackTransientParent" => [0, DEFAULT, 0],
                "FPIgnoreRaiseClickMotion" => [0, DEFAULT, 0],
                "WindowsPlacement" => ['TileCascadePlacement', DEFAULT, 'TileCascadePlacement'],
                "WindowsPlacementParams" => ['', DEFAULT, ''],
                "HideGeometryWindow" => [0, DEFAULT, 0],
                "ResizeOpaque" => [0, DEFAULT, 0],
                "MoveThreshold" => [3, DEFAULT, 3],
                "OpaqueMoveSize" => [5, DEFAULT, 5],
                );

########################################################################
# Common functions
########################################################################

#-----------------------------------------------------------------------
# Check if an user config is available. Else check for config in 
# $FVWM_USERDIR else config in $FNS_SYSTEMDIR
# return array with config path(s)
#-----------------------------------------------------------------------
sub check_4_cfgs {
    my @cfgs;
    if (-f "$FVWM_USERDIR/$user_cfg") { push(@cfgs, "$FVWM_USERDIR/$user_cfg"); }
    if (-f "$FVWM_USERDIR/$fns_cfg") { unshift(@cfgs, "$FVWM_USERDIR/$fns_cfg"); }
    elsif (-f "$FNS_SYSTEMDIR/$fns_cfg") { unshift(@cfgs, "$FNS_SYSTEMDIR/$fns_cfg"); }
    return @cfgs;
}


#-----------------------------------------------------------------------
# remove wasted whitespaces from string
#-----------------------------------------------------------------------
sub cleanup($) {
    my $str = shift;
    # Remove whitespaces at the beginning and the end again
    $str   =~ s/^\s+//g;
    $str   =~ s/\s+$//g;
    # remove quotes at the beginning and the end
    $str =~ s/^("|')//;
    $str =~ s/("|')$//;
    return $str;    
}


#-----------------------------------------------------------------------
# bring penalties in the correct form
#-----------------------------------------------------------------------
sub clean_penalties($) {
    my $str = shift;
    $str = &cleanup($str);
    my $values = '';
    my @penalties = split(' ',$str);
    foreach (@penalties) {
        next if $_ eq '';
        $values = "$values $_";
    }  
    $values = &cleanup($values);
    return $values;
}


#-----------------------------------------------------------------------
# check for exclamation point/mark
# returns 1 for found else 0
#-----------------------------------------------------------------------
sub check_4_EP ($) {
    my $str = shift;
    my $rc = 0;
    if ($str =~ /\!/) { $rc = 1; }
    return $rc;    
}


#-----------------------------------------------------------------------
# Get array index for a word
# get_index_of(word, array)
#-----------------------------------------------------------------------
sub get_index_of(@) {
    my ($word, @array) = @_;
    my ($index) = grep { $array[$_] =~ /$word/ } 0..$#array;
    return $index;
}


########################################################################
# Parsing routine
########################################################################
sub parse_configs {
    my @cfgs = &check_4_cfgs();
    local *CF;
    my $type = DEFAULT;
    my $value;
    
    foreach my $file (@cfgs) {
        open(CF,'<'.$file) or die "Open $file: $!";
        read(CF, my $data, -s $file);
        close(CF);
        
        # get the name to find out whether fns config or user config
        my($vol,$dir,$scanned_cfg) = File::Spec->splitpath($file);
        $type = USER if $scanned_cfg eq $user_cfg;
        $type = CONFIG if $scanned_cfg eq $fns_cfg;
         
        my @lines  = split(/\015\012|\012|\015/,$data);
        
        foreach my $line(@lines) {
            # skip comments
            next if($line =~ /^\s*#/);
            # skip all except Style and some specials
            next if($line !~ /^\s*(style|HideGeometryWindow|MoveThreshold|OpaqueMoveSize)/i);
            # remove style
            if ($line =~ /^\s*style/i) {
                $line =~ s/^\s*style //i;
                # skip all exept *
                next if($line !~ /^\s*\*/);
                # remove *
                $line =~ s/^\s*\* //;
            }
            
            # Remove whitespaces at the beginning and the end before parsing
            $line =~ s/^\s+//g;
            $line =~ s/\s+$//g;

            #### begin with parsing
            # check for Focus policies --------------------------------
            if ($line =~ /^\s*(ClickToFocus|MouseFocus|FocusFollowsMouse|SloppyFocus)$/i) {
                &init_config_value('FocusPolicy', $line, $type, 'cleanup');
                next;
            }
            
            # check for Focus options ---------------------------------
            # FPGrabFocusTransient = 1 !FPGrabFocusTransient = 0
            if ($line =~ /^\s*FPGrabFocusTransient/i) {
                if ($line =~ /^\s*!FPGrabFocusTransient$/i) {$value = 0;} else {$value = 1;}
                &init_config_value('FPGrabFocusTransient', $value, $type);
                next;
            }
            # FPGrabFocus = 1, !FPGrabFocus = 0
            if ($line =~ /^\s*FPGrabFocus/i) {
                if ($line =~ /^\s*!FPGrabFocus$/i) {$value = 0;} else {$value = 1;}
                &init_config_value('FPGrabFocus', $value, $type);
                next;
            }
            # FPIgnoreFocusClickMotion = 1, !FPIgnoreFocusClickMotion = 0
            if ($line =~ /^\s*FPIgnoreFocusClickMotion$/i) {
                &init_config_value('FPIgnoreFocusClickMotion', $line, $type, 'exclamation');
                next;
            }
            # FPFocusByProgram = 1, !FPFocusByProgram = 0
            if ($line =~ /^\s*FPFocusByProgram$/i) {
                &init_config_value('FPFocusByProgram', $line, $type, 'exclamation');
                next;
            }
            
            # check for Raise options ---------------------------------
            # MouseFocusClickRaises = 1, MouseFocusClickRaisesOff = 0
            if ($line =~ /^\s*MouseFocusClickRaises/i) {
                if ($line =~ /^\s*MouseFocusClickRaisesOff$/i) {$value = 0;} else {$value = 1;}
                &init_config_value('MouseFocusClickRaises', $value, $type);
                next;
            }
            # StackTransientParent = 1, DontStackTransientParent = 0
            if ($line =~ /StackTransientParent$/i) {
                if ($line =~ /^\s*DontStackTransientParent$/i) {$value = 0;} else {$value = 1;}
                &init_config_value('StackTransientParent', $value, $type);
                next;
            }
            # FPIgnoreRaiseClickMotion = 1, !FPIgnoreRaiseClickMotion = 0
            if ($line =~ /^\s*FPIgnoreRaiseClickMotion$/i) {
                &init_config_value('FPIgnoreRaiseClickMotion', $line, $type, 'EP');
                next;
            }
            
            # check for Windows Placement -----------------------------
            if ($line =~ /^\s*(TileCascadePlacement|CascadePlacement|ManualPlacement|TileManualPlacement)$/i) {
                &init_config_value('WindowsPlacement', $line, $type, 'cleanup');
                next;
            }
            if ($line =~ /^\s*PositionPlacement/i) {
                &init_config_value('WindowsPlacement', "PositionPlacement", $type);
                
                $line =~ s/^\s*PositionPlacement//i;
                if ($line =~ /^\s*(Center|UnderMouse)/i) {
                    &set_config_value('WindowsPlacementParams', &cleanup($line), 0);
                } else {
                    # we ignoring move-arguments
                    &set_config_value('WindowsPlacementParams', "TopLeft", 0);
                }
                &set_config_value('WindowsPlacementParams', $type, 1);
                if ($type == CONFIG) {&set_config_value('WindowsPlacementParams', $config{WindowsPlacementParams}[0], 2);}
                next;
            }
            if ($line =~ /^\s*MinOverlapPlacement$/i) {
                &init_config_value('WindowsPlacement', "MinOverlapPlacement", $type);
                next;
            }
            if ($line =~ /^\s*MinOverlapPlacementPenalties/i) {
                &init_config_value('WindowsPlacement', "MinOverlapPlacement", $type);
                
                $line =~ s/^\s*MinOverlapPlacementPenalties//i;
                &set_config_value('WindowsPlacementParams', &clean_penalties($line), 0);
                &set_config_value('WindowsPlacementParams', $type, 1);
                if ($type == CONFIG) {&set_config_value('WindowsPlacementParams', $config{WindowsPlacementParams}[0], 2);}
                else {&set_config_value('WindowsPlacementParams', '1 5 10 1 0.05 50', 2);}
                next;
            }
            if ($line =~ /^\s*MinOverlapPercentPlacement$/i) {
                &init_config_value('WindowsPlacement', "MinOverlapPercentPlacement", $type);
                next;
            }
            if ($line =~ /^\s*MinOverlapPercentPlacementPenalties/i) {
                &init_config_value('WindowsPlacement', "MinOverlapPercentPlacement", $type);

                $line =~ s/^\s*MinOverlapPercentPlacementPenalties//i;
                &set_config_value('WindowsPlacementParams', &clean_penalties($line), 0);
                &set_config_value('WindowsPlacementParams', $type, 1);
                if ($type == CONFIG) {&set_config_value('WindowsPlacementParams', $config{WindowsPlacementParams}[0], 2);}
                else {&set_config_value('WindowsPlacementParams', '12 6 4 1', 2);}
                next;
            }
            
            # check for Miscellaneous ---------------------------------
            # HideGeometryWindow Never = 0, HideGeometryWindow = 1
            if ($line =~ /^\s*HideGeometryWindow/i) {
                $line =~ s/^\s*HideGeometryWindow//i;
                $line =  &cleanup($line);
                if ($line =~ /^\s*Never/i) {$value = 0;} else {$value = 1;} # we ignoring  Move | Resize
                &init_config_value('HideGeometryWindow', $value, $type);
                next;
            }
            # ResizeOpaque = 1, ResizeOutline = 0
            if ($line =~ /^\s*ResizeOpaque/i) {
                &init_config_value('ResizeOpaque', 1, $type);
                next;
            }
            if ($line =~ /^\s*ResizeOutline/i) {
                &init_config_value('ResizeOpaque', 0, $type);
                next;
            }
            # MoveThreshold [pixels]; Default = 3, ommited | negative = 3
            if ($line =~ /^\s*MoveThreshold/i) {
                $line =~ s/^\s*MoveThreshold//i;
                &set_config_value('MoveThreshold', &cleanup($line), 0);
                &set_config_value('MoveThreshold', $type, 1);
                if ($config{MoveThreshold}[0] !~ /\d/ or $config{MoveThreshold}[0] < 0) {
                    &set_config_value('OpaqueMoveSize', 3, 0);
                }
                if ($type == CONFIG) {&set_config_value('MoveThreshold', $config{MoveThreshold}[0], 2);}
                next;
            }
            # OpaqueMoveSize [percentage]; 0 = rubber-band outline, unlimited|-1 = solid windows, omitted = default
            if ($line =~ /^\s*OpaqueMoveSize/i) {
                $line =~ s/^\s*OpaqueMoveSize//i;
                &set_config_value('OpaqueMoveSize', &cleanup($line), 0);
                &set_config_value('OpaqueMoveSize', $type, 1);
                my $value = $config{OpaqueMoveSize}[0];
                unless ($value =~ /\d/) {
                    if ($value eq "") {&set_config_value('OpaqueMoveSize', 5, 0);}
                    else {&set_config_value('OpaqueMoveSize', -1, 0);}
                }
                if ($type == CONFIG) {&set_config_value('OpaqueMoveSize', $config{OpaqueMoveSize}[0], 2);}
                next;
            }
        }
    }
    # some commands depends on each other, so we check this now
    # FPGrabFocus and FPGrabFocusTransient are default set on ClickToFocus
    unless ($config{FPGrabFocus}[0] =~ /\d/){
        if ($config{FocusPolicy}[0] eq "ClickToFocus") {$value = 1;} else {$value = 0;}
        &set_config_value('FPGrabFocus', $value, 0);
        if ($config{FocusPolicy}[1] == CONFIG) {
            &set_config_value('FPGrabFocus', CONFIG, 1);
            &set_config_value('FPGrabFocus', $config{FPGrabFocus}[0], 2);
        }
    }
    unless ($config{FPGrabFocusTransient}[0] =~ /\d/){
        if ($config{FocusPolicy}[0] eq "ClickToFocus") {$value = 1;} else {$value = 0;}
        &set_config_value('FPGrabFocusTransient', $value, 0);
        if ($config{FocusPolicy}[1] == CONFIG) {
            &set_config_value('FPGrabFocusTransient', CONFIG, 1);
            &set_config_value('FPGrabFocusTransient', $config{FPGrabFocusTransient}[0], 2);
        }
    }
}


#-----------------------------------------------------------------------
# Get a config value
# get_config_value(key, index)
#-----------------------------------------------------------------------
sub get_config_value(@) {
    my ($key, $index) = @_;
    $index = 0 unless defined($index);
    my $value = undef;
    $value = $config{$key}[$index] if (defined($config{$key}));
    return $value;
}


#-----------------------------------------------------------------------
# Set a config value
# set_config_value(key, value, index)
#-----------------------------------------------------------------------
sub set_config_value(@) {
    my ($key, $value, $index) = @_;
    unless (defined($index)) {
        $config{$key}[0] = $value;
        # set to changed (1)
        $config{$key}[1] = 1;
        $values_changed = 1;
        $not_applied = 1;
        $not_saved = 1;
        &set_save_button_active($not_saved);
        &set_apply_button_active($not_applied);
    } else {
        $config{$key}[$index] = $value;
    }
}


#-----------------------------------------------------------------------
# unset the state of a config value or all
# unset_config_value_state(<key|all>)
#-----------------------------------------------------------------------
sub unset_config_value_state($) {
    my $key = shift;
    if ($key eq "all") {
        foreach my $key (keys %config) {
            if ($config{$key}[1] == 1) {
                $config{$key}[1] = 2; # because it is saved in the user config
            }
        }
    } else {
        # unset if set
        $config{$key}[1] = 2 if $config{$key}[1] == 1;
    }
    # set FPGrabFocus/FPGrabFocusTransient to default values if the default value differs from the current
    if (get_config_value('FPGrabFocus', 1) == DEFAULT and (get_config_value('FPGrabFocus', 0) ne get_config_value('FPGrabFocus', 2))) {
        &set_config_value('FPGrabFocus', get_config_value('FPGrabFocus', 0), 2);
    }
    if (get_config_value('FPGrabFocusTransient', 1) == DEFAULT and (get_config_value('FPGrabFocusTransient', 0) ne get_config_value('FPGrabFocusTransient', 2))) {
        &set_config_value('FPGrabFocusTransient', get_config_value('FPGrabFocusTransient', 0), 2);
    }
}


#-----------------------------------------------------------------------
# initialize value while parsing
# init_config_value(key, value, config_type, cleanup|penalties|EP(exclamation))
#-----------------------------------------------------------------------
sub init_config_value(@) {
    my ($key, $value, $type, $clean) = @_;
    if (defined($clean)) {
        if ($clean eq 'cleanup') {
            &set_config_value($key, &cleanup($value), 0);
        }
        elsif ($clean eq 'penalties') {
            &set_config_value($key, &clean_penalties($value), 0);
        }
        else {
            &set_config_value($key, &check_4_EP($value), 0);
        }
    } else {
        &set_config_value($key, $value, 0);
    } 
    &set_config_value($key, $type, 1);
    if ($type == CONFIG) {&set_config_value($key, $config{$key}[0], 2);}
}


#-----------------------------------------------------------------------
# create a hash with sorted lists
#-----------------------------------------------------------------------
sub get_lol_4_cfg {
    my $do_apply = shift;
    my %order = (0 => ["FocusPolicy"], 
                 1 => ["FPGrabFocus", "FPGrabFocusTransient", "FPIgnoreFocusClickMotion", "FPFocusByProgram"],
                 2 => ["MouseFocusClickRaises", "StackTransientParent", "FPIgnoreRaiseClickMotion"],
                 3 => ["WindowsPlacement", "WindowsPlacementParams"],
                 4 => ["HideGeometryWindow", "ResizeOpaque", "OpaqueMoveSize", "MoveThreshold"]);
    my %new_order;
    my $i = 0;
    # get needed parts through comparing
    foreach my $count (sort(keys %order)) {
        $new_order{$count} = [];
        foreach my $key (@{$order{$count}}) {
            next if get_config_value($key, 1) == CONFIG;
            if ($do_apply) {
                # removes entries which are the same as the default
                next if get_config_value($key, 1) == DEFAULT and (get_config_value($key, 0) eq get_config_value($key, 2));
            } else {
                # removes entries which are default or the same as the default
                next if get_config_value($key, 1) == DEFAULT or (get_config_value($key, 0) eq get_config_value($key, 2));
            }
            push(@{$new_order{$count}}, $key);
            $i += 1; # how many changes are available
        }
    }
    return (\%new_order, \$i);
}


#-----------------------------------------------------------------------
# build user config or apply with a temp file
#-----------------------------------------------------------------------
sub build_config($) {
    my $do_apply = shift;

    my $user_config;
    $user_config = $FVWM_USERDIR . "/" . $user_cfg;
    my $cfg_backup;
    my $data;
    my @lines;
    my $user_parts = 0; 
    
    # check config changes
    my ($in_order_ref, $changes_ref) = get_lol_4_cfg($do_apply);
    my %in_order = %$in_order_ref;
    my $changes = $$changes_ref;
    unless ($changes == 0) {
        # check what state the values have. Depending on that
        # create the needed files
        if ($do_apply) {
            $cfg_backup = "$user_config.tmp"; # for apply
        } else {
            $cfg_backup = "$user_config.bak"; # for save
        }

        # check if user config exist and read it depending on save or apply
        if (-f $user_config) {
            copy($user_config, $cfg_backup);

            local *IN;
            open(IN,'<'.$cfg_backup) or die "Open $cfg_backup: $!";
            read(IN, $data, -s $cfg_backup);
            close(IN);
            @lines  = split(/\015\012|\012|\015/,$data);
        }
        
        # open the config file for writing
        my $out;
        if ($do_apply) {
            open $out, '>', $cfg_backup or die "Can't write $cfg_backup: $!";
        } else {
            open $out, '>', $user_config or die "Can't write $user_config: $!";
        }
        
        # Add header
        print $out "##########################################################################\n";
        print $out "# File was changed by FNS config tool on " . gmtime() . "\n";
        print $out "# !!! Don't add anything here by hand. Will be overwritten !!!\n";
        print $out "\n";
        
        my $entry = '';
        # Add Focus policies
        unless (@{$in_order{0}} == 0) {
            print $out "#=======================================================================\n";
            print $out "# Focus Policy\n";
            print $out "#=======================================================================\n";
            print $out "Style * " . get_config_value($in_order{0}[0]) . "\n";
            print $out "\n";
        }
        # Add Focus options
        if (@{$in_order{1}} != 0) {
            print $out "#=======================================================================\n";
            print $out "# Focus Options\n";
            print $out "#=======================================================================\n";
            foreach my $opt (@{$in_order{1}}) {
                if ($opt eq 'FPGrabFocus' or $opt eq 'FPGrabFocusTransient' or
                    $opt eq 'FPIgnoreFocusClickMotion' or $opt eq 'FPFocusByProgram') {
                        if (get_config_value($opt) == 1) {$entry = $opt;} else {$entry = "!" . $opt;}
                }
                print $out "Style * $entry\n";
            }
            print $out "\n";
        }
        # Add Raise options
        if (@{$in_order{2}} != 0) {
            print $out "#=======================================================================\n";
            print $out "# Raise Options\n";
            print $out "#=======================================================================\n";
            foreach my $opt (@{$in_order{2}}) {
                if ($opt eq 'MouseFocusClickRaises') {
                    if (get_config_value($opt) == 1) {$entry = $opt;} else {$entry = $opt . "Off";}
                }
                if ($opt eq 'FPIgnoreRaiseClickMotion') {
                    if (get_config_value($opt) == 1) {$entry = $opt;} else {$entry = "!" . $opt;}
                }
                else {
                    print $out "Style * $opt\n";
                    if (get_config_value($opt) == 1) {
                        print $out "Style * RaiseTransient\n";
                        print $out "Style * LowerTransient\n";
                        print $out "Style * StackTransientParent\n";
                    } else {
                        print $out "Style * DontRaiseTransient\n";
                        print $out "Style * DontLowerTransient\n";
                        print $out "Style * DontStackTransientParent\n";
                    }
                }
            }
            print $out "\n";
        }
        # Add Window Placement
        if (@{$in_order{3}} != 0) {
            print $out "#=======================================================================\n";
            print $out "# Window Placement\n";
            print $out "#=======================================================================\n";
            my $placement = get_config_value($in_order{3}[0]);
            my $placement_params = get_config_value("WindowsPlacementParams");
            if ($placement =~ /^MinOverlap/) { $placement_params = '';}
            print $out "Style * $placement $placement_params\n";
            
            if (@{$in_order{3}} == 2 and $placement ne 'PositionPlacement') {
                if ($placement eq "MinOverlapPlacement" and get_config_value("WindowsPlacementParams") ne '') {
                    unless ($do_apply and get_config_value("WindowsPlacementParams") eq get_config_value("WindowsPlacementParams", 2)) {
                        print $out "Style * MinOverlapPlacementPenalties " . get_config_value("WindowsPlacementParams") . "\n";
                    } else {
                        print $out "Style * !MinOverlapPlacementPenalties\n";
                    }
                } 
                elsif ($placement eq "MinOverlapPercentPlacement" and get_config_value("WindowsPlacementParams") ne '') {
                    unless ($do_apply and get_config_value("WindowsPlacementParams") eq get_config_value("WindowsPlacementParams", 2)) {
                        print $out "Style * MinOverlapPercentPlacementPenalties " . get_config_value("WindowsPlacementParams") . "\n";
                    } else {
                        print $out "Style * !MinOverlapPercentPlacementPenalties\n";
                    }
                }
            }
            print $out "\n";
        }
        # Add Miscellaneous
        if (@{$in_order{4}} != 0) {
            print $out "#=======================================================================\n";
            print $out "# Miscellaneous\n";
            print $out "#=======================================================================\n";
            foreach my $misc (@{$in_order{4}}) {
                if ($misc eq "ResizeOpaque"){
                    if (get_config_value($misc) == 1) {$entry = "ResizeOpaque";} else {$entry = "ResizeOutline";}
                    print $out "Style * " . $entry . "\n";
                } else {
                    if ($misc eq "HideGeometryWindow"){
                        if (get_config_value($_) == 0) {$entry = "Never";} else {$entry = "";}
                        print $out "$misc " . $entry . "\n";
                    } else {
                        print $out "$misc " . get_config_value($misc) . "\n";
                    }
                }
            }
            print $out "\n";
        }
        # Add end of FNS config
        print $out "# End of FNS configuration. User can add things after the next line ^^\n";
        print $out "##########################################################################\n";
        
        # Now add users parts if any
        unless ($do_apply) {
            my $fns_part = -1;
            foreach my $line (@lines) {
                unless ($fns_part == 1) {
                    if ($line =~ /^#{72}/) {
                        $fns_part += 1;
                    }
                } else {
                    print $out "$line\n";
                    $user_parts = 1;
                }
            }
        }
        close $out;
    }
    
    if ($do_apply) {
        $changes != 0 ? return $cfg_backup : return undef;
    } else {
        if ($user_parts or $changes) {
            return $user_config;
        } else {
            unless ($user_parts or $changes) {
                unlink($user_config) or warn "$0: could not unlink $user_config.tmp: $!\n"; 
            }
            return undef;
        }
    }
}


########################################################################
# Main
########################################################################
&parse_configs();


########################################################################
# Graphical User Interface + corresponding functions
########################################################################

#-----------------------------------------------------------------------
# Toplevel window
#-----------------------------------------------------------------------
my $win = SimpleGtk2->new_window(  Name => 'mainWindow', 
                        Version => $Version,
                        Size    => [500, 510], 
                        Title   => "FNS Windows Behaviour");
$win->add_signal_handler('mainWindow', 'delete_event', \&check_before_quit);


#-----------------------------------------------------------------------
# Focus Policies Frame 
#-----------------------------------------------------------------------
$win->add_frame( Name => 'PoliciesFrame', 
        Pos     => [10, 10], 
        Size    => [230, 85], 
        Title   => " Focus Policies ");


# ClickToFocus ---------------------------------------------------------
$win->add_radio_button( Name => 'radioClickToFocus', 
        Pos     => [10, 10], 
        Title   => "Click to Focus", 
        Group   => "PolicyGroup", 
        Tip     => "Give the focus to a window when it is clicked in.", 
        Frame   => 'PoliciesFrame');
$win->add_signal_handler('radioClickToFocus', 'toggled', \&check_policy_on);


# FocusFollowsMouse ----------------------------------------------------
$win->add_radio_button( Name => 'radioFocusFollowsMouse', 
        Pos     => [10, 30], 
        Title   => "Focus follows Mouse", 
        Group   => "PolicyGroup", 
        Tip     => "Give a window the focus as the pointer enters the window, and take it away when leaving.", 
        Frame   => 'PoliciesFrame');
$win->add_signal_handler('radioFocusFollowsMouse', 'toggled', \&check_policy_on);


# SloopyFocus ----------------------------------------------------------
$win->add_radio_button( Name => 'radioSloopyFocus', 
        Pos     => [10, 50], 
        Title   => "Sloopy Focus", 
        Group   => "PolicyGroup", 
        Tip     => "Give a window the focus as the pointer enters the window, but doesn't give up the \
                    focus if the pointer leaves the window to pass over the root window.", 
        Frame   => 'PoliciesFrame');
$win->add_signal_handler('radioSloopyFocus', 'toggled', \&check_policy_on);

sub check_policy_on {
    my $widget = shift;
    my $radio_button;
    my $set_button = 0;
    my $init = 0;
    my $policy = &get_config_value("FocusPolicy");
    if (defined($widget)) {
        $radio_button = $win->get_object($widget)->{name};
        if ($win->is_active($radio_button)) {
            if ($radio_button !~ /$policy$/) {
                $set_button = 1;
                $policy = $radio_button;
                $policy =~ s/^radio//; 
                &set_config_value("FocusPolicy", $policy);
                # check if FPGrabFocus and FPGrabFocusTransient un/set by user
                my $value = 0;
                if ($policy eq 'ClickToFocus') {
                    $value = 1;
                }
                unless (&get_config_value('FPGrabFocus', 1) == SET) {
                    &init_config_value('FPGrabFocus', $value, DEFAULT);
                    $focus_policy_changed = 1;
                    $win->set_value('checkGrabFocus', 'Active' => $value);
                }
                unless (&get_config_value('FPGrabFocusTransient', 1) == SET) {
                    &init_config_value('FPGrabFocusTransient', $value, DEFAULT);
                    $focus_policy_changed = 1,
                    $win->set_value('checkGrabFocusTransient', 'Active' => $value);
                }
            }
        } else {
            return;
        }
    } else {
        $radio_button = 'radio' . $policy;
        $set_button = 1;
        $init = 1;
    }
    $win->set_value($radio_button, 'Active' => $set_button);
}

# set the initial/found policy
&check_policy_on();


#-----------------------------------------------------------------------
# Raise Options Frame 
#-----------------------------------------------------------------------
$win->add_frame( Name => 'RaiseOptionsFrame', 
        Pos     => [250, 10], 
        Size    => [240, 85], 
        Title   => " Raise Options ");


# MouseFocusClickRaises ------------------------------------------------
$win->add_check_button( Name => 'checkRaisesIfFocused', 
        Pos     => [10, 10], 
        Title   => "Raise if focused", 
        Tip     => "Controls if the window is raised when focused (depending on the focus model).", 
        Active  => &get_config_value("MouseFocusClickRaises"), 
        Frame   => 'RaiseOptionsFrame');
$win->add_signal_handler('checkRaisesIfFocused', 'toggled', sub {&set_config_value("MouseFocusClickRaises", $win->is_active('checkRaisesIfFocused'));});


# StackTransientParent -------------------------------------------------
$win->add_check_button( Name => 'checkStackTransientParent', 
        Pos     => [10, 30], 
        Title   => "Stack Transient Parent", 
        Tip     => "This style transfers the raise action to the main window makes raise on a \
                    transient act just like raise on its main - the whole group is raised.", 
        Active  => &get_config_value("StackTransientParent"), 
        Frame   => 'RaiseOptionsFrame');
$win->add_signal_handler('checkStackTransientParent', 'toggled', sub {&set_config_value("StackTransientParent", $win->is_active('checkStackTransientParent'));});


# FPIgnoreRaiseClickMotion ---------------------------------------------
$win->add_check_button( Name => 'checkIgnoreRaiseClickMotion', 
        Pos     => [10, 50], 
        Title   => "No Raise Click on Motion", 
        Tip     => "If this style is used, clicking in a window and then dragging the pointer with \
                    the button held down does not count as the click to raise the window. Instead, \
                    the application processes these events normally. This is useful to select text \
                    in a terminal window with the mouse without raising the window. However, mouse \
                    bindings on the client window are not guaranteed to work anymore.\nThe distance \
                    that the pointer must be moved to trigger this is controlled by the MoveThreshold \
                    (in 'Miscellaneous').", 
        Active  => &get_config_value("FPIgnoreRaiseClickMotion"), 
        Frame   => 'RaiseOptionsFrame');
$win->add_signal_handler('checkIgnoreRaiseClickMotion', 'toggled', sub {&set_config_value("FPIgnoreRaiseClickMotion", $win->is_active('checkIgnoreRaiseClickMotion'));});


#-----------------------------------------------------------------------
# Focus Options Frame 
#-----------------------------------------------------------------------
$win->add_frame( Name => 'FocusOptionsFrame', 
        Pos     => [10, 105], 
        Size    => [480, 70], 
        Title   => " Focus Options ");


# GrabFocus ------------------------------------------------------------
$win->add_check_button( Name => 'checkGrabFocus', 
        Pos     => [10, 10], 
        Title   => "Grab Focus", 
        Tip     => "New normal windows with the 'Grab Focus' style automatically receive the focus \
                    when they are created. 'Grab Focus' is the default for windows with the 'Click to Focus' \
                    style. Note that even if this style is disabled, the application may take the focus \
                    itself. Fvwm can not prevent this.", 
        Active  => &get_config_value("FPGrabFocus"), 
        Frame   => 'FocusOptionsFrame');
$win->add_signal_handler('checkGrabFocus', 'toggled', \&check_grab_x_on);


# GrabFocusTransient ---------------------------------------------------
$win->add_check_button( Name => 'checkGrabFocusTransient', 
        Pos     => [10, 30], 
        Title   => "Grab Focus Transient", 
        Tip     => "New transient windows with the 'Grab Focus Transient' style automatically receive \
                    the focus when they are created. Note that even if this style is disabled, the \
                    application may take the focus itself. Fvwm can not prevent this.", 
        Active  => &get_config_value("FPGrabFocusTransient"), 
        Frame   => 'FocusOptionsFrame');
$win->add_signal_handler('checkGrabFocusTransient', 'toggled', \&check_grab_x_on);

sub check_grab_x_on {
    my $widget = shift;
    my $object_name = $win->get_object($widget)->{name};
    my $grab_option = $object_name;
    $grab_option =~ s/^check//;
    $grab_option = 'FP' . $grab_option;
    &set_config_value($grab_option, $win->is_active($object_name)) unless $focus_policy_changed;
    $focus_policy_changed = 0;
}


# FPIgnoreFocusClickMotion ---------------------------------------------
$win->add_check_button( Name => 'checkIgnoreFocusClickMotion', 
        Pos     => [250, 10], 
        Title   => "No Focus Click on Motion", 
        Tip     => "If this style is used, clicking in a window and then dragging the pointer with the \
                    button held down does not count as the click to focus the window. Instead, the \
                    application processes these events normally. This is useful to select text in a \
                    terminal window with the mouse without focusing the window. However, mouse bindings \
                    on the client window are not guaranteed to work anymore. The distance that the pointer \
                    must be moved to trigger this is controlled by the MoveThreshold below.", 
        Active  => &get_config_value("FPIgnoreFocusClickMotion"), 
        Frame   => 'FocusOptionsFrame');
$win->add_signal_handler('checkIgnoreFocusClickMotion', 'toggled', sub {&set_config_value("FPIgnoreFocusClickMotion", $win->is_active('checkIgnoreFocusClickMotion'));});


# FPFocusByProgram -----------------------------------------------------
$win->add_check_button( Name => 'checkFocusByProgram', 
        Pos     => [250, 30], 
        Title   => "Focus by Program", 
        Tip     => "Allows windows to take the focus themselves.", 
        Active  => &get_config_value("FPFocusByProgram"), 
        Frame   => 'FocusOptionsFrame');
$win->add_signal_handler('checkFocusByProgram', 'toggled', sub {&set_config_value("FPFocusByProgram", $win->is_active('checkFocusByProgram'));});


#-----------------------------------------------------------------------
# Window Placement Frame
#-----------------------------------------------------------------------
$win->add_frame( Name => 'PlacementFrame', 
        Pos     => [10, 185], 
        Size    => [480, 175], 
        Title   => " Window Placement ");


# TileCascadePlacement -------------------------------------------------
$win->add_radio_button( Name => 'radioTileCascadePlacement', 
        Pos     => [10, 10], 
        Title   => "Tile Cascade Placement", 
        Group   => "PlacementGroup", 
        Active  => 1, 
        Tip     => "Places new windows in a smart location - a location in which they do not overlap any \
                    other windows on the screen. If no such position can be found Cascade Placement is \
                    used as a fall-back method.", 
        Frame   => 'PlacementFrame');
$win->add_signal_handler('radioTileCascadePlacement', 'toggled', \&check_placement_on);


# TileManualPlacement --------------------------------------------------
$win->add_radio_button( Name => 'radioTileManualPlacement', 
        Pos     => [250, 10], 
        Title   => "Tile Manual Placement", 
        Group   => "PlacementGroup", 
        Tip     => "This is the same as Tile Cascade Placement, but uses Manual Placement as the fall-back method.", 
        Frame   => 'PlacementFrame');
$win->add_signal_handler('radioTileManualPlacement', 'toggled', \&check_placement_on);


# CascadePlacement -----------------------------------------------------
$win->add_radio_button( Name => 'radioCascadePlacement', 
        Pos     => [10, 40], 
        Title   => "Cascade Placement", 
        Group   => "PlacementGroup", 
        Tip     => "Place new windows in a cascading fashion.", 
        Frame   => 'PlacementFrame');
$win->add_signal_handler('radioCascadePlacement', 'toggled', \&check_placement_on);


# ManualPlacement ------------------------------------------------------
$win->add_radio_button( Name => 'radioManualPlacement', 
        Pos     => [250, 40], 
        Title   => "Manual Placement", 
        Group   => "PlacementGroup", 
        Tip     => "The user is required to place every new window manually. The window only shows as a rubber \
                    band until a place is selected manually. The window is placed when a mouse button or any \
                    key except Escape is pressed. Escape aborts manual placement which places the window in the \
                    top left corner of the screen.", 
        Frame   => 'PlacementFrame');
$win->add_signal_handler('radioManualPlacement', 'toggled', \&check_placement_on);


# PositionPlacement ----------------------------------------------------
$win->add_radio_button( Name => 'radioPositionPlacement', 
        Pos     => [10, 70], 
        Title   => "Position Placement", 
        Group   => "PlacementGroup", 
        Tip     => "When used with 'TopLeft', new windows are placed in the top left corner of the display. \
                    With 'Center', all new window appear at the center of the screen, and with 'UnderMouse', \
                    windows are centered under the mouse pointer where possible.",
        Frame   => 'PlacementFrame');
$win->add_signal_handler('radioPositionPlacement', 'toggled', \&check_placement_on);

$win->add_label( Name => 'labelWindow', 
        Pos     => [250, 73], 
        Title   => "Window:", 
        Sens    => 0,
        Frame   => 'PlacementFrame');

my @window_places = ("Center", "TopLeft", "UnderMouse");
my $index = &get_config_value("WindowsPlacement") eq 'PositionPlacement' ? &get_index_of(&get_config_value("WindowsPlacementParams"), @window_places) : 1;
$win->add_combo_box( Name => 'comboWindow', 
        Pos     => [340, 68], 
        Size    => [125, 25], 
        Tip     => "With 'TopLeft', new windows are placed in the top left corner of the display. With 'Center', \
                    all new window appear at the center of the screen, and with 'UnderMouse', windows are centered \
                    under the mouse pointer where possible.", 
        Sens    => 0,
        Frame   => 'PlacementFrame', 
        Data    => \@window_places, 
        Start   => $index);
$win->add_signal_handler('comboWindow', 'changed', sub {&set_config_value("WindowsPlacementParams", $win->get_title('comboWindow'));});


# MinOverlapPlacement --------------------------------------------------
$win->add_radio_button( Name => 'radioMinOverlapPlacement', 
        Pos     => [10, 100], 
        Title   => "Min Overlap Placement", 
        Group   => "PlacementGroup", 
        Tip     => "Automatically places new windows in a location in which the overlapping area in pixels \
                    of other windows is minimized. By default this placement policy tries to avoid \
                    overlapping icons and windows on higher layers.", 
        Frame   => 'PlacementFrame');
$win->add_signal_handler('radioMinOverlapPlacement', 'toggled', \&check_placement_on);

$win->add_label( Name => 'labelPixelPenalties', 
        Pos     => [250, 104], 
        Title   => "Penalties:", 
        Sens    => 0,
        Frame   => 'PlacementFrame');

my $min_overlap_penalties = &get_config_value("WindowsPlacementParams");
$win->add_entry( Name => 'entryPixelPenalties', 
        Pos     => [340, 99], 
        Size    => [125, 25], 
        Title   => (&get_config_value("WindowsPlacement") eq 'MinOverlapPlacement' and $min_overlap_penalties ne '') ? $min_overlap_penalties : '1 5 10 1 0.05 50', 
        Tip     => "The Penalties takes at most 6 positive or null decimal arguments: 'normal' 'ontop' 'icon' 'sticky' \
                    'below' 'strut'. The defaults are 1 5 10 1 0.05 50.\n\nThe 'normal' factor affects normal windows, \
                    the 'ontop' factor affects windows with a greater layer than the window being placed, the 'icon' \
                    factor affects icons, the 'sticky' factor affects sticky windows, the 'below' factor affects windows \
                    with a smaller layer than the window being placed, the 'strut' factor affects the complement of the \
                    EWMH working area if the window being placed has the EWMHPlacementUseWorkingArea style and windows \
                    with an EWMH strut hint (i.e., a 'please do not cover me' hint) if the window being placed has the \
                    EWMHPlacementUseDynamicWorkingArea style. These 'factors' represent the amount of area that these \
                    types of windows (or area) are counted as, when a new window is placed. For example, by default the \
                    area of ontop windows is counted 5 times as much as normal windows. So it covers 5 times as much area of \
                    another window before it will cover an ontop window. To treat ontop windows the same as other windows, \
                    set this to 1. To really, really avoid putting windows under ontop windows, set this to a high value, say \
                    1000.\nThis style affects the window already mapped and not the window which is currently placed.", 
        Sens    => 0,
        Frame   => 'PlacementFrame');
$win->add_signal_handler('entryPixelPenalties', 'changed', sub {&set_config_value("WindowsPlacementParams", $win->get_title('entryPixelPenalties')); return 0;});


# MinOverlapPercentPlacement -------------------------------------------
$win->add_radio_button( Name => 'radioMinOverlapPercentPlacement', 
        Pos     => [10, 132], 
        Title   => "Min Overlap Percent Placement", 
        Group   => "PlacementGroup", 
        Tip     => "Tries to minimize the overlapped percentages of other windows instead of the overlapped area in pixels. \
                    This placement policy tries to avoid covering other windows completely and tries even harder not to cover small windows.", 
        Frame   => 'PlacementFrame');
$win->add_signal_handler('radioMinOverlapPercentPlacement', 'toggled', \&check_placement_on);

$win->add_label( Name => 'labelPercentPenalties', 
        Pos     => [250, 135], 
        Title   => "Penalties:", 
        Sens    => 0,
        Frame   => 'PlacementFrame');

$min_overlap_penalties = &get_config_value("WindowsPlacementParams");
$win->add_entry( Name => 'entryPercentPenalties', 
        Pos     => [340, 130], 
        Size    => [125, 25], 
        Title   => (&get_config_value("WindowsPlacement") eq 'MinOverlapPlacement' and $min_overlap_penalties ne '') ? $min_overlap_penalties : '12 6 4 1', 
        Tip     => "The Penalties takes at most 4 positive or null integer arguments: 'cover_100' 'cover_95' 'cover_85' \
                    'cover_75'. The 'cover_xx' factor is used when the window being placed covers at least xx percent of \
                    the window. This factor is added to the factor determined by the Min Overlap Placement Penalties.", 
        Sens    => 0,
        Frame   => 'PlacementFrame');
$win->add_signal_handler('entryPercentPenalties', 'changed', sub {&set_config_value("WindowsPlacementParams", $win->get_title('entryPercentPenalties')); return 0;});

sub check_placement_on {
    my $widget = shift;
    my $radio_button;
    my $set_button = 0;
    my $policy = &get_config_value("WindowsPlacement");
    if (defined($widget)) {
        $radio_button = $win->get_object($widget)->{name};
        if ($win->is_active($radio_button)) {
            if ($radio_button !~ /$policy$/) {
                $set_button = 1;
                $policy = $radio_button;
                $policy =~ s/^radio//; 
                &set_config_value("WindowsPlacement", $policy);
            }
        } else {
            return;
        }
    } else {
        $radio_button = 'radio' . $policy;
        $set_button = 1;
    }
    $win->set_value($radio_button, 'Active' => $set_button);

    if (defined($widget)) {
        if ($radio_button eq 'radioPositionPlacement') {
            $win->set_sensitive('labelWindow', 1);
            $win->set_sensitive('comboWindow', 1);
            unless ($win->get_title('comboWindow') eq 'TopLeft') {
                &set_config_value('WindowsPlacement', $win->get_title('comboWindow'));
            } else {
                &init_config_value('WindowsPlacementParams', '', DEFAULT);
            }
            &set_config_value('WindowsPlacementParams', 'TopLeft', 2);
        } else {
            $win->set_sensitive('labelWindow', 0);
            $win->set_sensitive('comboWindow', 0);
            &init_config_value('WindowsPlacementParams', '', DEFAULT);
        }
        if ($radio_button eq 'radioMinOverlapPlacement') {
            $win->set_sensitive('labelPixelPenalties', 1);
            $win->set_sensitive('entryPixelPenalties', 1);
            unless ($win->get_title('entryPixelPenalties') eq '1 5 10 1 0.05 50') {
                &set_config_value('WindowsPlacement', $win->get_title('entryPixelPenalties'));
            } else {
                &init_config_value('WindowsPlacementParams', '', DEFAULT);
            }
            &set_config_value('WindowsPlacementParams', '1 5 10 1 0.05 50', 2);
        } else {
            $win->set_sensitive('labelPixelPenalties', 0);
            $win->set_sensitive('entryPixelPenalties', 0);
            &init_config_value('WindowsPlacementParams', '', DEFAULT);
        }
        if ($radio_button eq 'radioMinOverlapPercentPlacement') {
            $win->set_sensitive('labelPercentPenalties', 1);
            $win->set_sensitive('entryPercentPenalties', 1);
            unless ($win->get_title('entryPercentPenalties') eq '12 6 4 1') {
                &set_config_value('WindowsPlacement', $win->get_title('entryPercentPenalties'));
            } else {
                &init_config_value('WindowsPlacementParams', '', DEFAULT);
            }
            &set_config_value('WindowsPlacementParams', '12 6 4 1', 2);
        } else {
            $win->set_sensitive('labelPercentPenalties', 0);
            $win->set_sensitive('entryPercentPenalties', 0);
            &init_config_value('WindowsPlacementParams', '', DEFAULT);
        }
    }
}

# set the initial/found placement
&check_placement_on();


#-----------------------------------------------------------------------
# Miscellaneous
#-----------------------------------------------------------------------
$win->add_frame( Name => 'MiscFrame', 
        Pos     => [10, 370], 
        Size    => [480, 85], 
        Title   => " Miscellaneous ");

#$win->add_label( Name => 'labelClickTime', 
#        Pos     => [15, 15], 
#        Title   => "Click Time:", 
#        Justify => 'left', 
#        Frame   => 'MiscFrame');
#
#$win->add_spin_button( Name => 'spinClickTime', 
#        Pos     => [145, 10], 
#        Start   => 150, Min => 50, Max => 1000, Step => 10, 
#        Tip     => "Specifies the maximum delay in milliseconds between a button press and a button \
#                   release for the Function command to consider the action a mouse click. Default is 150 ms.", 
#        Align   => 'right', 
#        Frame   => 'MiscFrame');


# HideGeometryWindow ---------------------------------------------------
$win->add_check_button( Name => 'checkHideGeometryWindow', 
        Pos     => [10, 12], 
        Title   => "Hide Geometry Window", 
        Tip     => "Hides the position or size window that is usually shown when a window is moved or resized \
                    interactively.", 
        Active  => &get_config_value("HideGeometryWindow"), 
        Frame   => 'MiscFrame');
$win->add_signal_handler('checkHideGeometryWindow', 'toggled', sub {&set_config_value("HideGeometryWindow", $win->is_active('checkHideGeometryWindow'));});


# ResizeOpaque ---------------------------------------------------------
$win->add_check_button( Name => 'checkResizeOpaque', 
        Pos     => [10, 40], 
        Title   => "Resize Opaque", 
        Tip     => "Instructs fvwm to resize the corresponding windows with their contents visible \
                    instead of using an outline. Since this causes the application to redraw frequently \
                    it can be quite slow and make the window flicker excessively, depending on the amount \
                    of graphics the application redraws. Some applications do not like their windows being \
                    resized opaque, e.g. XEmacs or terminals with a pixmap background.", 
        Active  => &get_config_value("ResizeOpaque"), 
        Frame   => 'MiscFrame');
$win->add_signal_handler('checkResizeOpaque', 'toggled', sub {&set_config_value("ResizeOpaque", $win->is_active('checkResizeOpaque'));});


# MoveThreshold --------------------------------------------------------
$win->add_label( Name => 'labelMoveThreshold', 
        Pos     => [250, 15], 
        Title   => "Move Threshold:", 
        Frame   => 'MiscFrame');

$win->add_spin_button( Name => 'spinThreshold', 
        Pos     => [408, 10], 
        Start   => &get_config_value("MoveThreshold"),
        Min => 0, Max => 10, Step => 1, 
        Tip     => "When the user presses a mouse button upon an object fvwm waits to see if the action \
                    is a click or a drag. If the mouse moves by more than x pixels (default is 3) it is \
                    assumed to be a drag (the default value might be increased when 16000x9000 pixel displays become affordable).", 
        Align   => 'right', 
        Frame   => 'MiscFrame');
$win->add_signal_handler('spinThreshold', 'value-changed', sub {&set_config_value("MoveThreshold", $win->get_value('spinThreshold', 'active'));});


# OpaqueMoveSize -------------------------------------------------------
$win->add_label( Name => 'labelOpaqueMoveSize', 
        Pos     => [250, 45], 
        Title   => "Opaque Move Size:", 
        Frame   => 'MiscFrame');

$win->add_spin_button( Name => 'spinOpaqueMoveSize', 
        Pos     => [408, 40], 
        Start   => &get_config_value("OpaqueMoveSize"),
        Min => -1, Max => 200, Step => 1, 
        Tip     => "Tells fvwm the maximum size window with which opaque window movement should be used. \
                    The percentage is percent of the total screen area (may be greater than 100). With a \
                    value of 0 all windows are moved using the traditional rubber-band outline. -1 is \
                    given all windows are moved as solid windows. The default is 5 which allows small \
                    windows to be moved in an opaque manner but large windows are moved as rubber-bands.", 
        Align   => 'right', 
        Frame   => 'MiscFrame');
$win->add_signal_handler('spinOpaqueMoveSize', 'value-changed', sub {&set_config_value("OpaqueMoveSize", $win->get_value('spinOpaqueMoveSize', 'active'));});


#-----------------------------------------------------------------------
# Button bar
#-----------------------------------------------------------------------

# Save -----------------------------------------------------------------
$win->add_button( Name => 'saveButton', 
        Pos     => [15, 470], 
        Size    => [80, 25], 
        Title   => "Save",
        Sens    => $values_changed,
        Tip     => "Save new settings.");
$win->add_signal_handler('saveButton', 'clicked', \&save);

sub set_save_button_active($) {
    my $state = shift;
    $win->set_sensitive('saveButton', $state); 
}

sub save {
    my $user_cfg = &build_config();
    if(defined($user_cfg)) {
        #print "\n\nsave $user_cfg:\n";
        #system("cat $user_cfg");
        print STDERR "[" . $win->get_title($win->{name}) . "]: $user_cfg saved.\n";
    }
    $not_saved = 0;
    &unset_config_value_state('all') unless $not_applied;
    &set_save_button_active($not_saved);
}


# Help -----------------------------------------------------------------
$win->add_button( Name => 'helpButton', 
        Pos     => [155, 470], 
        Size    => [80, 25], 
        Title   => "Help", 
        Tip     => "Get help.");
$win->add_signal_handler('helpButton', 'clicked', sub {system("xterm -g 100x50 -n \"Help FNS-WindowsBehaviour\" -T \"Help FNS-WindowsBehaviour\" -e \"man FNS-WindowsBehaviour\" &");});


# Apply ----------------------------------------------------------------
$win->add_button( Name => 'applyButton', 
        Pos     => [295, 470], 
        Size    => [90, 25], 
        Size    => [80, 25], 
        Title   => "Apply", 
        Sens    => $values_changed,
        Tip     => "Apply new settings.");
$win->add_signal_handler('applyButton', 'clicked', \&apply);

sub apply {
    my $temp_cfg = build_config(1);
    if (defined($temp_cfg)) {
        #print "\n\napply $temp_cfg:\n";
        #system("cat $temp_cfg");
        $module->send("Read $temp_cfg");
        sleep(1);
        unlink($temp_cfg) or warn "$0: could not remove $temp_cfg: $!\n";
    }
    $not_applied = 0;
    &unset_config_value_state('all') unless $not_saved;
    &set_apply_button_active($not_applied);
}

sub set_apply_button_active($) {
    my $state = shift;
    $win->set_sensitive('applyButton', $state); 
}


# Cancel ---------------------------------------------------------------
$win->add_button( Name => 'cancelButton', 
        Pos     => [395, 470], 
        Size    => [90, 25], 
        Title   => "Cancel", 
        Tip     => "Cancel/Quit windows behaviour settings.");
$win->add_signal_handler('cancelButton', 'clicked', \&check_before_quit);

sub check_before_quit {
    my $changed = 0;
    foreach my $key (keys %config) {
        if ($config{$key}[1] == 1) {
            my $response;
            if ($not_saved) {
                $response = $win->show_msg_dialog("warning", "yes-no", "You have unsaved changes! Continue anyway?");
            } else {
                $response = $win->show_msg_dialog("warning", "yes-no", "You have unapplied changes! Continue anyway?");
            } 
            if ($response eq "yes") {
                last;
            } else {
                return;
            }
        }
    }
    Gtk2->main_quit;
}


#$win->show_and_run();
$win->show();

$module->eventLoop;
