#
# Copyright (c) 1993 Eric Schenk.
# All rights reserved.
#
# Permission is hereby granted, without written agreement and without
# license or royalty fees, to use, copy, modify, and distribute this
# software and its documentation for any purpose, provided that the
# above copyright notice and the following two paragraphs appear in
# all copies of this software.
# 
# IN NO EVENT SHALL ERIC SCHENK BE LIABLE TO ANY PARTY FOR
# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
# OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF ERIC
# SCHENK HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ERIC SCHENK SPECIFICALLY DISCLAIMS ANY WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
# ON AN "AS IS" BASIS, AND ERIC SCHENK HAS NO OBLIGATION TO
# PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

# routines to put frames up on screens and deal with geometry
# and property changes

proc compute_size_and_position {startup win} {
    if {!$startup && ![$win.plug info USPosition] && ![$win.plug info transient_for]} {
	# user positioned windows always end up in the global room.
	register room global $win
	# Compute the users requested position.
	# WARNING: The window can disappear during this procedure.
	set pos [user_pos $win]
	# Check if the window is still around and display it the requested position
        if [winfo exists $win] {wm geometry $win $pos}
    }
}

proc user_pos {win} {
    global tkwm_priv

    # initially we have decided nothing.
    set tkwm_priv(done) ""
    set tkwm_priv(position) ""

    scan [tkwm pointer] "%d %d" x y
    set tkwm_priv(width) [winfo width $win]
    set tkwm_priv(height) [winfo height $win]
    label .notifier -text "+$x+$y" -width 12 -height 1 -rel raised -border 2

    # initially place off viewable screen, so we can grab without displaying
    place .notifier -x 0 -y -100
    update idletasks
    tkwait visibility .notifier

    # Grab the pointer, but not the keyboard so keyboard events don't
    # get "lost".
    grabpointer -cursor top_left_corner
    set tkwm_priv(cursor) [lindex [. config -cursor] 4]
#    . config -cursor top_left_corner

    # This grab redirects events to the .notifier, and at the same
    # time waits until any existing global grabs go away. (The later
    # relies on a modified semantics of "grab" as implemented in
    # functions.tcl.) The later is important so that we don't interupt
    # some global dialog, such as a menu selection, with a request to
    # position the window.
    grab set .notifier

    # grab the server so updates of clients don't mess up the outline tracing
    # The server grab MUST be done after other types of grabs, otherwise
    # looping for other grabs can lock up forever.
    tkwm grabserver

    # Push on the root colormap so we can see the outline
    install_root_colormap

    # protect the code so that if it fails we still finish the ungrabserver.
    unwindProtect {
	# move the notifier onto the screen.
	place .notifier -x 0 -y 0
	tkwm outline $x $y $tkwm_priv(width) $tkwm_priv(height)
	focus .notifier
	
	bind .notifier <ButtonPress-1> {
	    global tkwm_priv
	    set tkwm_priv(position) "+%X+%Y"
	    tkwm outline %X %Y $tkwm_priv(width) $tkwm_priv(height)
	    # bind inside here just in case a button release preceeds the
	    # button press.
	    bind .notifier <ButtonRelease-1> {
		unwindProtect {
		    global tkwm_priv

		    # these two lines are needed to avoid a bug in TK (fixed in 3.4 & 3.5?)
		    grab release .notifier
		    # WARNING: the target window can cease to exists during this update.
		    update
		    destroy .notifier
		    tkwm outline 0 0 0 0
		    # actually clear the outline from the screen so we don't get "droppings"
		    # and make sure the notifier box goes away right away.
		    update idletasks
		    update
		} {
		    # make sure the tkwait can finish!
		    set tkwm_priv(done) 1
		}
	    }
	}
 	
	bind .notifier <Any-Motion> {
	    global tkwm_priv
	    if {$tkwm_priv(position)==""} {
	        .notifier config -text "+%X+%Y"
	        tkwm outline %X %Y $tkwm_priv(width) $tkwm_priv(height)
	    }
	}
        # The target window can cease to exists during this wait.
        tkwait variable tkwm_priv(done)
    } {
	catch {tkwm ungrabserver}
        # give back the root cursor
#        catch {. config -cursor $tkwm_priv(cursor)}
	# Give back the colormap
        catch {uninstall_root_colormap}
	# release our grabs no matter what
        catch {ungrabpointer}
    }
    return $tkwm_priv(position)
}

proc create_new_window {win} {
    global window_list
    # this is the array that will hold this windows private information
    global .${win}

    set tkwin .$win
    toplevel $tkwin -class Client -border 0

    # don't display the window yet!
    wm withdraw $tkwin

    handleError {
        tkwm plug $tkwin.plug $win -frame $tkwin -setgrid 1 -background black
    } {
	# the window no longer exists
	destroy $tkwin
	return
    }

    # get the requested size and position from the plug now
    # so we don't loose it if an update gets run (for example in tkerror!)
    scan [$tkwin.plug geometry] "%dx%d%s" width height position
    # set the size of the window so that an update after this doesn't
    # force an unneeded reconfiguration of the child.
    wm geometry $tkwin ${width}x${height}

    lappend window_list $tkwin

    # build a dummy frame (never mapped) that we use to access application
    # specific resources
    handleError {
        set ${tkwin}(res_name) $tkwin.[$tkwin.plug info res_name]
        frame [set ${tkwin}(res_name)] -class [$tkwin.plug info res_class] -border 0
    } {
	# the resource name might be something that we can't use because
        # it has a . or something in it. If so just pick a dummy name.
        set ${tkwin}(res_name) $tkwin.tkwm
        frame [set ${tkwin}(res_name)] -class [$tkwin.plug info res_class] -border 0
    }

    # assign the geometry managers to this client
    set ${tkwin}(clientManager) \
	[get_resource_value $tkwin clientManager ClientManager]
    set ${tkwin}(iconManager) \
	[get_resource_value $tkwin iconManager IconManager]
    
    # determine which room this application starts in
    register transient .[$tkwin.plug info transient_for] $tkwin
    register group .[$tkwin.plug info window_group] $tkwin

    handleError {
	register room [choose_room $tkwin.plug] $tkwin
    } {
	register room global $tkwin
	tkerror "User choose room code failed"
    }

    # catch the destruction of the plug by the client and get
    # rid of related data structures.
    # Note that the destruction of a plug destroys the plugs "frame"
    # window (as set by "-frame"), so we don't need to do the destroy
    # ourselves here. Also, don't try to destroy a parent of a plug
    # frame within this binding, because that will cause the current
    # version (3.4) of TK to go into an infinite loop.
    bind $tkwin.plug <Destroy> "deregister $tkwin; \
	global window_list $tkwin; \
	set_remove window_list $tkwin; \
        removeFromManagers $tkwin; \
	unset $tkwin"

    # determine if this window should be titled
    if [$tkwin.plug info transient_for] {
	set parent .[$tkwin.plug info transient_for]
	global $parent
	set titled [get_resource_value $tkwin transientsTitled TransientsTitled]
    } else {
        set titled [get_resource_value $tkwin titled Titled]
    }

    # do the decorations
    handleError {
	set inside [tkwm_client_frame $tkwin.decoration \
			-title [$tkwin.plug info name] \
			-titlebar $titled]
    } {
	# The users decoration command screwed up.
	# Do a very quick and dirty packing.
	catch {destroy $tkwin.decoration}
        set inside [frame $tkwin.decoration \
			-relief ridge \
			-border 5 \
			-cursor top_left_arrow]
        tkerror "User decoration code failed"
    }
    pack $tkwin.decoration -anchor nw -expand 1 -fill both
    pack $tkwin.plug -in $inside -anchor nw -expand 1 -fill both
    raise $tkwin.plug

    # Set the requested size and position. The first update is needed so
    # that changes in the border size don't screw up placement of negative
    # geometries. The second update is needed because size changes are
    # held over until idle.
    update idletasks
    wm geometry $tkwin $position
    update idletasks
    $tkwin.plug sendconfig

    return $tkwin
}

proc map_request {tkwin win} {
    global tkwm_priv
    global .$win
    
    if {$tkwin == "startup"} {
	set startup "1"
	set tkwin "??"
    } else {
	set startup "0"
    }
    
    if {$tkwin == "??"} {
	# WARNING: The window may have ceased to exist after this call.
	set tkwin [create_new_window $win]
        # if the window was destroyed before we got here, just skip everything.
        if ![winfo exists $tkwin] {return}
	set ${tkwin}(state) "withdrawn"
    } else {
        catch {set tkwin [lindex [$tkwin config -frame] 4]}
	if ![info exists ${tkwin}(state)] {set ${tkwin}(state) "withdrawn"}
    }

    set initial_state [$tkwin.plug info initial_state]
    # get the old state if we're suppose to be using it.
    if [option get . useClientState UseClientState] {
        set newstate [lindex [$tkwin.plug config -state] 4]
	if {$newstate=="withdrawn"} {set newstate $initial_state}
    } else {
	set newstate $initial_state
    }

    if {[set ${tkwin}(state)] == "iconified"} {
	# transition from iconified to normal
	deiconify $tkwin
    } else {
	# transition from withdrawn
	if {$newstate == "normal"} {
            # transition from withdrawn to normal
	    # WARNING: the window can disappear during this next procedure.
	    compute_size_and_position $startup $tkwin
	    # if the window disappeared on us then skip out.
            if ![winfo exists $tkwin] {return}
	    # make sure geometry computations complete
 	    update idletasks
	    deiconify $tkwin
	} else {
            # transition from withdrawn to iconic
	    # windows that are iconic on startup better be sure to specify
	    # their location, or they end up at +0+0
	    wm geometry $tkwin [$tkwin.plug geometry]
	    # make sure geometry computations complete
 	    update idletasks
	    iconify $tkwin
	}
    }

    # bind the events for the frame. Doing this here as opposed to
    # inside the window creation procedure leads to better interactive
    # response.

    noisyCatch {frame_bindings $tkwin}

    # if the window has an icon window (as opposed to a pixmap icon),
    # then create the icon window now since we really don't
    # want the icon window to just float about on the screen
    # until the first time we iconify the client.
    if {[winfo exists $tkwin] && ![winfo exists $tkwin.icon]
    && [$tkwin.plug info icon_window] != "??"} {
        make_icon $tkwin.icon $tkwin
        noisyCatch {icon_bindings $tkwin $tkwin.icon}
    }

    # Make sure the client knows where it ended up
    $tkwin.plug sendconfig

    # Make the changes display right now
    update
}

tkwm bind <MapRequest> {dispatch {noisyCatch {map_request %W %e}}}

tkwm bind <Configure> {noisyCatch {configureClient %W %w %h %x %y %d %S}}

# This code is so complicated because it needs to deal with the
# gravity of the configured window correctly.
# I could have put this in the core, but that would have precluded
# this code eventually paying attention to maximum and minimum window
# sizes and such things.
proc configureClient {W w h X Y d S} {
    if {"$w" != "??" && "$h" != "??" && $w>="0" && $h>="0"} {set size "${w}x$h"}
    if {"$X" != "??" && "$Y" != "??"} {set x $X; set y $Y}
    set frame [lindex [$W config -frame] 4]
    # instead of doing an update this code should compute the
    # size from the plug information. This avoids resizing before
    # moving, and also resizing only to resize smaller/larger to
    # meet max/min thresholds.
    if [info exists size] {wm geometry $frame $size; update idletasks}
    set w [winfo width $frame.plug]
    set h [winfo height $frame.plug]
    if {[info exists x] && [info exists y]} {
	switch [$W info win_gravity] {
	    "1" {
	       set x "+$x"
	       set y "+$y"
	    }
	    "3" {
	       set x "-[expr [winfo screenwidth $frame]-($x+$w)]"
	       set y "+$y"
	    }
	    "7" {
	       set x "+$x"
	       set y "-[expr [winfo screenheight $frame]-($y+$h)]"
	    }
	    "9" {
	       set x "-[expr [winfo screenwidth $frame]-($x+$w)]"
	       set y "-[expr [winfo screenheight $frame]-($y+$h)]"
	    }
	}
	wm geometry $frame $x$y
	update idletasks
    }
    if {"$d" != "??" && "$S" != "??"} {
      tkwm restack $frame $d $S
      update idletasks
    }
    $W sendconfig
}

tkwm bind <Iconify> {iconify [lindex [%W config -frame] 4]}

#this should deal with changes in icon_name and name.
#I'm not sure if any other possible changes call for action on
#the window managers part, other than reading the changed hints,
#which is done at the C code level. I should examine each property
#change to decide what should happen.

tkwm bind <Property> {
    handleError {
	set frame [lindex [%W config -frame] 4]
    } {
	set frame ""
    }
    switch "%P" {
	WM_NAME {
	    catch {
		$frame.decoration config -title [%W info name]
	    }
	}
	WM_ICON_NAME {
	    catch {
		$frame.icon.decoration config -title [%W info icon_name]
	    }
	}
        WM_HINTS {
	    # watch for changes in the icon bitmap, position, etc..
	    catch {
		# change the icon bitmap if we can...
	    	$frame.icon.plug config -bitmap [%W info icon_pixmap]
	    }
	}
    }
}
