# copyright (C) 1997-2006 Jean-Luc Fontaine (mailto:jfontain@free.fr)
# this program is free software: please read the COPYRIGHT file enclosed in this package or use the Help Copyright menu

# $Id: config.tcl,v 2.134 2006/04/29 11:50:46 jfontain Exp $


namespace eval configuration {

if {$global::withGUI} { ;# >8

    variable container
    variable interface
    variable hierarchy
    variable configure                                                              ;# whether folder should appear when configuring
    if {[string equal $::tcl_platform(platform) unix]} {
        set hierarchy {
            application application.size application.colors application.background application.fonts application.printing
                application.pages application.database application.trace
            viewers viewers.colors viewers.graphs viewers.pies viewers.tables viewers.cells
            tools tools.predictor
            thresholds thresholds.email thresholds.trace
            daemon
        }
        set prefer    {1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1}                               ;# with application background color
        set configure {1 1 0 1 0 0 0 0 0 1 1 1 1 1 1 0 0 0 0 0 0}                         ;# with application backgrounds (per page)
    } else {                                                  ;# printing configuration is done directly at printing time on windows
        set hierarchy {
            application application.size application.colors application.background application.pages application.database
                application.trace
            viewers viewers.colors viewers.graphs viewers.pies viewers.tables viewers.cells
            thresholds thresholds.email thresholds.trace
        }
        set prefer    {1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1}
        set configure {1 1 0 1 0 0 0 1 1 1 1 1 1 0 0 0}
    }

    variable closedIcon [image create photo -data {
        R0lGODlhEAANAPIAAAAAAH9/f7+/v///AP///wAAAAAAAAAAACH+JEZpbGUgd3JpdHRlbiBieSBBZG9iZSBQaG90b3Nob3CoIDUuMAAsAAAAABAADQAAAzNI
        Gsz6kAQxqAjxzcpvc1KWBUDYnRQZWmilYi37EmztlrAt43R8mzrO60P8lAiApHK5TAAAOw==
    }]
    variable openedIcon [image create photo -data {
        R0lGODlhEAANAPIAAAAAAH9/f7+/v///////AAAAAAAAAAAAACH+JEZpbGUgd3JpdHRlbiBieSBBZG9iZSBQaG90b3Nob3CoIDUuMAAsAAAAABAADQAAAzk4
        Gsz6cIQ44xqCZCGbk4MmclAAgNs4ml7rEaxVAkKc3gTAnBO+sbyQT6M7gVQpk9HlAhgHzqhUmgAAOw==
    }]
    variable leafIcon [image create photo -data {
        R0lGODlhDAANAIQAALi8uJiYmPgA+PDw8LC0sGhoaPj4+Pj8+FhYWPD08ODg4IiIiOjs6NDQ0Ojo6Njc2ODk4NjY2NDU0LCwsMjMyKisqMDAwLi4uKioqKCg
        oGhsaAAAAAAAAAAAAAAAAAAAACH5BAEAAAIALAAAAAAMAA0AAAVUIBCM5CicwaCuBFGggyEbB1G/6UzbNZLPh0Bh6IsBEwrCoihLOBmKBqHoTAwYDsUDQLUy
        IIqIZJryZsWNCfUKeUgalIovTLEALoQKJoPQIP6AgQghADs=
    }]
    variable minusIcon [image create photo -data {
        R0lGODlhCQAJAKEAAL+/v////wAAAP///ywAAAAACQAJAAACEYSPoRvG614DQVg7ZZbxoQ8UADs=
    }]
    variable plusIcon [image create photo -data {
        R0lGODlhCQAJAKEAAL+/v////wAAAP///ywAAAAACQAJAAACFISPoRu2spyCyol7G3hxz850CFIAADs=
    }]

} ;# >8                                                               else avoid Tk commands when running under tclsh in daemon mode

    proc load {arrayList} {
        foreach {name value} $arrayList {
            set ::global::$name $value
        }
    }

if {$global::withGUI} { ;# >8

    proc edit {preferencesMode} {                                                                        ;# preferences mode boolean
        variable hierarchy
        variable prefer
        variable configure
        variable container
        variable interface
        variable tree
        variable preferences
        variable dialog
        variable entryIcons
        variable leafIcon
        variable minusIcon
        variable plusIcon

        set preferences $preferencesMode                                                                ;# store mode for sub sheets
        set objects {}                                                                  ;# to delete upon destruction of this folder
        set title {moodss: }
        if {$preferences} {
            append title [mc Preferences]
        } else {
            append title [mc {Dashboard configuration}]
        }
        # do not let the Enter and Return keys close the dialog box so that the thresholds email listEntry widget becomes useful:
        set dialog [new dialogBox .grabber\
            -buttons hoc -default o -title $title -x [winfo pointerx .] -y [winfo pointery .] -enterreturn 0\
            -command configuration::done -helpcommand configuration::help -deletecommand {grab release .grabber} -die 0\
        ]
        grab .grabber                                                      ;# grab siblings such as help window so that it is usable
        lappend objects [linkedHelpWidgetTip $composite::($dialog,help,path)]
        set frame [frame $widget::($dialog,path).frame]
        set tree [Tree $frame.tree\
            -dragenabled 0 -dropenabled 0 -linestipple gray50 -deltay [expr {[font metrics $font::(mediumBold) -linespace] + 4}]\
            -background $widget::option(listbox,background) -selectbackground $widget::option(listbox,selectbackground)\
            -selectforeground $widget::option(listbox,selectforeground)\
            -closecmd {configuration::stateChange 0} -opencmd {configuration::stateChange 1} -selectcommand configuration::open\
            -crossopenimage $minusIcon -crosscloseimage $plusIcon\
        ]                                                                                  ;# to detect instance and cell selections
        $tree bindText <Control-Button-1> {}; $tree bindImage <Control-Button-1> {}                   ;# prevent multiple selections
        set container [frame $frame.container -borderwidth 1 -relief sunken]
        set message [createMessage $container.message]
        if {$preferences} {
            $message configure -text [format [mc {Preferences for the user: %s}] $::tcl_platform(user)]
        } else {
            $message configure -text [mc {Current configuration of the dashboard}]
        }
        pack $message -fill both -expand 1                                                   ;# initially display help message above
        catch {unset interface(current)}                                                               ;# currently opened interface
        foreach entry $hierarchy forPreferences $prefer forConfiguration $configure {
            if {($preferences && !$forPreferences) || (!$preferences && !$forConfiguration)} continue
            foreach {parent child} [split $entry .] {}
            if {[string length $child] == 0} {                                                            ;# no child implies folder
                set node\
                    [$tree insert end root #auto -text [mc $parent] -font $font::(mediumBold) -image $configuration::closedIcon]
                set parentNode $node
            } else {
                set node\
                    [$tree insert end $parentNode #auto -text [mc $child] -font $font::(mediumBold) -image $configuration::leafIcon]
            }
            regsub -all {\.} $entry :: interface($node,class)                            ;# use generated tree index as unique index
            $interface($node,class)::initialize
        }
        pack $tree -side left -fill y -padx 2
        pack $container -fill both -expand 1 -padx 2
        dialogBox::display $dialog $frame
        wm geometry $widget::($dialog,path) 600x300                                                        ;# maintain constant size
        bind $frame <Destroy> "delete $objects"                                      ;# delete objects not managed by the dialog box
    }

    proc open {tree node} {
        variable container
        variable interface

        if {[info exists interface(current)]} {
            if {$node == $interface(current)} return                                                                    ;# no change
            if {![$interface($interface(current),class)::check]} {                 ;# check validity before moving to next interface
                $tree selection set $interface(current)
                bell
                return
            }
        }
        eval destroy [winfo children $container]                                              ;# eventually remove current interface
        set frame [frame $container.frame]             ;# use a separate frame so that interfaces widget management cannot interfere
        pack $frame -fill both -expand 1
        $interface($node,class)::edit $frame
        set interface(current) $node
    }

    proc done {} {
        variable interface
        variable preferences
        variable variables
        variable dialog

        # check validity before closing
        if {[info exists interface(current)] && ![$interface($interface(current),class)::check]} return
        foreach name [array names interface *,class] {
            $interface($name)::apply                                                                             ;# apply new values
        }
        if {$preferences} {
            preferences::save $variables(1) $widget::($dialog,path)
        }
        delete $dialog
    }

    proc help {} {
        variable interface
        variable preferences

        if {[info exists interface(current)]} {
            $interface($interface(current),class)::help
        } elseif {$preferences} {
            generalHelpWindow #core.preferences
        } else {
            generalHelpWindow #core.configuration
        }
    }

    proc createMessage {path args} {                  ;# create a generic message widget with eventual option / value pairs argument
        message $path -width [winfo screenwidth .] -font $font::(mediumNormal) -justify center
        eval $path configure $args
        return $path
    }

    proc initialize {name} {                  ;# for use by folder classes, in order to initialize local variables depending on mode
        variable preferences

        if {$preferences} {
            if {![info exists ::preferences::$name]} {                       ;# initialize from global value if rc file was not read
                set ::preferences::$name [set ::global::$name]
            }
            return [set ::preferences::$name]
        } else {
            return [set ::global::$name]
        }
    }

    proc apply {name value {immediately 0}} {    ;# for use by folder classes, in order to update global variables depending on mode
        variable preferences

        if {$preferences} {
            set namespaces ::preferences
            if {$immediately} {lappend namespaces ::global}
        } else {
            set namespaces ::global
        }
        foreach namespace $namespaces {
            if {![info exists ${namespace}::$name] || ![string equal $value [set ${namespace}::$name]]} {
                set ${namespace}::$name $value             ;# update only when necessary in order not to activate a trace needlessly
            }
        }
    }

    proc variables {preferences} {                      ;# never access configuration variables directly: use this procedure instead
        variable variables

        return $variables($preferences)
    }

    proc stateChange {opened node} {
        variable tree
        variable closedIcon
        variable openedIcon

        if {$opened} {
            $tree itemconfigure $node -image $openedIcon
        } else {
            $tree itemconfigure $node -image $closedIcon
        }
    }


    namespace eval application {

        proc initialize {} {}

        proc edit {parentPath} {
            set message [configuration::createMessage $parentPath.message -text [mc {Application configuration}]]
            pack $message -fill both -expand 1                                               ;# initially display help message above
        }

        proc check {} {
            return 1                                                                                         ;# no data in this page
        }

        proc apply {} {}

        proc variables {} {return {}}

        proc help {} {
            generalHelpWindow #configuration.application
        }

        namespace eval size {                                                                           ;# start of application size

            proc variables {} {                                       ;# list of unqualified global variables handled by this folder
                return {canvasHeight canvasWidth}
            }

            proc initialize {} {
                variable height [configuration::initialize canvasHeight]
                variable width [configuration::initialize canvasWidth]
                variable automatic [expr {($width == 0) && ($height == 0)}]
                variable defaultMessage [mc {Canvas size (in pixels):}]
            }

            proc edit {parentPath} {
                variable height
                variable width
                variable message
                variable automatic
                variable entries
                variable defaultMessage

                set message [configuration::createMessage $parentPath.message -text $defaultMessage]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100                                    ;# occupy whole width
                grid rowconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                set button [checkbutton $parentPath.automatic\
                    -text [mc {automatic scaling}] -command configuration::application::size::update\
                    -variable configuration::application::size::automatic\
                ]
                grid $button -row 1 -column 0 -columnspan 100 -pady 10                                                     ;# center
                set values {640 800 1024 1280 1600}
                set path [spinbox $parentPath.widthEntry -width 4 -values $values]
                $path set $width
                grid $path -row 2 -column 2
                set entries $path
                $path configure -textvariable configuration::application::size::width
                # filter on positive integers and limit entry length
                setupEntryValidation $path {{checkMaximumLength 4 %P} {check31BitUnsignedInteger %P}}
                grid [label $parentPath.width -text [mc width:]] -row 2 -column 1 -padx 2
                grid columnconfigure $parentPath 3 -weight 1
                set values {400 480 600 768 1024 1280}
                set path [spinbox $parentPath.heightEntry -width 4 -values $values]
                $path set $height
                grid $path -row 2 -column 5
                lappend entries $path
                $path configure -textvariable configuration::application::size::height
                # filter on positive integers and limit entry length
                setupEntryValidation $path {{checkMaximumLength 4 %P} {check31BitUnsignedInteger %P}}
                grid [label $parentPath.height -text [mc height:]] -row 2 -column 4 -padx 2
                grid columnconfigure $parentPath 6 -weight 1
                if {!$configuration::preferences} {
                    grid [button $parentPath.apply -text [mc Apply] -command configuration::application::size::apply]\
                        -row 3 -column 0 -columnspan 100
                }
                grid rowconfigure $parentPath 3 -weight 1
                update
            }

            proc update {} {
                variable automatic
                variable entries

                if {$automatic} {set state disabled} else {set state normal}
                foreach entry $entries {
                    $entry configure -state $state
                }
            }

            proc check {} {
                variable height
                variable width
                variable message
                variable automatic
                variable defaultMessage

                set displayed [expr {[info exists message] && [winfo exists $message]}]
                if {!$automatic} {
                    if {([string length $width] == 0) || ($width == 0)} {
                        set error [mc {please set width}]
                    } elseif {([string length $height] == 0) || ($height == 0)} {
                        set error [mc {please set height}]
                    } elseif {$displayed} {
                        # may not exist, as apply procedure is invoked whenever user validates configuration
                        $message configure -font $font::(mediumNormal) -text $defaultMessage
                    }
                }
                if {[info exists error]} {
                    if {$displayed} {
                        $message configure -font $font::(mediumBold) -text $error
                    }
                    return 0
                } else {
                    return 1
                }
            }

            proc apply {} {
                variable height
                variable width
                variable automatic

                if {![check]} return
                if {$automatic} {
                    set width 0; set height 0
                }
                configuration::apply canvasHeight $height
                configuration::apply canvasWidth $width
                if {!$configuration::preferences} {
                    pages::updateScrollRegion $global::canvas
                }
            }

            proc help {} {
                generalHelpWindow #configuration.application.size
            }

        }                                                                                                 ;# end of application size

        namespace eval colors {                                                                       ;# start of application colors

            proc variables {} {
                return canvasBackground
            }

            proc initialize {} {
                variable background [configuration::initialize canvasBackground]
            }

            proc edit {parentPath} {                                             ;# note: this page is only used in preferences mode
                variable background
                variable colorViewer

                set message [configuration::createMessage $parentPath.message -text [mc {Dashboard background color:}]]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100                                    ;# occupy whole width
                grid rowconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                set colorViewer [button $parentPath.choose\
                    -text [mc Choose]... -command "configuration::application::colors::choose $parentPath"\
                ]
                updateColorViewer
                grid $colorViewer -row 1 -column 1
                grid columnconfigure $parentPath 1 -weight 1
                grid columnconfigure $parentPath 2 -weight 1
                grid rowconfigure $parentPath 2 -weight 1
            }

            proc check {} {
                return 1                                                                             ;# chosen color is always valid
            }

            proc apply {} {
                variable background

                if {![check]} return
                configuration::apply canvasBackground $background
            }

            proc updateColorViewer {} {
                variable colorViewer
                variable background

                $colorViewer configure -background $background -foreground [visibleForeground $background]
            }

            proc choose {parentPath} {
                variable background

                set choice [tk_chooseColor -initialcolor $background -title [mc {Choose color:}] -parent $parentPath]
                if {[string length $choice] > 0} {
                    set background $choice
                    updateColorViewer
                }
            }

            proc help {} {
                generalHelpWindow #configuration.application.colors
            }

        }                                                                                               ;# end of application colors

        namespace eval background {                                                               ;# start of application background

            proc variables {} {
                return [list canvasBackground canvasImageFile canvasImagePosition]
            }

            proc initialize {} {
                variable backgrounds
                variable images
                variable positions

                set data [pages::data]
                if {[llength $data] == 0} {                                                                              ;# no pages
                    set backgrounds [list [configuration::initialize canvasBackground]]
                    set images [list [configuration::initialize canvasImageFile]]
                    set positions [list [configuration::initialize canvasImagePosition]]
                } else {
                    set backgrounds {}
                    set images {}
                    set positions {}
                    foreach {page label raised} $data {
                        lappend backgrounds [composite::cget $page -background]
                        lappend images [composite::cget $page -imagefile]
                        lappend positions [composite::cget $page -imageposition]
                    }
                }
            }

            proc edit {parentPath} {                                                   ;# note: page used only in configuration mode
                variable choosers
                variable backgrounds
                variable images
                variable positions
                variable book

                set message [configuration::createMessage $parentPath.message -text [mc {Dashboard background colors and images:}]]
                grid $message -row 0 -column 0
                foreach {left top right bottom} [bounds $global::canvas] {}
                set size [list [expr {$right - $left}] [expr {$bottom - $top}]]
                set data [pages::data]
                if {[llength $data] == 0} {                                                                              ;# no pages
                    set file [lindex $images 0]
                    set chooser [new backgroundChooser $parentPath\
                        -font $font::(mediumNormal) -color [lindex $backgrounds 0] -targetsize $size\
                        -imagefile $file -useimage [expr {[string length $file] > 0}] -position [lindex $positions 0]\
                    ]
                    grid $widget::($chooser,path) -sticky nsew -row 1 -column 0
                    set choosers [list $chooser]
                } else {                                                                                            ;# several pages
                    set book [NoteBook $parentPath.book\
                        -background [$parentPath cget -background] -borderwidth 1 -internalborderwidth 0\
                        -font $font::(mediumNormal) -side $global::pagesTabPosition\
                    ]
                    set choosers {}
                    set first 1
                    foreach {index label raised} $data background $backgrounds file $images position $positions {
                        $book insert end $index
                        $book itemconfigure $index -text $label
                        set chooser [new backgroundChooser [$book getframe $index]\
                            -font $font::(mediumNormal) -color $background -targetsize $size\
                            -imagefile $file -useimage [expr {[string length $file] > 0}] -position $position\
                        ]
                        pack $widget::($chooser,path)
                        lappend choosers $chooser
                        if {$first} {         ;# the first page must always be raised otherwise content is not shown (notebook bug?)
                            $book raise $index
                            set first 0
                        }
                        if {$raised} {$book raise $index}
                    }
                    grid $book -sticky nsew -row 1 -column 0
                    bind $message <Destroy> "destroy $book"                                        ;# delete object upon destruction
                }
                bind $message <Destroy> "+ delete $choosers; unset configuration::application::background::choosers"
                grid [button $parentPath.apply -text [mc Apply] -command configuration::application::background::apply]\
                    -row 2 -column 0 -columnspan 100
                grid rowconfigure $parentPath 0 -weight 1
                grid rowconfigure $parentPath 2 -weight 1
            }

            proc check {} {
                variable backgrounds
                variable choosers
                variable images
                variable positions
                variable book

                if {[info exists choosers]} {                                                        ;# page opened: remember values
                    set backgrounds {}
                    set images {}
                    set positions {}
                    foreach chooser $choosers {
                        backgroundChooser::applyFileEntry $chooser       ;# synchronize file entry in case of manual input from user
                        lappend backgrounds [composite::cget $chooser -color]
                        if {[composite::cget $chooser -useimage]} {
                            lappend images [composite::cget $chooser -imagefile]
                        } else {
                            lappend images {}
                        }
                        lappend positions [composite::cget $chooser -position]
                    }
                }
                return 1                                                                ;# chosen colors and images are always valid
            }

            proc apply {} {
                variable backgrounds
                variable images
                variable positions

                if {![check]} return
                set data [pages::data]
                if {[llength $data] == 0} {                                                                      ;# single main page
                    set background [lindex $backgrounds 0]
                    set file [lindex $images 0]
                    set position [lindex $positions 0]
                    $global::canvas configure -background $background
                    updateCanvasImage $file
                    if {[string length $file] > 0} {
                        updateCanvasImagePosition $global::canvasImageItem $position
                    }
                    configuration::apply canvasBackground $background
                    configuration::apply canvasImageFile $file
                    configuration::apply canvasImagePosition $position
                } else {
                    configuration::apply canvasBackground $global::canvasBackground           ;# since it was specified in variables
                    foreach {page label raised} $data background $backgrounds file $images position $positions {
                        composite::configure $page -background $background -imagefile $file -imageposition $position
                    }
                }
                updateCanvasImagesPosition
                pages::updateScrollRegion $global::canvas                                ;# in case of large image so it wholly fits
            }

            proc help {} {
                generalHelpWindow #configuration.application.background
            }

        }                                                                                           ;# end of application background

        namespace eval fonts {                                                                         ;# start of application fonts

            proc variables {} {
                return {fontFamily fontSize}
            }

            proc initialize {} {
                variable family [configuration::initialize fontFamily]
                variable size [configuration::initialize fontSize]

            }

            proc edit {parentPath} {
                variable family
                variable size
                variable label
                variable sizesEntry

                grid rowconfigure $parentPath 0 -weight 1
                grid rowconfigure $parentPath 3 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 5 -weight 1
                set message [configuration::createMessage $parentPath.message\
                    -text [mc "Global font:\n(restart application for changes to take effect)"]
                ]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100
                grid [label $parentPath.family -text [mc Family:]] -row 1 -column 1 -padx 2
                set entry [new comboEntry $parentPath -font $widget::option(entry,font) -editable 0\
                    -list [lsort -dictionary [font families]] -command configuration::application::fonts::family\
                ]
                lappend objects $entry
                composite::configure $entry entry -textvariable configuration::application::fonts::family
                composite::configure $entry button -listheight 10
                grid $widget::($entry,path) -row 1 -column 2
                set entry [new comboEntry $parentPath -font $widget::option(entry,font) -width 2 -editable 0\
                    -command configuration::application::fonts::size\
                ]
                set sizesEntry $entry
                lappend objects $entry
                composite::configure $entry entry -textvariable configuration::application::fonts::size
                composite::configure $entry button -listheight 10
                grid $widget::($entry,path) -row 1 -column 3
                grid [label $parentPath.pixels -text [mc points]] -row 1 -column 4 -padx 2
                set label [label $parentPath.label -background $widget::option(entry,background) -relief sunken\
                    -borderwidth 1 -pady 5 -text [mc "ABCDEFGHIJKLMNOPQRSTUVWXYZ\nabcdefghijklmnopqrstuvwxyz"]
                ]
                grid $label -sticky ew -row 2 -column 0 -columnspan 100 -padx 10 -pady 10
                bind $message <Destroy> "delete $objects"                                   ;# delete inner objects upon destruction
                family $family                                                                                 ;# which also updates
            }

            proc check {} {
                return 1                                                                             ;# chosen color is always valid
            }

            proc apply {} {
                variable family
                variable size

                if {![check]} return
                configuration::apply fontFamily $family
                configuration::apply fontSize $size
            }

            proc help {} {
                generalHelpWindow #preferences.application.fonts
            }

            proc family {name} {
                variable family $name
                variable sizesEntry
                variable size

                composite::configure $sizesEntry -list [font::sizes $family 1]
                size [font::closest $family $size]
            }

            proc size {value} {
                variable size $value

                update
            }

            proc update {} {
                variable family
                variable size
                variable label

                $label configure -font [list $family $size]
            }

        }                                                                                                ;# end of application fonts

        namespace eval printing {                                                                   ;# start of application printing

            proc variables {} {
                return {printToFile fileToPrintTo printCommand printOrientation printPalette printPaperSize}
            }

            proc initialize {} {
                variable toFile [configuration::initialize printToFile]
                variable printFile [configuration::initialize fileToPrintTo]
                variable command [configuration::initialize printCommand]
                variable orientations
                variable orientation
                variable palettes
                variable palette
                variable sizes
                variable size

                if {![info exists orientations]} {
                    foreach orientation $global::printOrientations {lappend orientations [mc $orientation]}
                    foreach palette $global::printPalettes {lappend palettes [mc $palette]}
                    foreach size $global::printPaperSizes {lappend sizes [mc $size]}
                }
                set index [lsearch -exact $global::printOrientations [configuration::initialize printOrientation]]
                if {$index < 0} {set index 0}
                set orientation [lindex $orientations $index]
                set index [lsearch -exact $global::printPalettes [configuration::initialize printPalette]]
                if {$index < 0} {set index 0}
                set palette [lindex $palettes $index]
                set index [lsearch -exact $global::printPaperSizes [configuration::initialize printPaperSize]]
                if {$index < 0} {set index 0}
                set size [lindex $sizes $index]
            }

            proc edit {parentPath} {
                variable toFile
                variable printFile
                variable command
                variable orientations
                variable palettes
                variable sizes

                set objects {}                                                          ;# to delete upon destruction of this folder
                set row 0
                set message [configuration::createMessage $parentPath.message -text [mc {Printing setup:}]]
                grid $message -sticky nsew -row $row -column 0 -columnspan 3                                   ;# occupy whole width
                grid rowconfigure $parentPath $row -weight 1
                incr row
                radiobutton $parentPath.toCommand\
                    -variable configuration::application::printing::toFile -value 0 -text [mc Command:]
                grid $parentPath.toCommand -row $row -column 0 -sticky w -padx 2
                entry $parentPath.command -textvariable configuration::application::printing::command
                grid $parentPath.command -row $row -column 1 -columnspan 2 -sticky ew -padx 2
                incr row
                radiobutton $parentPath.toFile -variable configuration::application::printing::toFile -value 1 -text [mc {to File:}]
                grid $parentPath.toFile -row $row -column 0 -sticky w -padx 2
                entry $parentPath.file -textvariable configuration::application::printing::printFile
                grid $parentPath.file -row $row -column 1 -sticky ew -padx 2
                button $parentPath.browse\
                    -text [mc Browse]... -command "configuration::application::printing::inquirePrintFile $parentPath"
                grid $parentPath.browse -row $row -column 2 -sticky ew -padx 2
                if {$toFile} {
                    $parentPath.toFile invoke
                } else {
                    $parentPath.toCommand invoke
                }
                incr row
                grid [label $parentPath.orientation -text [mc Orientation:]] -row $row -column 0 -sticky w -padx 2
                set entry [new comboEntry $parentPath -font $widget::option(entry,font) -list $orientations -editable 0]
                lappend objects $entry
                composite::configure $entry entry -textvariable configuration::application::printing::orientation
                composite::configure $entry button -listheight [llength $orientations]
                composite::configure $entry button scroll -selectmode single
                grid $widget::($entry,path) -row $row -column 1 -columnspan 2 -sticky ew -padx 2
                incr row
                grid [label $parentPath.palette -text [mc Palette:]] -row $row -column 0 -sticky w -padx 2
                set entry [new comboEntry $parentPath -font $widget::option(entry,font) -list $palettes -editable 0]
                lappend objects $entry
                composite::configure $entry entry -textvariable configuration::application::printing::palette
                composite::configure $entry button -listheight [llength $palettes]
                composite::configure $entry button scroll -selectmode single
                grid $widget::($entry,path) -row $row -column 1 -columnspan 2 -sticky ew -padx 2
                incr row
                grid [label $parentPath.size -text [mc {Paper size:}]] -row $row -column 0 -sticky w -padx 2
                set entry [new comboEntry $parentPath -font $widget::option(entry,font) -list $sizes -editable 0]
                lappend objects $entry
                composite::configure $entry entry -textvariable configuration::application::printing::size
                composite::configure $entry button -listheight [llength $sizes]
                composite::configure $entry button scroll -selectmode single
                grid $widget::($entry,path) -row $row -column 1 -columnspan 2 -sticky ew -padx 2
                grid rowconfigure $parentPath [incr row] -weight 1
                grid columnconfigure $parentPath 1 -weight 1
                bind $message <Destroy> "delete $objects"                                   ;# delete inner objects upon destruction
            }

            proc inquirePrintFile {parentPath} {
                variable printFile

                set file [tk_getSaveFile\
                    -title [mc {moodss: Print to file}] -parent $parentPath -initialdir [file dirname $printFile]\
                    -defaultextension .ps -filetypes [list {Postscript .ps} [list [mc {All files}] *]]\
                    -initialfile [file tail $printFile]\
                ]
                if {[string length $file] > 0} {                                                                     ;# not canceled
                    set printFile $file
                }
            }

            proc check {} {
                return 1                                                                          ;# chosen values cannot be invalid
            }

            proc apply {} {
                variable toFile
                variable printFile
                variable command
                variable orientations
                variable orientation
                variable palettes
                variable palette
                variable size
                variable sizes

                configuration::apply printToFile $toFile 1                          ;# note: all printing data is needed immediately
                configuration::apply fileToPrintTo [file normalize $printFile] 1                               ;# save absolute path
                configuration::apply printCommand $command 1
                set index [lsearch -exact $orientations $orientation]; if {$index < 0} {set index 0}
                configuration::apply printOrientation [lindex $global::printOrientations $index] 1
                set index [lsearch -exact $palettes $palette]; if {$index < 0} {set index 0}
                configuration::apply printPalette [lindex $global::printPalettes $index] 1
                set index [lsearch -exact $sizes $size]; if {$index < 0} {set index 0}
                configuration::apply printPaperSize [lindex $global::printPaperSizes $index] 1
            }

            proc help {} {
                generalHelpWindow #preferences.application.printing
            }

        }                                                                                             ;# end of application printing

        namespace eval pages {                                                                         ;# start of application pages

            proc variables {} {
                return pagesTabPosition
            }

            proc initialize {} {
                variable position [configuration::initialize pagesTabPosition]
            }

            proc edit {parentPath} {
                set message [configuration::createMessage $parentPath.message -text [mc {Pages tab position:}]]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100                                    ;# occupy whole width
                grid rowconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                grid [\
                    radiobutton $parentPath.top -variable configuration::application::pages::position -value top -text [mc top]\
                ] -row 1 -column 1
                grid [\
                    radiobutton $parentPath.bottom -variable configuration::application::pages::position -value bottom\
                    -text [mc bottom]\
                ] -row 1 -column 2
                grid columnconfigure $parentPath 3 -weight 1
                grid rowconfigure $parentPath 2 -weight 1
            }

            proc check {} {
                return 1                                                                          ;# chosen values cannot be invalid
            }

            proc apply {} {
                variable position

                configuration::apply pagesTabPosition $position
                pages::labelsSide $position
            }

            proc help {} {
                generalHelpWindow #preferences.application.pages
            }

        }                                                                                                ;# end of application pages

        namespace eval database {                                                                   ;# start of application database

            proc variables {} {
                return databaseOptions                     ;# a list of switch, value, switch, ... usable as a Tcl array initializer
            }

            proc initialize {} {
                variable data
                variable password                                                                           ;# used for confirmation
                variable type

                set data(-file) {}                                   ;# for backward compatibility when SQLite was not yet supported
                set data(-database) {}
                array set data [configuration::initialize databaseOptions]
                set type {}
                if {[string length $data(-dsn)] > 0} {
                    set type odbc
                } elseif {[string length $data(-host)] > 0} {
                    set type mysql
                    if {[string length $data(-database)] == 0} {set data(-database) moodss}                            ;# by default
                } elseif {[string length $data(-file)] > 0} {
                    set type sqlite
                }
                catch {set password $data(-password)}                                                           ;# may not yet exist
                if {![info exists data(-debuglevel)]} {
                    set data(-debuglevel) 0                     ;# for backward compatibility when debug level was not yet supported
                }
                if {![string equal $::tcl_platform(platform) unix]} {set data(-debuglevel) 0}
            }

            proc edit {parentPath} {
                variable data
                variable message
                variable label
                variable radioButton
                variable checkButton
                variable entry
                variable password                                                                           ;# used for confirmation
                variable type

                # parameters: file, host, dsn, user, password, port, MySQL database
                set row 0
                set text [mc {Database setup:}]
                if {$global::database != 0} {
                    append text \n
                    append text [mc {(please disconnect from database first)}]
                }
                set message [configuration::createMessage $parentPath.message -text $text]
                grid $message -sticky nsew -row $row -column 0 -columnspan 100                                 ;# occupy whole width
                grid rowconfigure $parentPath $row -weight 1
                incr row
                set radioButton(none) [radiobutton $parentPath.noneChoice\
                    -variable configuration::application::database::type -value {} -text [mc None]\
                    -command configuration::application::database::update\
                ]
                grid $radioButton(none) -row $row -column 0 -sticky w -padx 2
                incr row
                set radioButton(file) [radiobutton $parentPath.fileChoice\
                    -variable configuration::application::database::type -value sqlite -text [mc {SQLite file:}]\
                    -command configuration::application::database::update\
                ]
                grid $radioButton(file) -row $row -column 0 -sticky w -padx 2
                set entry(file) [entry $parentPath.file -textvariable configuration::application::database::data(-file)]
                grid $entry(file) -row $row -column 1 -columnspan 3 -sticky ew -padx 2
                set entry(choose) [button $parentPath.chooseFile\
                    -text [mc Choose]... -command "configuration::application::database::inquireSQLiteFile $parentPath"\
                ]
                grid $entry(choose) -row $row -column 4 -sticky e -padx 2
                incr row
                set radioButton(dsn) [radiobutton $parentPath.dsnChoice\
                    -variable configuration::application::database::type -value odbc -text [mc {ODBC DSN:}]\
                    -command configuration::application::database::update\
                ]
                grid $radioButton(dsn) -row $row -column 0 -sticky w -padx 2
                set entry(dsn) [entry $parentPath.dsn -textvariable configuration::application::database::data(-dsn)]
                grid $entry(dsn) -row $row -column 1 -columnspan 100 -sticky ew -padx 2
                incr row
                set radioButton(host) [radiobutton $parentPath.hostChoice\
                    -variable configuration::application::database::type -value mysql -text [mc {MySQL host:}]\
                    -command configuration::application::database::update\
                ]
                grid $radioButton(host) -row $row -column 0 -sticky w -padx 2
                set entry(host) [entry $parentPath.host -textvariable configuration::application::database::data(-host)]
                grid $entry(host) -row $row -column 1 -columnspan 100 -sticky ew -padx 2
                incr row
                set label(user) [label $parentPath.userLabel -text [mc user:]]
                grid $label(user) -row $row -column 0 -sticky w -padx 2
                set entry(user) [entry $parentPath.user -textvariable configuration::application::database::data(-user)]
                grid $entry(user) -row $row -column 1 -columnspan 100 -sticky ew -padx 2
                incr row
                set label(password) [label $parentPath.passwordLabel -text [mc password:]]
                grid $label(password) -row $row -column 0 -sticky w -padx 2
                set entry(password) [entry $parentPath.password\
                    -textvariable configuration::application::database::data(-password) -width 8 -show *\
                ]
                grid $entry(password) -row $row -column 1 -sticky ew -padx 2
                set label(confirm) [label $parentPath.confirmLabel -text [mc confirm:]]
                grid $label(confirm) -row $row -column 2 -padx 2
                set entry(confirm)\
                    [entry $parentPath.confirm -textvariable configuration::application::database::password -width 8 -show *]
                grid $entry(confirm) -row $row -column 3 -sticky ew -padx 2 -columnspan 2
                incr row
                set label(port) [label $parentPath.portLabel -text [mc port:]]
                grid $label(port) -row $row -column 0 -sticky w -padx 2
                set entry(port) [entry $parentPath.port -textvariable configuration::application::database::data(-port)]
                setupEntryValidation $entry(port) {{check31BitUnsignedInteger %P}}                    ;# filter on positive integers
                grid $parentPath.port -row $row -column 1 -columnspan 100 -sticky ew -padx 2
                incr row
                set label(database) [label $parentPath.databaseLabel -text [mc database:]]
                grid $label(database) -row $row -column 0 -sticky w -padx 2
                set entry(database) [entry $parentPath.database -textvariable configuration::application::database::data(-database)]
                grid $parentPath.database -row $row -column 1 -columnspan 100 -sticky ew -padx 2
                incr row
                set checkButton(trace) [checkbutton $parentPath.trace\
                    -variable configuration::application::database::data(-debuglevel) -text [mc {Trace SQL statements and queries}]\
                ]
                if {![string equal $::tcl_platform(platform) unix]} {$checkButton(trace) configure -state disabled}
                grid $checkButton(trace) -row $row -column 0 -columnspan 100 -sticky w -padx 2
                grid rowconfigure $parentPath [incr row] -weight 1
                grid columnconfigure $parentPath 1 -weight 1
                grid columnconfigure $parentPath 3 -weight 1
                update
            }

            proc update {} {
                variable data
                variable type
                variable label
                variable radioButton
                variable checkButton
                variable entry

                if {$global::database != 0} {                                                       ;# connected: disable everything
                    foreach name {none file dsn host} {$radioButton($name) configure -state disabled}
                    foreach name {user password confirm port database} {$label($name) configure -state disabled}
                    foreach name {file choose dsn host user password confirm port database} {
                        $entry($name) configure -state disabled
                    }
                    $checkButton(trace) configure -state disabled
                    return
                }
                $checkButton(trace) configure -state normal
                switch $type {
                    sqlite {
                        foreach name {file choose} {$entry($name) configure -state normal}
                        foreach name {user password confirm port database} {$label($name) configure -state disabled}
                        foreach name {dsn host user password confirm port database} {$entry($name) configure -state disabled}
                        if {[string length $data(-file)] == 0} {set data(-file) moodss.dat}         ;# by default in local directory
                        focus $entry(file)
                    }
                    odbc {
                        foreach name {user password confirm} {$label($name) configure -state normal}
                        foreach name {dsn user password confirm} {$entry($name) configure -state normal}
                        foreach name {port database} {$label($name) configure -state disabled}
                        foreach name {file host choose port database} {$entry($name) configure -state disabled}
                        focus $entry(dsn)
                    }
                    mysql {
                        foreach name {user password confirm port database} {$label($name) configure -state normal}
                        foreach name {host user password confirm port database} {$entry($name) configure -state normal}
                        foreach name {file dsn choose} {$entry($name) configure -state disabled}
                        if {[string length $data(-host)] == 0} {set data(-host) localhost}                             ;# by default
                        if {[string length $data(-database)] == 0} {set data(-database) moodss}                        ;# by default
                        focus $entry(host)
                    }
                    default {                                                                                         ;# no database
                        foreach name {user password confirm port database} {$label($name) configure -state disabled}
                        foreach name {file choose dsn host user password confirm port database} {
                            $entry($name) configure -state disabled
                        }
                        $checkButton(trace) configure -state disabled
                    }
                }
            }

            proc inquireSQLiteFile {parentPath} {
                variable data

                set file [tk_getSaveFile\
                    -title [mc {moodss: SQLite file}] -parent $parentPath\
                    -initialdir [file dirname $data(-file)] -initialfile [file tail $data(-file)]\
                ]
                if {[string length $file] > 0} {                                                                     ;# not canceled
                    set data(-file) $file
                }
            }

            proc check {} {
                variable data
                variable message
                variable type
                variable entry
                variable password

                set displayed [expr {[info exists message] && [winfo exists $message]}]
                set data(-file) [string trim $data(-file)]
                if {![string equal $type sqlite] && ![string equal $data(-password) $password]} {
                    if {$displayed} {
                        $message configure -font $font::(mediumBold) -text [mc {passwords do not match:}]
                        focus $entry(password)
                    }
                    return 0
                }
                switch $type {
                    sqlite {
                        if {[string length $data(-file)] == 0} {
                            if {$displayed} {
                                $message configure -font $font::(mediumBold) -text [mc {a file name is needed:}]
                                focus $entry(file)
                            }
                            return 0
                        }
                        foreach name {host dsn user password port database} {set data(-$name) {}}
                    }
                    odbc {
                        if {[string length $data(-dsn)] == 0} {
                            if {$displayed} {
                                $message configure -font $font::(mediumBold) -text [mc {a DSN is needed:}]
                                focus $entry(dsn)
                            }
                            return 0
                        }
                        foreach name {file host database} {set data(-$name) {}}
                    }
                    mysql {
                        if {[string length $data(-host)] == 0} {
                            if {$displayed} {
                                $message configure -font $font::(mediumBold) -text [mc {a host is needed:}]
                                focus $entry(host)
                            }
                            return 0
                        }
                        if {[string equal $data(-host) localhost] && ([string length $data(-port)] > 0)} {
                            if {$displayed} {
                                $message configure -font $font::(mediumBold) -text [mc {port useless with local socket connection:}]
                                focus $entry(port)
                            }
                            return 0
                        }
                        if {[string length $data(-database)] == 0} {
                            if {$displayed} {
                                $message configure -font $font::(mediumBold) -text [mc {a database name is needed:}]
                                focus $entry(database)
                            }
                            return 0
                        }
                        foreach name {file dsn} {set data(-$name) {}}
                    }
                    default {                                                                                         ;# no database
                        foreach name {file host dsn user password port database} {set data(-$name) {}}
                    }
                }
                return 1
            }

            proc apply {} {
                variable data

                if {![check]} return
                if {[string length $data(-file)] > 0} {
                    set data(-file) [file normalize $data(-file)]                                              ;# save absolute path
                }
                foreach name [lsort -dictionary [array names data]] {
                    lappend list $name $data($name)
                }
                configuration::apply databaseOptions $list 1                                                   ;# needed immediately
            }

            proc help {} {
                generalHelpWindow #preferences.application.database
            }

        }                                                                                             ;# end of application database

        namespace eval trace {                                                                         ;# start of application trace

            proc variables {} {
                return {automaticallyShowTrace traceNumberOfRows}
            }

            proc initialize {} {
                variable automatic [configuration::initialize automaticallyShowTrace]
                variable numberOfRows [configuration::initialize traceNumberOfRows]
            }

            proc edit {parentPath} {
                variable automatic
                variable numberOfRows
                variable message

                grid rowconfigure $parentPath 0 -weight 1
                grid rowconfigure $parentPath 3 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 3 -weight 1
                set message [configuration::createMessage $parentPath.message -text [mc {Trace window configuration:}]]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100

                set button [checkbutton $parentPath.automatic\
                    -text [mc {automatic display}] -variable configuration::application::trace::automatic\
                ]
                grid $button -row 1 -column 0 -columnspan 100 -pady 10                                                     ;# center

                grid [label $parentPath.rows -text [mc {number of rows:}]] -row 2 -column 1 -padx 2 -sticky w
                set values {5 10 15 20 30 50 100}
                set path [spinbox $parentPath.entry -width 4 -values $values]
                $path set $numberOfRows
                grid $path -row 2 -column 2 -sticky e
                $path configure -textvariable configuration::application::trace::numberOfRows
                # filter on positive integers and limit entry length:
                setupEntryValidation $path {{checkMaximumLength 4 %P} {check31BitUnsignedInteger %P}}
            }

            proc check {} {
                variable numberOfRows
                variable message

                set displayed [expr {[info exists message] && [winfo exists $message]}]
                set valid 1
                if {[string length $numberOfRows] == 0} {
                    set text [mc {please set number of rows}]
                    set valid 0
                } elseif {$numberOfRows == 0} {                                     ;# cannot be negative because of input filtering
                    set text [mc {number of rows cannot be set to 0}]
                    set valid 0
                }
                if {!$valid && $displayed} {
                    $message configure -font $font::(mediumBold) -text $text
                }
                return $valid
            }

            proc apply {} {
                variable automatic
                variable numberOfRows

                if {![check]} return
                configuration::apply automaticallyShowTrace $automatic 1                                ;# could be of immediate use
                configuration::apply traceNumberOfRows $numberOfRows 1                                  ;# could be of immediate use
            }

            proc help {} {
                generalHelpWindow #preferences.application.trace
            }

        }                                                                                                ;# end of application trace

    }                                                                                                          ;# end of application


    namespace eval viewers {

        proc initialize {} {}

        proc edit {parentPath} {
            set message [configuration::createMessage $parentPath.message -text [mc {Viewers configuration}]]
            pack $message -fill both -expand 1                                               ;# initially display help message above
        }

        proc check {} {
            return 1                                                                                         ;# no data in this page
        }

        proc apply {} {}

        proc variables {} {return {}}

        proc help {} {
            generalHelpWindow #configuration.viewers
        }

        namespace eval colors {                                                                           ;# start of viewers colors

            proc variables {} {
                return viewerColors
            }

            proc initialize {} {
                variable colors [configuration::initialize viewerColors]
            }

            proc edit {parentPath} {
                variable colorsFrame

                set message [configuration::createMessage $parentPath.message -text [mc {Change colors:}]]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100
                grid rowconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                set colorsFrame [frame $parentPath.colors -borderwidth 1 -background black]
                refresh
                grid $colorsFrame -row 1 -column 0
                grid rowconfigure $parentPath 2 -weight 1
            }

            proc refresh {} {
                variable colors
                variable colorsFrame

                eval destroy [winfo children $colorsFrame]                                     ;# eventually remove existing buttons
                set index 0
                foreach color $colors {
                    set button [button $colorsFrame.$index -background $color -activebackground $color -borderwidth 1]
                    $button configure -command "configuration::viewers::colors::choose $index"
                    pack $button -side left
                    incr index
                }
            }

            proc choose {index} {
                variable colors
                variable colorsFrame

                set button $colorsFrame.$index
                set background [tk_chooseColor -initialcolor [$button cget -background] -title [mc {Choose color:}] -parent $button]
                if {[string length $background] > 0} {
                    $button configure -background $background
                    set colors [lreplace $colors $index $index $background]
                }
            }

            proc check {} {
                return 1
            }

            proc apply {} {
                variable colors
                variable colorsFrame

                if {![check]} return
                if {![info exists colorsFrame]} return                                 ;# nothing to do as folder was never accessed
                configuration::apply viewerColors $colors
            }

            proc help {} {
                generalHelpWindow #configuration.viewers.colors
            }

        }                                                                                                   ;# end of viewers colors

        namespace eval graphs {                                                                           ;# start of viewers graphs

            proc variables {} {
                return [list\
                    graphNumberOfIntervals graphMinimumY graphXAxisLabelsRotation graphLabelsPosition graphPlotBackground\
                    graphDisplayGrid\
                ]
            }

            proc initialize {} {
                variable numberOfSamples [configuration::initialize graphNumberOfIntervals]
                variable zeroBasedOrdinate [string equal [configuration::initialize graphMinimumY] 0]
                variable degrees [configuration::initialize graphXAxisLabelsRotation]
                variable labelsPositions
                variable labelsPositionsWidth
                variable labelsPosition
                variable plotBackground [configuration::initialize graphPlotBackground]
                variable grid [configuration::initialize graphDisplayGrid]

                if {![info exists labelsPositions]} {
                    set labelsPositionsWidth 0
                    foreach position $global::graphLabelsPositions {
                        lappend labelsPositions [set position [mc $position]]
                        set length [string length $position]
                        if {$length > $labelsPositionsWidth} {set labelsPositionsWidth $length}
                    }
                }
                set index [lsearch -exact $global::graphLabelsPositions [configuration::initialize graphLabelsPosition]]
                if {$index < 0} {set index 0}
                set labelsPosition [lindex $labelsPositions $index]
            }

            proc edit {parentPath} {
                variable numberOfSamples
                variable degrees
                variable message
                variable labelsPositions
                variable labelsPositionsWidth
                variable labelsPosition
                variable colorViewer

                grid rowconfigure $parentPath 0 -weight 1
                grid rowconfigure $parentPath 7 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 4 -weight 1
                set message [configuration::createMessage $parentPath.message -text [mc {Data graphs settings:}]]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100
                if {[info exists databaseInstances::singleton]} {                                           ;# database history mode
                    set state disabled
                } else {
                    set state normal
                }
                grid [label $parentPath.samplesLabel -text [mc {X axis:}] -state $state] -row 1 -column 1 -padx 2 -sticky e
                grid [set frame [frame $parentPath.samples]] -row 1 -column 2 -columnspan 100 -sticky w
                set values {20 50 100 150 200 300 500 1000}
                set path [spinbox $frame.entry -width 4 -values $values -state $state]
                $path set $numberOfSamples
                pack $path -side left
                $path configure -textvariable configuration::viewers::graphs::numberOfSamples
                # filter on positive integers and limit entry length:
                setupEntryValidation $path {{checkMaximumLength 4 %P} {check31BitUnsignedInteger %P}}
                pack [label $frame.samples -text [mc samples] -state $state] -side left
                grid [label $parentPath.yAxis -text [mc {Y axis:}]] -row 2 -column 1 -padx 2 -sticky e
                grid [set frame [frame $parentPath.scale]] -row 2 -column 2 -columnspan 100 -sticky w
                set button [radiobutton $frame.zero\
                    -variable ::configuration::viewers::graphs::zeroBasedOrdinate -value 1 -text [mc {zero based}]\
                ]
                pack $button -side left
                set button [radiobutton $frame.scale\
                    -variable ::configuration::viewers::graphs::zeroBasedOrdinate -value 0 -text [mc {auto scale}]\
                ]
                pack $button -side left
                grid [label $parentPath.rotationLabel -text [mc {X axis labels rotation:}]] -row 3 -column 1 -padx 2 -sticky e
                grid [set frame [frame $parentPath.rotation]] -row 3 -column 2 -columnspan 100 -sticky w
                set path [spinbox $frame.entry -width 2 -state readonly -from 45 -to 90 -increment 5]
                $path set $degrees
                pack $path -side left
                $path configure -textvariable configuration::viewers::graphs::degrees
                pack [label $frame.degrees -text [mc degrees]] -side left
                grid [label $parentPath.labelsLabel -text [mc {Position of labels:}]] -row 4 -column 1 -padx 2 -sticky e
                set entry [new comboEntry $parentPath\
                    -font $widget::option(entry,font) -editable 0 -list $labelsPositions -width $labelsPositionsWidth\
                ]
                lappend objects $entry
                composite::configure $entry entry -textvariable configuration::viewers::graphs::labelsPosition
                composite::configure $entry button -listheight 4
                grid $widget::($entry,path) -row 4 -column 2 -columnspan 100 -sticky w -padx 2
                grid [label $parentPath.backgroundLabel -text [mc {Plot background:}]] -row 5 -column 1 -padx 2 -sticky e
                set colorViewer [button $parentPath.choose\
                    -text [mc Choose]... -command "configuration::viewers::graphs::choose $parentPath"\
                ]
                grid $colorViewer -row 5 -column 2 -columnspan 100 -sticky w -padx 2
                updateColorViewer
                grid rowconfigure $parentPath 5 -pad 2               ;# since the color chooser button is a bit taller than the rest
                grid [label $parentPath.gridLabel -text [mc Grid:]] -row 6 -column 1 -padx 2 -sticky e
                grid [set frame [frame $parentPath.grid]] -row 6 -column 2 -columnspan 100 -sticky w
                set button [radiobutton $frame.on -variable ::configuration::viewers::graphs::grid -value 1 -text [mc displayed]]
                pack $button -side left
                set button [radiobutton $frame.off -variable ::configuration::viewers::graphs::grid -value 0 -text [mc hidden]]
                pack $button -side left
                if {!$configuration::preferences} {
                    grid [button $parentPath.apply -text [mc Apply] -command configuration::viewers::graphs::apply]\
                        -row 7 -column 0 -columnspan 100
                }
                if {[info exists objects]} {
                    bind $message <Destroy> "delete $objects"                               ;# delete inner objects upon destruction
                }
            }

            proc updateColorViewer {} {
                variable colorViewer
                variable plotBackground

                $colorViewer configure -background $plotBackground -foreground [visibleForeground $plotBackground]
            }

            proc choose {parentPath} {
                variable plotBackground

                set choice [tk_chooseColor -initialcolor $plotBackground -title [mc {Choose color:}] -parent $parentPath]
                if {[string length $choice] > 0} {
                    set plotBackground $choice
                    updateColorViewer
                }
            }

            proc check {} {
                variable numberOfSamples
                variable message

                set displayed [expr {[info exists message] && [winfo exists $message]}]
                set valid 1
                if {[string length $numberOfSamples] == 0} {
                    set text [mc {please set number of samples}]
                    set valid 0
                } elseif {$numberOfSamples == 0} {                                  ;# cannot be negative because of input filtering
                    set text [mc {number of samples cannot be set to 0}]
                    set valid 0
                }
                if {!$valid && $displayed} {
                    $message configure -font $font::(mediumBold) -text $text
                }
                return $valid
            }

            proc apply {} {
                variable numberOfSamples
                variable zeroBasedOrdinate
                variable degrees
                variable labelsPositions
                variable labelsPosition
                variable plotBackground
                variable grid

                if {![check]} return
                configuration::apply graphNumberOfIntervals $numberOfSamples
                if {$zeroBasedOrdinate} {set minimum 0} else {set minimum {}}
                configuration::apply graphMinimumY $minimum
                configuration::apply graphXAxisLabelsRotation $degrees
                set index [lsearch -exact $labelsPositions $labelsPosition]; if {$index < 0} {set index 0}
                configuration::apply graphLabelsPosition [set position [lindex $global::graphLabelsPositions $index]]
                configuration::apply graphPlotBackground $plotBackground
                configuration::apply graphDisplayGrid $grid
                if {$configuration::preferences} return
                foreach graph $bltGraph::(graphs) {
                    composite::configure $graph -xlabelsrotation $degrees -plotbackground $plotBackground -grid $grid
                    catch {composite::configure $graph -labelsposition $position}                     ;# predictors lock such option
                    catch {composite::configure $graph -samples $numberOfSamples}                  ;# predictors have no such option
                    catch {composite::configure $graph -yminimum $minimum}                     ;# stacked graphs have no such option
                }
                foreach chart $dataBarChart::(list) {
                    composite::configure $chart -labelsposition $position
                    catch {composite::configure $chart -yminimum $minimum}          ;# stacked bar charts do not support such option
                }
                if {[info exists databaseInstances::singleton]} {
                    composite::configure $databaseInstances::singleton -xlabelsrotation $degrees -plotbackground $plotBackground
                }
            }

            proc help {} {
                generalHelpWindow #configuration.viewers.graphs
            }

        }                                                                                                   ;# end of viewers graphs

        namespace eval pies {                                                                               ;# start of viewers pies

            proc variables {} {
                return pieLabeler
            }

            proc initialize {} {
                variable labeler [configuration::initialize pieLabeler]
            }

            proc edit {parentPath} {
                set message [configuration::createMessage $parentPath.message -text [mc {Data values position:}]]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100
                grid rowconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                set button [radiobutton $parentPath.box\
                    -variable ::configuration::viewers::pies::labeler -value box -text [mc {next to labels}]\
                ]
                grid $button -row 1 -column 1
                set button [radiobutton $parentPath.peripheral\
                    -variable ::configuration::viewers::pies::labeler -value peripheral -text [mc peripheral]\
                ]
                grid $button -row 1 -column 2
                grid columnconfigure $parentPath 3 -weight 1
                grid rowconfigure $parentPath 2 -weight 1
            }

            proc check {} {
                return 1                                                                            ;# simple choice so always valid
            }

            proc apply {} {
                variable labeler

                if {![check]} return
                configuration::apply pieLabeler $labeler
            }

            proc help {} {
                generalHelpWindow #configuration.viewers.pies
            }

        }                                                                                                     ;# end of viewers pies

        namespace eval tables {                                                                           ;# start of viewers tables

            proc variables {} {
                return currentValueTableRows
            }

            proc initialize {} {
                variable numberOfRows [configuration::initialize currentValueTableRows]
            }

            proc edit {parentPath} {
                variable numberOfRows
                variable message

                grid rowconfigure $parentPath 0 -weight 1
                grid rowconfigure $parentPath 2 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 3 -weight 1
                set message [configuration::createMessage $parentPath.message\
                    -text [mc {Values table settings (in database history mode):}]\
                ]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100
                grid [label $parentPath.rows -text [mc {maximum number of rows:}]] -row 1 -column 1 -padx 2 -sticky w
                set values {10 20 50 100 200 500 1000 2000 5000 10000}
                set path [spinbox $parentPath.entry -width 6 -values $values]
                $path set $numberOfRows
                grid $path -row 1 -column 2 -sticky e
                $path configure -textvariable configuration::viewers::tables::numberOfRows
                # filter on positive integers and limit entry length:
                setupEntryValidation $path {{checkMaximumLength 6 %P} {check31BitUnsignedInteger %P}}
            }

            proc check {} {
                variable numberOfRows
                variable message

                set displayed [expr {[info exists message] && [winfo exists $message]}]
                set valid 1
                if {[string length $numberOfRows] == 0} {
                    set text [mc {please set number of rows}]
                    set valid 0
                } elseif {$numberOfRows == 0} {                                     ;# cannot be negative because of input filtering
                    set text [mc {number of rows cannot be set to 0}]
                    set valid 0
                }
                if {!$valid && $displayed} {
                    $message configure -font $font::(mediumBold) -text $text
                }
                return $valid
            }

            proc apply {} {
                variable numberOfRows

                if {![check]} return
                configuration::apply currentValueTableRows $numberOfRows
            }

            proc help {} {
                generalHelpWindow #configuration.viewers.tables
            }

        }                                                                                                   ;# end of viewers tables

        namespace eval cells {                                                                             ;# start of viewers cells

            proc variables {} {
                return cellsLabelModuleHeader
            }

            proc initialize {} {
                variable identify [configuration::initialize cellsLabelModuleHeader]
            }

            proc edit {parentPath} {
                set message [configuration::createMessage $parentPath.message\
                    -text [mc "Whether module identifier\nis included in data cells labels:"]\
                ]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100                                    ;# occupy whole width
                grid rowconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                grid [radiobutton $parentPath.top -variable configuration::viewers::cells::identify -value 1 -text [mc yes]]\
                    -row 1 -column 1
                grid [radiobutton $parentPath.bottom -variable configuration::viewers::cells::identify -value 0 -text [mc no]]\
                    -row 1 -column 2
                grid columnconfigure $parentPath 3 -weight 1
                grid rowconfigure $parentPath 2 -weight 1
            }

            proc check {} {
                return 1                                                                            ;# simple choice so always valid
            }

            proc apply {} {
                variable identify

                if {![check]} return
                configuration::apply cellsLabelModuleHeader $identify
                if {$configuration::preferences} return
                foreach viewer $viewer::(list) {
                    viewer::updateLabels $viewer
                }
            }

            proc help {} {
                generalHelpWindow #configuration.viewers.cells
            }

        }                                                                                                    ;# end of viewers cells

    }                                                                                                              ;# end of viewers


    namespace eval tools {

        proc initialize {} {}

        proc edit {parentPath} {
            set message [configuration::createMessage $parentPath.message -text [mc {Tools configuration}]]
            pack $message -fill both -expand 1                                               ;# initially display help message above
        }

        proc check {} {
            return 1                                                                                         ;# no data in this page
        }

        proc apply {} {}

        proc variables {} {return {}}

        proc help {} {
            generalHelpWindow #preferences.tools
        }

        namespace eval predictor {                                                                       ;# start of tools predictor
            proc variables {} {
                return [list rPath predictorMaximumProcesses showSampleDatasets]
            }

            proc initialize {} {
                variable path [configuration::initialize rPath]
                variable processes [configuration::initialize predictorMaximumProcesses]
                variable samples [configuration::initialize showSampleDatasets]
            }

            proc edit {parentPath} {
                variable parent $parentPath
                variable path
                variable processes
                variable samples

                set row 0
                set message [configuration::createMessage $parentPath.message -text [mc {Predictor setup:}]]
                grid $message -sticky nsew -row $row -column 0 -columnspan 3                                   ;# occupy whole width
                grid rowconfigure $parentPath $row -weight 1
                incr row
                grid [label $parentPath.path -text [mc {R program path:}]] -row $row -column 0 -sticky w -padx 2
                entry $parentPath.file -textvariable configuration::tools::predictor::path
                grid $parentPath.file -row $row -column 1 -sticky ew -padx 2
                button $parentPath.browse\
                    -text [mc Browse]... -command "configuration::tools::predictor::inquirePath $parentPath"
                grid $parentPath.browse -row $row -column 2 -sticky ew -padx 2
                incr row
                grid [label $parentPath.number -text [mc {Number of processes:}]] -row $row -column 0 -sticky w -padx 2
                set spin [spinbox $parentPath.processes\
                    -width 2 -from 1 -to 32 -increment 1 -state readonly -justify right\
                    -textvariable configuration::tools::predictor::processes\
                ]
                grid $spin -row $row -column 1 -sticky w -padx 2
                incr row
                checkbutton $parentPath.samples\
                    -text [mc {Sample time series in database}] -variable configuration::tools::predictor::samples
                grid $parentPath.samples -row $row -column 0 -columnspan 100 -sticky w -padx 2
                grid rowconfigure $parentPath [incr row] -weight 1
                grid columnconfigure $parentPath 1 -weight 1
            }

            proc inquirePath {parentPath} {
                variable path

                set file [tk_getOpenFile\
                    -title [mc {moodss: R program path}] -parent $parentPath -initialdir [file dirname $path]\
                    -initialfile [file tail $path]\
                ]
                if {$file ne ""} {                                                                                   ;# not canceled
                    set path $file
                }
            }

            proc check {} {
                variable parent
                variable path

                set path [string trim $path]
                if {$path eq ""} {return 1}                                                                ;# allow R not to be used
                if {[catch {exec $path --version} result]} {
                    tk_messageBox -parent $parent -title [mc {moodss: R program error}] -type ok -icon error -message $result
                    return 0
                }
                # examples: "R 2.2.0 (2005-10-06).Copyright (C) 2005 R Dev...", "R version 2.3.0 (2006-04-24)..."
                foreach {program version value} $result break
                if {$version eq "version"} {set version $value}                                                             ;# R 2.3
                if {($program ne "R") || ([package vcompare $version 2.1.1] < 0)} {
                    tk_messageBox -parent $parent -title [mc {moodss: R program error}] -type ok -icon error\
                        -message [mc {moodss: invalid R program or version (2.1.1 or above)}]
                    return 0
                }
                return 1
            }

            proc apply {} {
                variable path
                variable processes
                variable samples

                configuration::apply rPath $path 1
                configuration::apply predictorMaximumProcesses $processes 1
                configuration::apply showSampleDatasets $samples 1
                database::initializeSamples $samples
            }

            proc help {} {
                generalHelpWindow #preferences.tools.predictor
            }

        }                                                                                                  ;# end of tools predictor

    }                                                                                                                ;# end of tools


    namespace eval thresholds {

        proc initialize {} {}

        proc edit {parentPath} {
            set message [configuration::createMessage $parentPath.message -text [mc {Thresholds configuration}]]
            pack $message -fill both -expand 1
        }

        proc check {} {
            return 1                                                                                         ;# no data in this page
        }

        proc apply {} {}

        proc variables {} {return {}}

        proc help {} {
            generalHelpWindow #preferences.thresholds
        }

        namespace eval email {                                                                          ;# start of thresholds email

            proc variables {} {
                return {fromAddress smtpServers mailSubject mailBody}
            }

            proc initialize {} {
                variable from [configuration::initialize fromAddress]
                variable servers [configuration::initialize smtpServers]
                variable subject [configuration::initialize mailSubject]
                variable body [configuration::initialize mailBody]
            }

            proc edit {parentPath} {
                variable servers
                variable list
                variable body
                variable text
                variable parent $parentPath
                variable message

                set row 0
                set message [configuration::createMessage $parentPath.message -text [mc {Mail settings:}]]
                grid $message -row $row -column 0 -columnspan 3 -pady 5                                        ;# occupy whole width
                incr row
                set label [label $parentPath.from -text [mc {From address:}]]
                grid $label -row $row -column 0 -columnspan 2 -sticky w -padx 2
                set entry [entry $parentPath.address -textvariable configuration::thresholds::email::from]
                grid $entry -row $row -column 2 -sticky ew -padx 2
                incr row
                set label [label $parentPath.out -justify left -text [mc "Outgoing mail\nSMTP servers:"]]
                grid $label -row $row -column 0 -columnspan 2 -sticky nw -padx 2
                set list [new listEntry $parentPath]
                listEntry::set $list $servers
                grid $widget::($list,path) -row $row -column 2 -sticky nsew -padx 2
                incr row
                set label [label $parentPath.subjectLabel -text [mc Subject:]]
                grid $label -row $row -column 0 -sticky w -padx 2
                set font $font::(fixedNormal)
                set entry [entry $parentPath.subject -font $font -textvariable configuration::thresholds::email::subject]
                grid $entry -row $row -column 1 -columnspan 2 -sticky ew -padx 2
                incr row
                set label [label $parentPath.bodyLabel -text [mc Body:]]
                grid $label -row $row -column 0 -sticky nw -padx 2
                set text [text $parentPath.body -height 1 -background white -font $font]
                $text insert end $body
                setupTextBindings $text
                grid $text -row $row -column 1 -rowspan 2 -columnspan 2 -sticky nsew -padx 2
                incr row
                set button [button $parentPath.default\
                    -text [mc Default] -command configuration::thresholds::email::default -padx 2\
                ]
                set tip [new widgetTip -path $button -text [mc {reset email message subject and body to default values}]]
                bind $button <Destroy> "delete $tip"
                grid $button -row $row -column 0 -sticky s
                grid [frame $parentPath.filler -height [font metrics $font -ascent]]
                grid rowconfigure $parentPath $row -weight 1
                grid columnconfigure $parentPath 2 -weight 1
                # delete inner objects upon destruction:
                bind $message <Destroy> "delete $list; unset configuration::thresholds::email::list"
            }

            proc default {} {
                variable subject
                variable body
                variable text

                set subject $global::mail(subject,default)
                set body $global::mail(body,default)
                $text delete 1.0 end
                $text insert end $body
            }

            proc check {} {                                       ;# only possible once this interface has been opened at least once
                variable from
                variable parent
                variable message

                set displayed [expr {[info exists message] && [winfo exists $message]}]
                set from [string trim $from]
                if {[string length $from] == 0} {
                    if {$displayed} {
                        $message configure -font $font::(mediumBold) -text [mc {please set From address}]
                    }
                    return 0
                }
                if {[string length [emailAddressError $from]] > 0} {
                    tk_messageBox -parent $parent -title [mc {moodss: Email error}] -type ok -icon error\
                        -message "$from: [emailAddressError $from]"
                    return 0
                }
                return 1
            }

            proc apply {} {
                variable from
                variable servers
                variable subject
                variable body
                variable text
                variable list

                configuration::apply fromAddress $from 1                               ;# all email data could be needed immediately
                if {[info exists list]} {                                                                 ;# if interface is visible
                    set servers [listEntry::get $list]                                                               ;# update value
                    set body [$text get 1.0 end]
                }
                configuration::apply smtpServers $servers 1
                configuration::apply mailSubject [string trim $subject] 1
                configuration::apply mailBody [string trim $body] 1
            }

            proc help {} {
                generalHelpWindow #preferences.thresholds.email
            }

        }                                                                                                 ;# end of thresholds email

        namespace eval trace {                                                                          ;# start of thresholds trace

            proc variables {} {
                return traceThresholds
            }

            proc initialize {} {
                variable trace [configuration::initialize traceThresholds]
            }

            proc edit {parentPath} {
                set message [configuration::createMessage $parentPath.message\
                    -text [mc "Whether thresholds messages\nare sent to the trace module:"]]
                grid $message -sticky nsew -row 0 -column 0 -columnspan 100
                grid rowconfigure $parentPath 0 -weight 1
                grid columnconfigure $parentPath 0 -weight 1
                set button [radiobutton $parentPath.yes -variable ::configuration::thresholds::trace::trace -value 1 -text [mc yes]]
                grid $button -row 1 -column 1
                set button [radiobutton $parentPath.no -variable ::configuration::thresholds::trace::trace -value 0 -text [mc no]]
                grid $button -row 1 -column 2
                grid columnconfigure $parentPath 3 -weight 1
                grid rowconfigure $parentPath 2 -weight 1
            }

            proc check {} {
                return 1                                                                            ;# simple choice so always valid
            }

            proc apply {} {
                variable trace

                if {![check]} return
                configuration::apply traceThresholds $trace 1                                         ;# could be immediately needed
            }

            proc help {} {
                generalHelpWindow #preferences.thresholds.trace
            }

        }                                                                                                 ;# end of thresholds trace

    }


    namespace eval daemon {

        proc variables {} {
            return moompsResourceFile
        }

        proc initialize {} {
            variable file [configuration::initialize moompsResourceFile]
            variable current $file
        }

        proc edit {parentPath} {
            variable file
            variable message

            set message [configuration::createMessage $parentPath.message]
            resetMessage $message
            grid $message -sticky nsew -row 0 -column 0 -columnspan 100                                        ;# occupy whole width
            grid rowconfigure $parentPath 0 -weight 1
            grid [label $parentPath.label -text [mc {Preferences file:}]] -row 1 -column 0 -sticky w -padx 2
            entry $parentPath.file -textvariable configuration::daemon::file -width 32
            grid $parentPath.file -row 2 -column 0 -sticky ew -padx 2
            grid columnconfigure $parentPath 0 -weight 1
            button $parentPath.browse -text [mc Browse]... -command "configuration::daemon::inquireFile $parentPath"
            grid $parentPath.browse -row 2 -column 1 -sticky e -padx 2
            grid rowconfigure $parentPath 3 -weight 1
        }

        proc resetMessage {message} {
            $message configure -font $font::(mediumNormal) -text [mc {moomps daemon configuration:}]
        }

        proc inquireFile {parentPath} {
            variable file

            set value [tk_getSaveFile\
                -title [mc {moodss: Daemon preferences file}] -parent $parentPath\
                -initialdir [file dirname $file] -initialfile [file tail $file]\
            ]
            if {[string length $value] > 0} {                                                                        ;# not canceled
                set file $value
            }
        }

        proc check {} {
            variable file
            variable message

            set displayed [expr {[info exists message] && [winfo exists $message]}]
            if {$displayed} {
                resetMessage $message
            }
            set user $::tcl_platform(user)
            if {[file exists $file]} {                                                                        ;# file already exists
                if {[file isdirectory $file]} {
                    set error [mc {file cannot be a directory}]
                } elseif {![file writable $file]} {
                    set error [format [mc {file not writable by user: %s}] $user]
                } elseif {![catch {set channel [open $file]} error]} {
                    unset error
                    gets $channel
                    set line [string trim [gets $channel]]                                                   ;# retrieve second line
                    if {![string equal $line {<!DOCTYPE moompsPreferences>}]} {
                        set error [mc {not a moomps preferences file}]
                    }
                    close $channel
                }
            } elseif {![file writable [file dirname $file]]} {                                    ;# file must eventually be created
                set error [format [mc "directory: %1\$s\nnot writable by user: %2\$s"] [file dirname $file] $user]
            }
            if {[info exists error]} {                                                                        ;# there was a problem
                if {$displayed} {
                    $message configure -font $font::(mediumBold) -text $error
                }
                return 0
            } else {
                return 1
            }
        }

        proc apply {} {
            variable file
            variable current

            if {[string equal $file $current]} return         ;# no change: avoid checking every time the user validates preferences
            if {![check]} return
            set current $file
            configuration::apply moompsResourceFile [file normalize $file] 1                       ;# save absolute path immediately
        }

        proc help {} {
            generalHelpWindow #preferences.moomps
        }

    }

    # contruct configuration variables list which can be used at any time (i.e. when saving configuration to file)
    variable variables
    set variables(0) {}                                                                                         ;# for configuration
    set variables(1) {}                                                                                           ;# for preferences
    foreach entry $hierarchy forPreferences $prefer forConfiguration $configure {
        regsub -all {\.} $entry :: class
        if {$forConfiguration} {
            set variables(0) [concat $variables(0) [${class}::variables]]
        }
        if {$forPreferences} {
            set variables(1) [concat $variables(1) [${class}::variables]]
        }
    }

} ;# >8

}
