#
# $Id: object.tcl,v 1.7 1995/02/10 08:32:50 sls Exp $
#
# This software is copyright (C) 1994 by the Lawrence Berkeley Laboratory.
# 
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that: (1) source code distributions
# retain the above copyright notice and this paragraph in its entirety, (2)
# distributions including binary code include the above copyright notice and
# this paragraph in its entirety in the documentation or other materials
# provided with the distribution, and (3) all advertising materials mentioning
# features or use of this software display the following acknowledgement:
# ``This product includes software developed by the University of California,
# Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
# the University nor the names of its contributors may be used to endorse
# or promote products derived from this software without specific prior
# written permission.
# 
# THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
# WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

set object_priv(currentClass) {}
set object_priv(objectCounter) 0

proc object_class {name spec} {
    global object_priv
    set object_priv(currentClass) $name
    lappend object_priv(objects) $name
    upvar #0 ${name}_priv class
    set class(members) {}
    set class(params) {}
    eval $spec
    proc $name:config args "uplevel \[concat object_config \$args]"
    proc $name:configure args "uplevel \[concat object_config \$args]"
    proc $name:cget {self option} "uplevel \[list object_cget \$self \$option]"
}

proc method {name args body} {
    global object_priv
    set className $object_priv(currentClass)
    upvar #0 ${className}_priv class
    lappend class(methods) $name
    set methodArgs self
    append methodArgs " " $args
    proc $className:$name $methodArgs "upvar #0 \$self slot\n$body"
}

proc member {name {defaultValue {}}} {
    global object_priv
    set className $object_priv(currentClass)
    upvar #0 ${className}_priv class
    if ![info exists class(member_info/$name)] {
	lappend class(members) [list $name $defaultValue]
    }
    set class(member_info/$name) {}
}

proc param {name {defaultValue {}} {resourceClass {}} {configCode {}}} {
    global object_priv
    set className $object_priv(currentClass)
    upvar #0 ${className}_priv class
    if {$resourceClass == ""} {
	set resourceClass \
	    [string toupper [string index $name 0]][string range $name 1 end]
    }
    if ![info exists class(param_info/$name)] {
	lappend class(params) $name
    }
    set class(param_info/$name) [list $defaultValue $resourceClass]
    if {$configCode != {}} {
	proc $className:config:$name self $configCode
    }
}

proc object_include {super_class_name} {
    global object_priv
    set className $object_priv(currentClass)
    upvar #0 ${className}_priv class
    upvar #0 ${super_class_name}_priv super_class
    foreach p $super_class(params) {
	lappend class(params) $p
	set class(param_info/$p) $super_class(param_info/$p)
    }
    set class(members) [concat $super_class(members) $class(members)]
    foreach m $super_class(methods) {
	set formals {}
	set proc $super_class_name:$m
	foreach arg [info args $proc] {
	    if [info default $proc $arg def] {
		lappend formals [list $arg $def]
	    } else {
		lappend formals $arg
	    }
	}
	proc $className:$m $formals [info body $proc]
    }
}

proc object_new {className {name {}}} {
    if {$name == {}} {
	global object_priv
	set name O_[incr object_priv(objectCounter)]
    }
    upvar #0 $name object
    upvar #0 ${className}_priv class
    set object(__class) $className
    foreach var $class(params) {
	set info $class(param_info/$var)
	set resourceClass [lindex $info 1]
	if ![catch {set val [option get $name $var $resourceClass]}] {
	    if {$val == ""} {
		set val [lindex $info 0]
	    }
	} else {
	    set val [lindex $info 0]
	}
	set object($var) $val
    }
    foreach var $class(members) {
	set object([lindex $var 0]) [lindex $var 1]
    }
    proc $name {method args} [format {
	upvar #0 %s object
	uplevel [concat $object(__class):$method %s $args]
    } $name $name]
    return $name
}

proc object_define_creator {windowType name spec} {
    object_class $name $spec
    if {[info procs $name:create] == {}} {
	error "widget \"$name\" must define a create method"
    }
    if {[info procs $name:reconfig] == {}} {
	error "widget \"$name\" must define a reconfig method"
    }
    proc $name {window args} [format {
	%s $window -class %s
	rename $window object_window_of$window
	upvar #0 $window object
	set object(__window) $window
	object_new %s $window
	proc %s:frame {self args} \
	    "uplevel \[concat object_window_of$window \$args]"
	uplevel [concat $window config $args]
	$window create
	set object(__created) 1
	bind $window <Destroy> \
	    "if !\[string compare %%W $window\] { object_delete $window }"
	$window reconfig
	return $window
    } $windowType \
	  [string toupper [string index $name 0]][string range $name 1 end] \
	  $name $name]
}

proc widget {name spec} {
    object_define_creator frame $name $spec
}

proc dialog {name spec} {
    object_define_creator toplevel $name $spec
}

proc object_config {self args} {
    upvar #0 $self object
    set len [llength $args]
    if {$len == 0} {
	upvar #0 $object(__class)_priv class
	set result {}
	foreach param $class(params) {
	    set info $class(param_info/$param)
	    lappend result \
		[list -$param $param [lindex $info 1] [lindex $info 0] \
		 $object($param)]
	}
	if [info exists object(__window)] {
	    set result [concat $result [object_window_of$object(__window) config]]
	}
	return $result
    }
    if {$len == 1} {
	upvar #0 $object(__class)_priv class
	if {[string index $args 0] != "-"} {
	    error "param '$args' didn't start with dash"
	}
	set param [string range $args 1 end]
	if {[set ndx [lsearch -exact $class(params) $param]] == -1} {
	    if [info exists object(__window)] {
		return [object_window_of$object(__window) config -$param]
	    }
	    error "no param '$args'"
	}
	set info $class(param_info/$param)
	return [list -$param $param [lindex $info 1] [lindex $info 0] \
		$object($param)]
    }
    # accumulate commands and eval them later so that no changes will take
    # place if we find an error
    set cmds ""
    while {$args != ""} {
	set fieldId [lindex $args 0]
        if {[string index $fieldId 0] != "-"} {
            error "param '$fieldId' didn't start with dash"
        }
        set fieldId [string range $fieldId 1 end]
        if ![info exists object($fieldId)] {
	    if {[info exists object(__window)]} {
		if [catch [list object_window_of$object(__window) config -$fieldId]] {
		    error "tried to set param '$fieldId' which did not exist."
		} else {
		    lappend cmds \
			[list object_window_of$object(__window) config -$fieldId [lindex $args 1]]
		    set args [lrange $args 2 end]
		    continue
		}
	    }

        }
	if {[llength $args] == 1} {
	    return $object($fieldId)
	} else {
	    lappend cmds [list set object($fieldId) [lindex $args 1]]
	    if {[info procs $object(__class):config:$fieldId] != {}} {
		lappend cmds [list $self config:$fieldId]
	    }
	    set args [lrange $args 2 end]
	}
    }
    foreach cmd $cmds {
	eval $cmd
    }
    if {[info exists object(__created)] && [info procs $object(__class):reconfig] != {}} {
	$self reconfig
    }
}

proc object_cget {self var} {
    upvar #0 $self object
    return [lindex [object_config $self $var] 4]
}

proc object_delete self {
    upvar #0 $self object
    if {[info exists object(__class)] && [info commands $object(__class):destroy] != ""} {
	$object(__class):destroy $self
    }
    if [info exists object(__window)] {
	if [string length [info commands object_window_of$self]] {
	    catch {rename $self {}}
	    rename object_window_of$self $self
	}
	destroy $self
    }
    catch {unset object}
}

proc object_slotname slot {
    upvar self self
    return [set self]($slot)
}
