#!/usr/bin/wish8.3
# or
#!/opt/tcltk/bin/wish8.4

# copyright (C) 1997-2001 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


proc recordRCSId {variableName empty operation} {
    foreach {header file version} $::rcsId break
    regsub {,v$} $file {} file
    set ::sourceVersion($file) $version
}
trace variable ::rcsId w recordRCSId                                                                ;# keep track of RCS identifiers

set rcsId {$Id: moodss.tcl,v 2.61 2001/02/28 20:36:10 jfontain Exp $}


option add *BorderWidth 1                                                        ;# reduce all widgets border width to improve looks
option add *Canvas.BorderWidth 0                                                               ;# restore original values for canvas
option add *Frame.BorderWidth 0                                                                                             ;# frame
option add *Toplevel.BorderWidth 0                                                                                   ;# and toplevel
option add *ScrollbarWidth 12
option add *Checkbutton.selectColor gray
option add *Radiobutton.selectColor gray
option add *Menu.selectColor gray

proc printUsage {exitCode} {
    puts stderr "Usage: $::argv0 \[OPTION\]... \[MODULE\] \[MODULE\]..."
    puts stderr {  --debug          module errors verbose reporting}
    puts stderr {  -f, --file       configuration file name}
    puts stderr {  -h, --help       display this help and exit}
    puts stderr {  -p, --poll-time  poll time in seconds}
    puts stderr {  -r, --read-only  disable viewer creation, editing, ...}
    puts stderr {  -S, --static     disable internal window manager sizing and moving}
    puts stderr {  --show-modules   try to find available moodss modules}
    puts stderr {  --version        output version information and exit}
    exit $exitCode
}

proc printVersion {} {
    puts "moodss (a Modular Object Oriented Dynamic SpreadSheet) version $global::applicationVersion"
}

source misc.tcl
source utility.tcl
startGatheringPackageDirectories
source getopt.tcl

if {[catch\
    {\
        set argv [parseCommandLineArguments\
            {
                -f 1 --file 1 --debug 0 -h 0 -he 0 -hel 0 -help 0 --help 0 -p 1 --poll-time 1 -r 0 --read-only 0 -S 0 --static 0
                --show-modules 0 --version 0
            } $argv arguments\
        ]\
    }\
    message\
]} {
    puts stderr $message
    printUsage 1
}

foreach {short long} {-f --file -h --help -p --poll-time -r --read-only -S --static} {
    catch {set arguments($short) $arguments($long)}                                          ;# long version if present has priority
}

if {[info exists arguments(-h)]||[info exists arguments(-he)]||[info exists arguments(-hel)]||[info exists arguments(-help)]} {
    printUsage 1
}

# since it invokes some Tk commands which are not available when -help (or shortened) switch is passed in command line, this file
# sourcing must appear after -help switch detection and processing
source global.tcl

if {[info exists arguments(--version)]} {
    printVersion
    exit
}

set global::debug [info exists arguments(--debug)]

source entrychk.tcl
source preferen.tcl
source config.tcl
configuration::load [preferences::read]                                               ;# initialize from rc file as soon as possible

# search in current directory sub-directories for development and Tcl package moodss sub-directory
if {[string equal $tcl_platform(platform) unix]} {
    if {[lsearch -exact $auto_path /usr/lib]<0} {                     ;# in case Tcl/Tk is somewhere else than in the /usr hierarchy
        lappend auto_path /usr/lib /usr/lib/moodss                                          ;# moodss rpm installs in /usr hierarchy
    }
    lappend auto_path $::tcl_pkgPath/moodss [pwd]
} else {                                                                                                                  ;# windows
    lappend auto_path $::tcl_libPath/moodss [pwd]
}
package require msgcat
namespace import msgcat::*
package require internationalization

if {[catch {package require stooop 4.1}]} {
    source stooop.tcl                                                                     ;# in case stooop package is not installed
}
namespace import stooop::*
if {[catch {package require switched 2.2}]} {                                           ;# in case switched package is not installed
    source switched.tcl
}

source module.tcl
source modperl.tcl
source modpython.tcl
source modules.tcl

if {[info exists arguments(--show-modules)]} {
    modules::printAvailable
    exit                                                                                                                     ;# done
}

if {[catch {package require scwoop 4.0}]} {
    source scwoutil.tcl
    source scwoop.tcl                                                                     ;# in case scwoop package is not installed
    source bindings.tcl
    source widgetip.tcl
    source arrowbut.tcl
    if {$tcl_version<8.4} {
        source spinent.tcl
    }
    source panner.tcl
    source scroll.tcl
    source combobut.tcl
    source scrolist.tcl
    source comboent.tcl
}
if {[catch {package require tkpiechart 6.3}]} {                                       ;# in case tkpiechart package is not installed
    source pielabel.tcl
    source boxlabel.tcl
    source relirect.tcl
    source canlabel.tcl
    source labarray.tcl
    source perilabel.tcl
    source slice.tcl
    source selector.tcl
    source objselec.tcl
    source pie.tcl
}
if {[catch {package require Tktable 2.6}]} {                                             ;# in case tkTable package is not installed
    switch $tcl_platform(platform) {
        unix {
            load ./Tktable.so
        }
        windows {
            load ./tktable.dll
        }
    }
}
if {[catch {package require BLT 2.4}]} {                                                     ;# in case BLT package is not installed
    switch $tcl_platform(platform) {
        unix {
            load ./libBLT.so
        }
        windows {
            load ./blt.dll
        }
    }
}
catch {package require smtp 1.2}                                                     ;# eventually allow thresholds email capability

# intercept closing from window manager so that exit can be effectively used when renamed
# and that shutting down when hung initializing a remote capable module is possible
wm protocol . WM_DELETE_WINDOW exit
wm command . [concat $argv0 $argv]                                   ;# for proper window manager (windowmaker for example) behavior
wm group . .

source font.tcl
source scrollbl.tcl
source xifo.tcl
source lifolbl.tcl
source dialog.tcl
source viewer.tcl
source keyslink.tcl
source help.tcl
source threshold.tcl
source gui.tcl

# frame used as a common parent, for example to configuration and general help windows, so user can interact with both but not other
frame .grabber                                                                                      ;# windows, such as the main one

pack [createMessageWidget .] -side bottom -fill x                              ;# so that modules can display informational messages
update

wm title . {moodss: loading modules...}                   ;# load uninitialized modules 1st so that their tables are placed properly

source record.tcl

if {[info exists arguments(-f)]} {                                                              ;# configuration file name specified
    # modules from save file must be loaded before any command line module to preserve data namespace indices (see modules code)
    set global::saveFile $arguments(-f)
    set initializer [new record -file $global::saveFile]
    record::read $initializer
    configuration::load [record::configurationData $initializer]                                 ;# set global values from save file
    modules::parse [record::modulesWithArguments $initializer]                                                          ;# recursive
    set modules::(initialized) [record::modules $initializer]
} else {
    set global::saveFile {}
}

if {[catch {modules::parse $argv} message]} {                                                                           ;# recursive
    puts stderr $message
    exit 1
}

wm title . {moodss: initializing modules...}

if {[catch modules::initialize message]} {                                                     ;# with user feedback in message area
    puts stderr $message
    exit 1
}

source canvhand.tcl
source canvaswm.tcl
source colorlab.tcl
source blt2d.tcl
source databar.tcl
source graph.tcl
source datagraf.tcl
source stagraph.tcl
source datapie.tcl
source sumtable.tcl
source freetext.tcl
source drag.tcl
source drop.tcl
source menuhelp.tcl
source printcap.tcl
source prntview.tcl
source print.tcl
source scroller.tcl
source modgui.tcl

set readOnly [info exists arguments(-r)]
set global::static [info exists arguments(-S)]

set global::scroll [new scroll canvas .]
set global::canvas $composite::($global::scroll,scrolled,path)
$global::canvas configure -highlightthickness 0 -takefocus 0
trace variable global::canvasHeight w updateCanvasSize                                       ;# track changes for good user feedback
trace variable global::canvasWidth w updateCanvasSize
updateCanvasSize
trace variable global::canvasBackground w updateCanvasBackground                             ;# track changes for good user feedback
updateCanvasBackground

set global::windowManager [new canvasWindowManager $global::canvas]

if {[info exists ::geometry]} {                                                               ;# command line geometry was specified
    wm geometry . $::geometry
} elseif {[info exists initializer]} {
    foreach {width height} [record::sizes $initializer] {}                                   ;# used stored geometry for scroll area
    composite::configure $global::scroll -width $width -height $height
} else {
    wm geometry . 450x300
}

image create photo applicationIcon -data [dataGraph::iconData]                                ;# use data graph icon for application
if {[string equal $tcl_platform(platform) unix]} {
    wm iconname . "moodss [modules::names]"                                                       ;# give the icon a meaningful name
    wm iconwindow . [toplevel .icon]
    pack [label .icon.image -image applicationIcon]
}

if {!$readOnly} {
    pack [createDragAndDropZone .] -fill x
}
pack $widget::($global::scroll,path) -fill both -expand 1

if {[info exists arguments(-p)]} {                                                     ;# command line argument has highest priority
    modules::setPollTimes $arguments(-p)
} elseif {[info exists initializer]} {                                                                  ;# then stored configuration
    modules::setPollTimes [record::pollTime $initializer]
} else {                                                                                                       ;# use modules values
    modules::setPollTimes
}

updateTitle                                                                              ;# now that modules and poll time are known
createMenuWidget . $readOnly [llength $global::pollTimes]

source tablesel.tcl
source datatab.tcl
source lastwish.tcl
source htmllib.tcl                                                ;# Tcl HTML library from Sun, used for viewing HTML help documents
source htmldata.tcl
source htmlview.tcl
source html.tcl                                            ;# must be sourced after HTML library since some procedures are redefined

proc createCellsViewer {class cells draggable static {pollTime {}}} {
    set viewer [new $class $global::canvas -draggable $draggable]
    if {[string length $pollTime]>0} {
        composite::configure $viewer -interval $pollTime
    }
    viewer::view $viewer $cells
    manageViewer $viewer 1 -static $static
    return $viewer
}

proc manageViewer {viewer destroyable args} {                         ;# arguments are window manager configuration switched options
    set path $widget::($viewer,path)
    canvasWindowManager::manage $global::windowManager $path
    eval canvasWindowManager::configure $global::windowManager $path $args
    if {$destroyable} {
        composite::configure $viewer -deletecommand "canvasWindowManager::unmanage $global::windowManager $path"
    }
}

proc save {ask} {                               ;# save current configuration in user defined file or currently defined storage file
    if {$ask||([string length $global::saveFile]==0)} {
        set file [tk_getSaveFile\
            -title {moodss: Save} -initialdir [pwd] -defaultextension .moo -filetypes {{{moodss data} .moo}}\
            -initialfile $global::saveFile
        ]
        if {[string length $file]==0} return                                                                 ;# user canceled saving
        updateFileSaveMenuHelp [set global::saveFile $file]
    }
    # flash instead of pushing as that goes too fast to be seen
    lifoLabel::flash $global::messenger "saving in $global::saveFile..."
    set record [new record -file $global::saveFile]
    record::write $record
    delete $record
    record::snapshot                                                                               ;# remember current configuration
}

proc refresh {} {
    static updateEvent

    catch {after cancel $updateEvent}                                                             ;# eventually cancel current event
    if {[llength $modules::(synchronous)]==0} return                                                                ;# nothing to do
    foreach instance $modules::(synchronous) {
        set namespace $modules::instance::($instance,namespace)
        ${namespace}::update                                                                ;# ask module to update its dynamic data
    }
    set updateEvent [after [expr {1000*$global::pollTime}] refresh]                                       ;# convert to milliseconds
}

proc changeAllCellsColor {array row column color} {                                                  ;# color is empty for resetting
    dataTable::changeAllCellsColor $array $row $column $color
    viewer::changeAllCellsColor $array $row $column $color
}

rename exit _exit
proc exit {{code 0}} {                                                             ;# intercept exit in case of unsaved changes, ...
    if {$code!=0} {                                                                            ;# exit immediately in case of errors
        _exit $code
    }
    if {![record::changed]} _exit
    set message {There are unsaved configuration changes. Do you want them saved to file}
    set file [string length $::global::saveFile]
    if {$file} {                                                                                             ;# there is a save file
        append message ": $::global::saveFile"
    }
    append message ?
    set action [tk_dialog .saveorexit {moodss: Save} $message question 0 Yes No Cancel]
    switch $action {
        0 {
            save [expr {!$file}]
            if {![record::changed]} _exit                                                                          ;# data was saved
        }
        1 _exit
    }
}

set draggable [expr {!$readOnly}]


proc createSavedViewers {record} {
    foreach {class cells x y width height level switchedOptions} [record::viewersData $record] {
        if {[string equal $class ::thresholds]} {                        ;# thresholds viewer is a special case and is not displayed
            set viewer $thresholds::singleton
            eval switched::configure $viewer $switchedOptions
        } else {
            set viewer [eval new $class $global::canvas $switchedOptions -draggable $::draggable]
            foreach list [composite::configure $viewer] {
                if {[string equal [lindex $list 0] -interval]} {                                  ;# viewer supports interval option
                    composite::configure $viewer -interval $global::pollTime                              ;# so use current interval
                    break                                                                                                    ;# done
                }
            }
            manageViewer $viewer 1 -static $global::static -setx $x -sety $y -setwidth $width -setheight $height -level $level
        }
        set viewerCells($viewer) $cells                                                                              ;# gather cells
    }
    # monitor cells now that all viewers exist (for example, summary tables have their own data and thus need be created before
    # other viewers can reference them)
    foreach {viewer cells} [array get viewerCells] {
        viewer::view $viewer $cells
    }
}

if {[info exists initializer]} {                  ;# stored configuration, now that modules data is initialized, create some viewers
    createSavedViewers $initializer
}

# must be invoked only when the application is running, that is after all the save file and command line modules have been loaded
proc dynamicallyLoadModules {arguments} {     ;# arguments list format is: module [-option [value] -option ...] module [-option ...]
    set instances $modules::(instances)
    modules::parse $arguments
    modules::initialize                                               ;# initializes only modules that have not yet been initialized
    modules::setPollTimes                                            ;# recalculate valid poll times but do not change current value
    foreach instance $modules::(instances) {
        if {[lsearch -exact $instances $instance]>=0} continue
        displayModule $instance $::draggable                                                               ;# new module or instance
    }
    updateTitle
    destroy $global::menu                                                                                            ;# update menus
    createMenuWidget . $::readOnly [llength $global::pollTimes]
    refresh                                                                               ;# make sure data is immediately displayed
}

proc dynamicallyUnloadModule {namespace} {
    foreach instance $modules::(instances) {
        if {[string equal $modules::instance::($instance,namespace) $namespace]} break                                      ;# found
    }
    if {[lindex $modules::instance::($instance,times) 0]>0} {                                       ;# then if module is synchronous
        ldelete modules::(synchronous) $instance                                                              ;# remove it from list
    }
    foreach table $dataTable::(list) {                                                                      ;# delete related tables
        if {[string equal [modules::namespaceFromArray [composite::cget $table -data]] $namespace]} {
            canvasWindowManager::unmanage $global::windowManager $widget::($table,path)
            delete $table
        }
    }
    modules::instance::empty $instance                             ;# empty module data so that related viewers can show empty cells
    modules::unload $instance
    modules::setPollTimes                                            ;# recalculate valid poll times but do not change current value
    updateTitle
    destroy $global::menu                                                                                            ;# update menus
    createMenuWidget . $::readOnly [llength $global::pollTimes]
}

set modules::(synchronous) {}                                                                    ;# save list of synchronous modules
set x 0.0
set y 0.0
proc displayModule {instance draggable} {
    if {[lindex $modules::instance::($instance,times) 0]>0} {                                            ;# if module is synchronous
### should be done in modules code ###
        lappend modules::(synchronous) $instance
    }
    if {[info exists modules::instance::($instance,views)]} {                             ;# check whether several views are defined
        set viewMembers $modules::instance::($instance,views)                             ;# create and manage a table for each view
    } else {
        set viewMembers {{}}                                                           ;# there is a single table (the default view)
    }
    set index 0
    set namespace $modules::instance::($instance,namespace)
    foreach members $viewMembers {
        set initialize [expr {[info exists ::initializer]&&([lsearch -exact $modules::(initialized) $namespace]>=0)}]
        if {$initialize} {
            set arguments [record::tableOptions $::initializer $namespace $index]                ;# extra stored arguments for table
        } else {
            set arguments {}
        }
        if {[llength $members]>0} {                                                                                  ;# it is a view
            array set ::view $members
            set table [eval new dataTable $global::canvas -data ${namespace}::data -view ::view -draggable $draggable $arguments]
            unset ::view
        } else {                                                                                     ;# use single default data view
            set table [eval new dataTable $global::canvas -data ${namespace}::data -draggable $draggable $arguments]
        }
        if {[info exists modules::instance::($instance,identifier)]} {                                 ;# set a title for data table
            set title $modules::instance::($instance,identifier)                                    ;# favor identifier if it exists
        } else {
            set title $namespace                                                                      ;# else simply use module name
        }
        if {$initialize} {                                                                       ;# setup initialized modules tables
            # use stored window manager initialization data for table
            foreach {x y width height level} [record::tableWindowManagerData $::initializer $namespace $index] {}
            manageViewer $table 0 -static $global::static -setx $x -sety $y -setwidth $width -setheight $height -level $level\
                -title $title
        } else {
            manageViewer $table 0 -static $global::static -setx $::x -sety $::y -title $title
            set ::x [expr {$::x+$global::xWindowManagerInitialOffset}]                    ;# offset tables to achieve a nicer layout
            set ::y [expr {$::y+$global::yWindowManagerInitialOffset}]
        }
        incr index                                                                                      ;# next view for initializer
    }
}

# display all modules using their namespace name (which may be indexed or set in the module code itself)
foreach instance $modules::(instances) {
    displayModule $instance $draggable
}

refresh                                                                                                ;# initialize refresh process
update                                                   ;# required so that table and viewer windows sizes are correct for snapshot
record::snapshot                                  ;# take a snap shot of initial configuration so any future changes can be detected
