#! /bin/sh
# the next line restarts using wish \
exec wish8.4 "$0" ${1+"$@"}

#
# ----------------------------------------------------------------------
# Password Gorilla, a password database manager
# Copyright (c) 2005 Frank Pilhofer
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# ----------------------------------------------------------------------
#

set ::gorillaVersion {$Revision: 1.42 $}

#
# ----------------------------------------------------------------------
# Make sure that our prerequisite packages are available. Don't want
# that to fail with a cryptic error message.
# ----------------------------------------------------------------------
#

set ::gorillaDir [file dirname [info script]]

if {[catch {
    package require Tk
} oops]} {
    #
    # Someone's trying to run this application with pure Tcl and no Tk.
    #

    puts "This application requires Tk, which does not seem to be available:"
    puts $oops
    exit 1
}

if {[catch {
    package require Tcl 8.4
}]} {
    wm withdraw .
    tk_messageBox -type ok -icon error -default ok \
	    -title "Need more recent Tcl/Tk" \
	    -message "The Password Gorilla requires at least Tcl/Tk 8.4\
	    to run. This smells like Tcl/Tk [info patchlevel].\
	    Please upgrade."
    exit 1
}

#
# The isaac package should be in the current directory
#

foreach file {isaac.tcl} {
    if {[catch {
	source [file join $::gorillaDir $file]
    } oops]} {
	wm withdraw .
	tk_messageBox -type ok -icon error -default ok \
		-title "Need $file" \
		-message "The Password Gorilla requires the \"$file\"\
		package. This seems to be an installation problem, as\
		this file ought to be part of the Password Gorilla\
		distribution."
	exit 1
    }
}

#
# Look for Itcl, or, failing that, tcl++
#

if {[catch {
    package require Itcl
}]} {
    #
    # If we can't have Itcl, can we load tcl++?
    #

    foreach testtclppdir [glob -nocomplain [file join $::gorillaDir tcl++*]] {
	if {[file isdirectory $testtclppdir]} {
	    lappend auto_path $testtclppdir
	}
    }

    if {[catch {
	package require tcl++
    }]} {
	wm withdraw .
	tk_messageBox -type ok -icon error -default ok \
		-title "Need \[Incr Tcl\]" \
		-message "The Password Gorilla requires the \[incr Tcl\]\
		add-on to Tcl. Please install the \[incr Tcl\] package."
	exit 1
    }

    #
    # When using tcl++, fool the other packages (twofish, blowfish
    # and pwsafe) into thinking that Itcl is present. The original
    # tcl++ didn't want to be so bold.
    #

    namespace eval ::itcl {
	namespace import -force ::tcl++::class
	namespace import -force ::tcl++::delete
    }

    package provide Itcl 3.0
}

#
# There may be a copy of BWidget in our directory
#

foreach testbwdir [glob -nocomplain [file join $::gorillaDir BWidget*]] {
    if {[file isdirectory $testbwdir]} {
	lappend auto_path $testbwdir
    }
}

if {[catch {
    package require BWidget
}]} {
    wm withdraw .
    tk_messageBox -type ok -icon error -default ok \
	    -title "Need BWidget" \
	    -message "The Password Gorilla requires the \"BWidget\"\
	    add-on to Tcl/Tk. Please install the BWidget package."
    exit 1
}

#
# The pwsafe, blowfish, twofish and sha1 packages may be in subdirectories
#

foreach subdir {sha1 blowfish twofish pwsafe} {
    set testDir [file join $::gorillaDir $subdir]
    if {[file isdirectory $testDir]} {
	lappend auto_path $testDir
    }
}

if {[catch {
    package require pwsafe
} oops]} {
    wm withdraw .
    tk_messageBox -type ok -icon error -default ok \
	    -title "Need PWSafe" \
	    -message "The Password Gorilla requires the \"pwsafe\" package.\
	    This seems to be an installation problem, as the pwsafe package\
	    ought to be part of the Password Gorilla distribution."
    exit 1
}

#
# If installed, we can use the uuid package (part of Tcllib) to generate
# UUIDs for new logins, but we don't depend on it.
#

catch {package require uuid}

#
# ----------------------------------------------------------------------
# Prepare
# ----------------------------------------------------------------------
#

namespace eval gorilla {}

if {![info exists ::gorilla::init]} {
    wm withdraw .
    set ::gorilla::init 0
}

#
# ----------------------------------------------------------------------
# GUI and other Initialization
# ----------------------------------------------------------------------
#

proc gorilla::Init {} {
    set ::gorilla::status ""
    set ::gorilla::uniquenodeindex 0
    set ::gorilla::dirty 0
    set ::gorilla::overridePasswordPolicy 0
    set ::gorilla::isPRNGInitialized 0
    set ::gorilla::activeSelection 0
    catch {unset ::gorilla::dirName}
    catch {unset ::gorilla::fileName}
    catch {unset ::gorilla::db}
    catch {unset ::gorilla::statusClearId}
    catch {unset ::gorilla::clipboardClearId}
    catch {unset ::gorilla::idleTimeoutTimerId}

    if {[llength [trace info variable ::gorilla::status]] == 0} {
	trace add variable ::gorilla::status write ::gorilla::StatusModified
    }

    #
    # Some default preferences
    #

    set ::gorilla::preference(defaultVersion) 3
    set ::gorilla::preference(unicodeSupport) 1
    set ::gorilla::preference(lru) [list]
}

proc gorilla::InitGui {} {
    option add *Button*font {Helvetica 10 bold}
    option add *title*font {Helvetica 16 bold}
    option add *Menu*tearOff 0

    set menudesc {
	"&File" "" "" 0 {
	    {command "New ..." {all} "Create new password database" {} \
		    -command "gorilla::New"}
	    {command "&Open ..." {all} "Open password database" {Ctrl o} \
		    -command "gorilla::Open"}
	    {command "&Merge ..." {all open} "Merge a second database into this database" {} \
		    -command "gorilla::Merge"}
	    {command "&Save" {all save} "Save password database" {Ctrl s} \
		    -command "gorilla::Save"}
	    {command "Save &As ..." {all open} "Save password database" {} \
		    -command "gorilla::SaveAs"}
	    {separator}
	    {command "&Export ..." {all open} "Export database as text" {} \
		    -command "gorilla::Export"}
	    {separator}
	    {command "&Preferences ..." {all} "Configure preferences" {} \
		    -command "gorilla::Preferences"}
	    {separator}
	    {command "E&xit" {all} "Exit" {Ctrl x} \
		    -command "gorilla::Exit"}
	}
	"&Edit" "" "" 0 {
	    {command "Copy Username" {all login} \
		    "Copy username to the clipboard" {Ctrl u} \
		    -command "gorilla::CopyUsername"}
	    {command "Copy Password" {all login} \
		    "Copy password to the clipboard" {Ctrl p} \
		    -command "gorilla::CopyPassword"}
	    {command "Copy URL" {all login} \
		    "Copy URL to the clipboard" {Ctrl w} \
		    -command "gorilla::CopyURL"}
	    {separator}
	    {command "Clear Clipboard" {all} "Clear clipboard contents" {Ctrl c} \
		    -command "gorilla::ClearClipboard"}
	    {separator}
	    {command "&Find ..." {all open} \
		 "Find a login, password or text" {Ctrl f} \
		 -command "gorilla::Find"}
	    {command "Find next" {all open} \
		 "Repeat find" {Ctrl g} \
		 -command "gorilla::RunFind"}
	}
	"&Login" "" "" 0 {
	    {command "Add Login ..." {all open} "Add a new login" {Ctrl a} \
		    -command "gorilla::AddLogin"}
	    {command "Edit Login ..." {all login} "Edit this login" {Ctrl e} \
		    -command "gorilla::EditLogin"}
	    {command "Delete Login" {all login} "Delete this login" {} \
		    -command "gorilla::DeleteLogin"}
	    {separator}
	    {command "Add Group ..." {all open} "Add a new toplevel group" {} \
		    -command "gorilla::AddGroup"}
	    {command "Add Subgroup ..." {all group} "Add a new subgroup" {} \
		    -command "gorilla::AddSubgroup"}
	    {command "Rename Group ..." {all group} "Rename this group" {} \
		    -command "gorilla::RenameGroup"}
	    {command "Delete Group" {all group} "Delete this group and all its logins" {} \
		    -command "gorilla::DeleteGroup"}
	}
	"&Manage" "" "" 0 {
	    {command "Password Policy ..." {all open} \
		    "Set the password policy for the password generator" {} \
		    -command "gorilla::PasswordPolicy"}
	    {command "Database Preferences ..." {all open} \
		    "Configure database-specific preferences" {} \
		    -command "gorilla::DatabasePreferencesDialog"}
	    {separator}
	    {command "Change Master Password ..." {all open} \
		    "Change the database's master password" {} \
		    -command "gorilla::ChangePassword"}
	}
	"&Help" "" "" 0 {
	    {command "Help ..." {all} "Help on using Password Gorilla" {} \
		    -command "gorilla::Help"}
	    {command "License ..." {all} "Show Password Gorilla license information" {} \
		    -command "gorilla::License"}
	    {separator}
	    {command "&About ..." {all} "About Password Gorilla" {} \
		    -command "gorilla::About"}
	}
    }

    wm title . "Password Gorilla"

    if {[info exists ::gorilla::preference(geometry,.)]} {
	TryResizeFromPreference .
    } else {
	wm geometry . 640x480
    }

    set mainframe [MainFrame .mainframe \
	    -textvariable ::gorilla::status \
	    -menu $menudesc]
    set allframe [$mainframe getframe]
    set sw [ScrolledWindow $allframe.sw -relief sunken -borderwidth 2]
    set tree [Tree $sw.tree -relief flat -borderwidth 0 \
	    -selectcommand gorilla::TreeNodeSelectionChanged \
	    -dragenabled 1 -dragevent 1 -dropenabled 1 \
	    -dropovermode "n" -droptypes {TREE_NODE move} \
	    -dropovercmd gorilla::TreeDropOver \
	    -dropcmd gorilla::TreeDrop]
    $sw setwidget $tree
    pack $sw -side top -expand yes -fill both

    $tree bindText <Button-1> "gorilla::TreeNodeSelect"
    $tree bindImage <Button-1> "gorilla::TreeNodeSelect"
    $tree bindText <Double-Button-1> "gorilla::TreeNodeDouble"
    $tree bindImage <Double-Button-1> "gorilla::TreeNodeDouble"
    $tree bindText <Button-3> "gorilla::TreeNodePopup"
    $tree bindImage <Button-3> "gorilla::TreeNodePopup"

    #
    # On the Macintosh, make the context menu also pop up on
    # Control-Left Mousebutton, for those poor souls that only
    # have one button.
    #

    catch {
	if {[info exists ::tcl_platform(platform)] && \
		$::tcl_platform(platform) == "macintosh"} {
	    $tree bindText <Control-Button-1> "gorilla::TreeNodePopup"
	    $tree bindImage <Control-Button-1> "gorilla::TreeNodePopup"
	}
    }

    pack $mainframe -fill both -expand yes

    #
    # remember widgets
    #

    set ::gorilla::toplevel(.) "."
    set ::gorilla::widgets(main) $mainframe
    set ::gorilla::widgets(tree) $tree

    #
    # Initialize menu state
    #

    UpdateMenu
    $mainframe setmenustate group disabled
    $mainframe setmenustate login disabled

    #
    # bindings
    #

    catch {
	bind . <MouseWheel> "$tree yview scroll \[expr {-%D/120}\] units"
    }

    bind . <Control-L> "gorilla::Reload"
    bind . <Control-R> "gorilla::Refresh"
    bind . <Control-C> "gorilla::ToggleConsole"
    bind . <Control-q> "gorilla::Exit"

    #
    # Handler for the X Selection
    #

    selection handle . gorilla::XSelectionHandler

    #
    # Handler for the WM_DELETE_WINDOW event, which is sent when the
    # user asks the window manager to destroy the application
    #

    wm protocol . WM_DELETE_WINDOW gorilla::Exit
}

#
# Initialize the Pseudo Random Number Generator
#

proc gorilla::InitPRNG {{seed ""}} {
    #
    # Try to compose a not very predictable seed
    #

    append seed "20041201"
    append seed [clock seconds] [clock clicks] [pid]
    append seed [winfo id .] [winfo geometry .] [winfo pointerxy .]
    set hashseed [pwsafe::int::sha1isz $seed]

    #
    # Init PRNG
    #

    isaac::srand $hashseed
    set ::gorilla::isPRNGInitialized 1
}

#
# Update Menu items
#

proc gorilla::UpdateMenu {} {
    set selection [$::gorilla::widgets(tree) selection get]

    if {[llength $selection] == 0} {
	$::gorilla::widgets(main) setmenustate group disabled
	$::gorilla::widgets(main) setmenustate login disabled
    } else {
	set node [lindex $selection 0]
	set data [$::gorilla::widgets(tree) itemcget $node -data]
	set type [lindex $data 0]

	if {$type == "Group" || $type == "Root"} {
	    $::gorilla::widgets(main) setmenustate group enabled
	    $::gorilla::widgets(main) setmenustate login disabled
	} else {
	    $::gorilla::widgets(main) setmenustate group disabled
	    $::gorilla::widgets(main) setmenustate login enabled
	}
    }

    if {[info exists ::gorilla::fileName] && \
	    [info exists ::gorilla::db] && $::gorilla::dirty} {
	$::gorilla::widgets(main) setmenustate save enabled
    } else {
	$::gorilla::widgets(main) setmenustate save disabled
    }

    if {[info exists ::gorilla::db]} {
	$::gorilla::widgets(main) setmenustate open enabled
    } else {
	$::gorilla::widgets(main) setmenustate open disabled
    }
}

#
# Attempt to resize a toplevel window based on our preference
#

proc gorilla::TryResizeFromPreference {top} {
    if {![info exists ::gorilla::preference(rememberGeometries)] || \
	    !$::gorilla::preference(rememberGeometries)} {
	return
    }
    if {![info exists ::gorilla::preference(geometry,$top)]} {
	return
    }
    if {[scan $::gorilla::preference(geometry,$top) "%dx%d" width height] != 2} {
	unset ::gorilla::preference(geometry,$top)
	return
    }
    if {$width < 10 || $width > [winfo screenwidth .] || \
	    $height < 10 || $height > [winfo screenheight .]} {
	unset ::gorilla::preference(geometry,$top)
	return
    }
    wm geometry $top ${width}x${height}
}

#
# This callback traces writes to the ::gorilla::status variable, which
# is shown in the UI's status line. We arrange for the variable to be
# cleared after some time, so that potentially sensible information
# like "password copied to clipboard" does not show forever.
#

proc gorilla::StatusModified {name1 name2 op} {
    if {![string equal $::gorilla::status ""] && \
	    ![string equal $::gorilla::status "Ready."] && \
	    ![string equal $::gorilla::status "Welcome to the Password Gorilla."]} {
	if {[info exists ::gorilla::statusClearId]} {
	    after cancel $::gorilla::statusClearId
	}
	set ::gorilla::statusClearId [after 5000 ::gorilla::ClearStatus]
    } else {
	if {[info exists ::gorilla::statusClearId]} {
	    after cancel $::gorilla::statusClearId
	}
    }	
}

proc gorilla::ClearStatus {} {
    catch {unset ::gorilla::statusClearId}
    set ::gorilla::status ""
}

#
# ----------------------------------------------------------------------
# Init GUI right away, so that the user has something to look at while
# the rest of the app is loading
# ----------------------------------------------------------------------
#
#
#if {!$::gorilla::init} {
#    set ::gorilla::status "Loading ..."
#    gorilla::InitGui
#    update
#}
#
#
# ----------------------------------------------------------------------
# Tree Management: Select a node
# ----------------------------------------------------------------------
#

proc gorilla::TreeNodeSelect {node} {
    ArrangeIdleTimeout
    set selection [$::gorilla::widgets(tree) selection get]

    if {[llength $selection] > 0} {
	set currentselnode [lindex $selection 0]

	if {$node == $currentselnode} {
	    return
	}
    }

    focus $::gorilla::widgets(tree)
    $::gorilla::widgets(tree) selection set $node
    $::gorilla::widgets(tree) see $node
    set ::gorilla::activeSelection 0
}

proc gorilla::TreeNodeSelectionChanged {widget nodes} {
    UpdateMenu
    ArrangeIdleTimeout
}

#
# ----------------------------------------------------------------------
# Tree Management: Double click
# ----------------------------------------------------------------------
#
# Double click on a group toggles its openness
# Double click on a login copies the password to the clipboard
#

proc gorilla::TreeNodeDouble {node} {
    ArrangeIdleTimeout
    focus $::gorilla::widgets(tree)
    $::gorilla::widgets(tree) see $node

    set data [$::gorilla::widgets(tree) itemcget $node -data]
    set type [lindex $data 0]

    if {$type == "Group" || $type == "Root"} {
	set open [$::gorilla::widgets(tree) itemcget $node -open]
	if {$open} {
	    $::gorilla::widgets(tree) itemconfigure $node -open 0
	} else {
	    $::gorilla::widgets(tree) itemconfigure $node -open 1
	}
    } else {
	if {[info exists ::gorilla::preference(doubleClickAction)]} {
	    switch -- $::gorilla::preference(doubleClickAction) {
		copyPassword {
		    gorilla::CopyPassword
		}
		editLogin {
		    gorilla::EditLogin
		}
		default {
		    # do nothing
		}
	    }
	}
    }
}

#
# ----------------------------------------------------------------------
# Tree Management: Popup
# ----------------------------------------------------------------------
#

proc gorilla::TreeNodePopup {node} {
    ArrangeIdleTimeout
    TreeNodeSelect $node

    set xpos [expr [winfo pointerx .] + 5]
    set ypos [expr [winfo pointery .] + 5]

    set data [$::gorilla::widgets(tree) itemcget $node -data]
    set type [lindex $data 0]

    switch -- $type {
	Root -
	Group {
	    GroupPopup $node $xpos $ypos
	}
	Login {
	    LoginPopup $node $xpos $ypos
	}
    }
}

#
# ----------------------------------------------------------------------
# Tree Management: Popup for a Group
# ----------------------------------------------------------------------
#

proc gorilla::GroupPopup {node xpos ypos} {
    if {![info exists ::gorilla::widgets(popup,Group)]} {
	set ::gorilla::widgets(popup,Group) [menu .popupForGroup]
	$::gorilla::widgets(popup,Group) add command \
		-label "Add Login" \
		-command "gorilla::PopupAddLogin"
	$::gorilla::widgets(popup,Group) add command \
		-label "Add Subgroup" \
		-command "gorilla::PopupAddSubgroup"
	$::gorilla::widgets(popup,Group) add command \
		-label "Rename Group" \
		-command "gorilla::PopupRenameGroup"
	$::gorilla::widgets(popup,Group) add separator
	$::gorilla::widgets(popup,Group) add command \
		-label "Delete Group" \
		-command "gorilla::PopupDeleteGroup"
    }

    set data [$::gorilla::widgets(tree) itemcget $node -data]
    set type [lindex $data 0]

    if {$type == "Root"} {
	$::gorilla::widgets(popup,Group) entryconfigure 2 -state disabled
	$::gorilla::widgets(popup,Group) entryconfigure 4 -state disabled
    } else {
	$::gorilla::widgets(popup,Group) entryconfigure 2 -state normal
	$::gorilla::widgets(popup,Group) entryconfigure 4 -state normal
    }

    tk_popup $::gorilla::widgets(popup,Group) $xpos $ypos
}

proc gorilla::PopupAddLogin {} {
    set node [lindex [$::gorilla::widgets(tree) selection get] 0]
    set data [$::gorilla::widgets(tree) itemcget $node -data]
    set type [lindex $data 0]

    if {$type == "Group"} {
	gorilla::AddLoginToGroup [lindex $data 1]
    } elseif {$type == "Root"} {
	gorilla::AddLoginToGroup ""
    }
}

proc gorilla::PopupAddSubgroup {} {
    gorilla::AddSubgroup
}

proc gorilla::PopupDeleteGroup {} {
    gorilla::DeleteGroup
}

proc gorilla::PopupRenameGroup {} {
    gorilla::RenameGroup
}

#
# ----------------------------------------------------------------------
# Tree Management: Popup for a Login
# ----------------------------------------------------------------------
#

proc gorilla::LoginPopup {node xpos ypos} {
    if {![info exists ::gorilla::widgets(popup,Login)]} {
	set ::gorilla::widgets(popup,Login) [menu .popupForLogin]
	$::gorilla::widgets(popup,Login) add command \
		-label "Copy Username to Clipboard" \
		-command "gorilla::PopupCopyUsername"
	$::gorilla::widgets(popup,Login) add command \
		-label "Copy Password to Clipboard" \
		-command "gorilla::PopupCopyPassword"
	$::gorilla::widgets(popup,Login) add command \
		-label "Copy URL to Clipboard" \
		-command "gorilla::PopupCopyURL"
	$::gorilla::widgets(popup,Login) add separator
	$::gorilla::widgets(popup,Login) add command \
		-label "Edit Login" \
		-command "gorilla::PopupEditLogin"
	$::gorilla::widgets(popup,Login) add separator
	$::gorilla::widgets(popup,Login) add command \
		-label "Delete Login" \
		-command "gorilla::PopupDeleteLogin"
    }

    tk_popup $::gorilla::widgets(popup,Login) $xpos $ypos
}

proc gorilla::PopupEditLogin {} {
    gorilla::EditLogin
}

proc gorilla::PopupCopyUsername {} {
    gorilla::CopyUsername
}

proc gorilla::PopupCopyPassword {} {
    gorilla::CopyPassword
}

proc gorilla::PopupCopyURL {} {
    gorilla::CopyURL
}

proc gorilla::PopupDeleteLogin {} {
    DeleteLogin
}

#
# ----------------------------------------------------------------------
# Tree Management: Drag and Drop
# ----------------------------------------------------------------------
#

proc gorilla::TreeDropOver {tree srcwidget where op type draggednode} {
    set dropovernode [lindex $where 1]
    set dont [lindex [$tree itemcget $dropovernode -data] 0]
    if {$dropovernode == "" || \
	    $dropovernode == $draggednode || \
	    ($dont != "Group" && $dont != "Root")} {
	return [list 2 node]
    }
    return [list 3 node]
}

proc gorilla::TreeDrop {tree srcwidget where op type draggednode} {
    set dropovernode [lindex $where 1]
    set dont [lindex [$tree itemcget $dropovernode -data] 0]
    if {$dropovernode == "" || \
	    $dropovernode == $draggednode || \
	    ($dont != "Group" && $dont != "Root")} {
	return
    }
    gorilla::MoveTreeNode $draggednode $dropovernode
    ArrangeIdleTimeout
}

#
# ----------------------------------------------------------------------
# New
# ----------------------------------------------------------------------
#

proc gorilla::CollectTicks {} {
    lappend ::gorilla::collectedTicks [clock clicks]
}

proc gorilla::New {} {
    ArrangeIdleTimeout

    #
    # If the current database was modified, give user a chance to think
    #

    if {$::gorilla::dirty} {
	set answer [tk_messageBox -parent . \
		-type yesnocancel -icon warning -default yes \
		-title "Save changes?" \
		-message "The current password database is modified.\
		Do you want to save the current database before creating\
		the new database?"]
	if {$answer == "yes"} {
	    if {[info exists ::gorilla::fileName]} {
		if {![::gorilla::Save]} {
		    return
		}
	    } else {
		if {![::gorilla::SaveAs]} {
		    return
		}
	    }
	} elseif {$answer != "no"} {
	    return
	}
    }

    #
    # Timing between clicks is used for our initial random seed
    #

    set ::gorilla::collectedTicks [list [clock clicks]]
    InitPRNG [join $::gorilla::collectedTicks -] ;# not a very good seed yet

    if {[catch {
	set password [GetPassword 1 "New Database: Choose Master Password"]
    }]} {
	# canceled
	return
    }

    lappend ::gorilla::collectedTicks [clock clicks]
    InitPRNG [join $::gorilla::collectedTicks -] ;# much better seed now

    set myOldCursor [. cget -cursor]
    . configure -cursor watch
    update idletasks

    wm title . "Password Gorilla - <New Database>"

    if {[info exists ::gorilla::db]} {
	itcl::delete object $::gorilla::db
    }

    set ::gorilla::dirty 0
    set ::gorilla::db [namespace current]::[pwsafe::db \#auto $password]
    pwsafe::int::randomizeVar password
    catch {unset ::gorilla::fileName}

    #
    # Apply defaults: auto-save, idle timeout, version, Unicode support
    #

    if {[info exists ::gorilla::preference(saveImmediatelyDefault)]} {
	$::gorilla::db setPreference SaveImmediately \
	        $::gorilla::preference(saveImmediatelyDefault)
    }

    if {[info exists ::gorilla::preference(idleTimeoutDefault)]} {
	if {$::gorilla::preference(idleTimeoutDefault) > 0} {
	    $::gorilla::db setPreference LockOnIdleTimeout 1
	    $::gorilla::db setPreference IdleTimeout \
		$::gorilla::preference(idleTimeoutDefault)
	} else {
	    $::gorilla::db setPreference LockOnIdleTimeout 0
	}
    }

    if {[info exists ::gorilla::preference(defaultVersion)]} {
	if {$::gorilla::preference(defaultVersion) == 3} {
	    $::gorilla::db setHeaderField 0 [list 3 0]
	}
    }

    if {[info exists ::gorilla::preference(unicodeSupport)]} {
	$::gorilla::db setPreference IsUTF8 \
		$::gorilla::preference(unicodeSupport)
    }

    $::gorilla::widgets(tree) selection clear
    catch {
	$::gorilla::widgets(tree) delete \
		[$::gorilla::widgets(tree) nodes root]
    }

    $::gorilla::widgets(tree) insert end root "RootNode" \
	    -open 1 -drawcross auto \
	    -image $::gorilla::images(group) \
	    -text "<New Database>" \
	    -data [list Root]

    set ::gorilla::status "Add logins using \"Add Login\" in the \"Login\" menu."
    . configure -cursor $myOldCursor

    if {[$::gorilla::db getPreference "SaveImmediately"]} {
	gorilla::SaveAs
    }

    UpdateMenu
}

#
# ----------------------------------------------------------------------
# Open a database file; used by "Open" and "Merge"
# ----------------------------------------------------------------------
#

proc gorilla::DestroyOpenDatabaseDialog {} {
    set ::gorilla::guimutex 2
}

proc gorilla::OpenPercentTrace {name1 name2 op} {
    if {![info exists ::gorilla::openPercentLastUpdate]} {
	set ::gorilla::openPercentLastUpdate [clock clicks -milliseconds]
	return
    }

    set now [clock clicks -milliseconds]
    set td [expr {$now - $::gorilla::openPercentLastUpdate}]

    if {$td < 200} {
	return
    }

    set ::gorilla::openPercentLastUpdate $now

    if {$::gorilla::openPercent > 0} {
	set info [format "Opening ... %2.0f %%" $::gorilla::openPercent]
	$::gorilla::openPercentWidget configure -text $info
	update idletasks
    }
}

proc gorilla::OpenDatabase {title {defaultFile ""}} {
    ArrangeIdleTimeout
    set top .openDialog

    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top
	TryResizeFromPreference $top

	label $top.splash -bg "#ffffff" \
		-image $::gorilla::images(splash)
	pack $top.splash -side left -fill both

	Separator $top.vsep -orient vertical
	pack $top.vsep -side left -fill y -padx 3

	set aframe [frame $top.right]
	label $aframe.title -anchor center
	pack $aframe.title -side top -fill x -pady 10

	set sep1 [Separator $aframe.sep1 -orient horizontal]
	pack $sep1 -side top -fill x -pady 10

	frame $aframe.file
	label $aframe.file.l -text "Database" -width 12
	ComboBox $aframe.file.cb -width 50
	button $aframe.file.sel -width 10 -text "Browse" \
		-command "set ::gorilla::guimutex 3"
	pack $aframe.file.l -side left -padx 5
	pack $aframe.file.cb -side left -padx 5 -fill x -expand yes
	pack $aframe.file.sel -side left -padx 5
	pack $aframe.file -side top -pady 5 -fill x -expand yes

	frame $aframe.pw
	label $aframe.pw.l -text "Password" -width 12
	entry $aframe.pw.pw -width 20 -show "*" -font {Courier}
	bind $aframe.pw.pw <KeyPress> "+::gorilla::CollectTicks"
	bind $aframe.pw.pw <KeyRelease> "+::gorilla::CollectTicks"
	label $aframe.pw.empty -width 10 -font {Helvetica 10 bold}
	pack $aframe.pw.l -side left -padx 5
	pack $aframe.pw.pw -side left -padx 5 -fill x -expand yes
	pack $aframe.pw.empty -side left -padx 5
	pack $aframe.pw -side top -pady 5 -fill x -expand yes

	set sep2 [Separator $aframe.sep2 -orient horizontal]
	pack $sep2 -side top -fill x -pady 10

	frame $aframe.buts
	set but1 [button $aframe.buts.b1 -width 15 -text "OK" \
		-command "set ::gorilla::guimutex 1"]
	set but2 [button $aframe.buts.b2 -width 15 -text "Cancel" \
		-command "set ::gorilla::guimutex 2"]
	pack $but1 $but2 -side left -pady 10 -padx 20
	pack $aframe.buts -side top

	label $aframe.info -relief sunken -anchor w
	pack $aframe.info -side top -fill x -expand yes

	bind $aframe.file.cb <Return> "set ::gorilla::guimutex 1"
	bind $aframe.pw.pw <Return> "set ::gorilla::guimutex 1"
	bind $aframe.buts.b1 <Return> "set ::gorilla::guimutex 1"
	bind $aframe.buts.b2 <Return> "set ::gorilla::guimutex 2"
	pack $aframe -side right -fill both -expand yes

	$aframe.file.cb configure -width 10
	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::DestroyOpenDatabaseDialog
    } else {
	set aframe $top.right
	wm deiconify $top
    }

    wm title $top $title
    $aframe.title configure -text $title
    $aframe.pw.pw delete 0 end

    if {[info exists ::gorilla::preference(lru)]} {
	$aframe.file.cb configure -values $::gorilla::preference(lru)
	$aframe.file.cb setvalue first
	set info "Select a database, and enter its password."
    } else {
	set info "To create a new database, click cancel, then \"New\" from the \"File\" menu."
    }

    $aframe.info configure -text $info

    if {$defaultFile != ""} {
	catch {
	    set ::gorilla::dirName [file dirname $defaultFile]
	}

	set values [$aframe.file.cb cget -values]
	set found [lsearch -exact $values $defaultFile]

	if {$found != -1} {
	    $aframe.file.cb setvalue @$found
	} else {
	    set values [linsert $values 0 $defaultFile]
	    $aframe.file.cb configure -values $values
	    $aframe.file.cb setvalue @0
	}
    }

    #
    # Run dialog
    #

    set oldGrab [grab current .]

    update idletasks
    raise $top
    focus $aframe.pw.pw
    grab $top

    #
    # Timing between clicks is used for our initial random seed
    #

    set ::gorilla::collectedTicks [list [clock clicks]]
    InitPRNG [join $::gorilla::collectedTicks -] ;# not a very good seed yet

    while {42} {
	ArrangeIdleTimeout
	set ::gorilla::guimutex 0
	vwait ::gorilla::guimutex

	lappend myClicks [clock clicks]

	if {$::gorilla::guimutex == 2} {
	    break
	} elseif {$::gorilla::guimutex == 1} {
	    set fileName [$aframe.file.cb cget -text]
	    set nativeName [file nativename $fileName]

	    if {$fileName == ""} {
		tk_messageBox -parent $top -type ok -icon error -default ok \
			-title "No File" \
			-message "Please select a password database."
		continue
	    }

	    if {![file readable $fileName]} {
		tk_messageBox -parent $top -type ok -icon error -default ok \
			-title "File Not Found" \
			-message "The password database\
			\"$nativeName\" does not exists or can not\
			be read."
		continue
	    }

	    $aframe.info configure -text "Verifying password ..."

	    set myOldCursor [$top cget -cursor]
	    set dotOldCursor [. cget -cursor]
	    $top configure -cursor watch
	    . configure -cursor watch
	    update idletasks

	    lappend ::gorilla::collectedTicks [clock clicks]
	    InitPRNG [join $::gorilla::collectedTicks -] ;# much better seed now

	    set password [$aframe.pw.pw get]

	    set ::gorilla::openPercent 0
	    set ::gorilla::openPercentWidget $aframe.info
	    trace add variable ::gorilla::openPercent [list "write"] \
		::gorilla::OpenPercentTrace

	    if {[catch {
		set newdb [pwsafe::createFromFile $fileName $password \
			       ::gorilla::openPercent]
	    } oops]} {
		pwsafe::int::randomizeVar password

		trace remove variable ::gorilla::openPercent [list "write"] \
		    ::gorilla::OpenPercentTrace
		unset ::gorilla::openPercent

		. configure -cursor $dotOldCursor
		$top configure -cursor $myOldCursor

		tk_messageBox -parent $top -type ok -icon error -default ok \
			-title "Error Opening Database" \
			-message "Can not open password database\
			\"$nativeName\": $oops"

		$aframe.info configure -text $info
		continue
	    }

	    trace remove variable ::gorilla::openPercent [list "write"] \
		::gorilla::OpenPercentTrace
	    unset ::gorilla::openPercent

	    . configure -cursor $dotOldCursor
	    $top configure -cursor $myOldCursor
	    pwsafe::int::randomizeVar password
	    break
	} elseif {$::gorilla::guimutex == 3} {
	    set types {
		{{Password Database Files} {.psafe3 .dat}}
		{{All Files} *}
	    }

	    if {![info exists ::gorilla::dirName]} {
		set ::gorilla::dirName [pwd]
	    }

	    set fileName [tk_getOpenFile -parent $top \
		    -title "Browse for a password database ..." \
		    -defaultextension ".psafe3" \
		    -filetypes $types \
		    -initialdir $::gorilla::dirName]
	    
	    if {$fileName == ""} {
		continue
	    }

	    set nativeName [file nativename $fileName]

	    catch {
		set ::gorilla::dirName [file dirname $fileName]
	    }

	    set values [$aframe.file.cb cget -values]
	    set found [lsearch -exact $values $nativeName]

	    if {$found != -1} {
		$aframe.file.cb setvalue @$found
	    } else {
		set values [linsert $values 0 $nativeName]
		$aframe.file.cb configure -values $values
		$aframe.file.cb setvalue @0
	    }

	    focus $aframe.pw.pw
	}
    }

    set fileName [$aframe.file.cb cget -text]
    set nativeName [file nativename $fileName]

    pwsafe::int::randomizeVar ::gorilla::collectedTicks
    $aframe.pw.pw configure -text ""

    if {$oldGrab != ""} {
	grab $oldGrab
    } else {
	grab release $top
    }

    wm withdraw $top

    if {$::gorilla::guimutex != 1} {
	return
    }

    #
    # Add file to LRU preference
    #

    if {[info exists ::gorilla::preference(lru)]} {
	set found [lsearch -exact $::gorilla::preference(lru) $nativeName]
	if {$found == -1} {
	    set ::gorilla::preference(lru) \
		    [linsert $::gorilla::preference(lru) 0 $nativeName]
	} elseif {$found != 0} {
	    set tmp [lreplace $::gorilla::preference(lru) $found $found]
	    set ::gorilla::preference(lru) [linsert $tmp 0 $nativeName]
	}
    } else {
	set ::gorilla::preference(lru) [list $nativeName]
    }

    #
    # Show any warnings?
    #

    set dbWarnings [$newdb cget -warningsDuringOpen]

    if {[llength $dbWarnings] > 0} {
	set message $fileName
	append message ": " [join $dbWarnings "\n"]
	tk_messageBox -parent . \
	    -type ok -icon warning -title "File Warning" \
	    -message $message
    }

    #
    # All seems well
    #

    ArrangeIdleTimeout
    return [list $fileName $newdb]
}

#
# ----------------------------------------------------------------------
# Open a file
# ----------------------------------------------------------------------
#

proc gorilla::Open {{defaultFile ""}} {
    #
    # If the current database was modified, give user a chance to think
    #

    if {$::gorilla::dirty} {
	set answer [tk_messageBox -parent . \
		-type yesnocancel -icon warning -default yes \
		-title "Save changes?" \
		-message "The current password database is modified.\
		Do you want to save the database?\n\
		\"Yes\" saves the database, and continues to the \"Open File\" dialog.\n\
		\"No\" discards all changes, and continues to the \"Open File\" dialog.\n\
		\"Cancel\" returns to the main menu."]
	if {$answer == "yes"} {
	    if {[info exists ::gorilla::fileName]} {
		if {![::gorilla::Save]} {
		    return
		}
	    } else {
		if {![::gorilla::SaveAs]} {
		    return
		}
	    }
	} elseif {$answer != "no"} {
	    return
	}
    }

    set openInfo [OpenDatabase "Open Password Database" $defaultFile]

    if {[llength $openInfo] == 0} {
	#
	# Canceled
	#
	return
    }

    set fileName [lindex $openInfo 0]
    set newdb [lindex $openInfo 1]
    set nativeName [file nativename $fileName]

    wm title . "Password Gorilla - $nativeName"

    if {[info exists ::gorilla::db]} {
	itcl::delete object $::gorilla::db
    }

    set ::gorilla::status "Password database $nativeName loaded."
    set ::gorilla::fileName $fileName
    set ::gorilla::db $newdb
    set ::gorilla::dirty 0

    $::gorilla::widgets(tree) selection clear
    $::gorilla::widgets(tree) delete [$::gorilla::widgets(tree) nodes root]
    catch {array unset ::gorilla::groupNodes}

    $::gorilla::widgets(tree) insert end root "RootNode" \
	    -open 1 -drawcross auto \
	    -image $::gorilla::images(group) \
	    -text $nativeName \
	    -data [list Root]

    AddAllRecordsToTree
    UpdateMenu
}

#
# ----------------------------------------------------------------------
# Merge file
# ----------------------------------------------------------------------
#

variable gorilla::fieldNames [list "" \
	"UUID" \
	"group name" \
	"title" \
	"user name" \
	"notes" \
	"password" \
	"creation time" \
	"password modification time" \
	"last access time" \
	"password lifetime" \
	"password policy" \
	"last modification time"]

proc gorilla::Merge {} {
    set openInfo [OpenDatabase "Merge Password Database"]

    if {[llength $openInfo] == 0} {
	#
	# Canceled
	#
	return
    }

    set ::gorilla::status "Merging "

    set fileName [lindex $openInfo 0]
    set newdb [lindex $openInfo 1]
    set nativeName [file nativename $fileName]

    set totalLogins 0
    set addedNodes [list]
    set conflictNodes [list]
    set identicalLogins 0

    set addedReport [list]
    set conflictReport [list]
    set identicalReport [list]
    set totalRecords [llength [$newdb getAllRecordNumbers]]

    foreach nrn [$newdb getAllRecordNumbers] {
	incr totalLogins

	set percent [expr {int(100.*$totalLogins/$totalRecords)}]
	set ::gorilla::status "Merging ($percent% done) ..."
	update idletasks

	set ngroup ""
	set ntitle ""
	set nuser ""

	if {[$newdb existsField $nrn 2]} {
	    set ngroup [$newdb getFieldValue $nrn 2]
	}

	if {[$newdb existsField $nrn 3]} {
	    set ntitle [$newdb getFieldValue $nrn 3]
	}

	if {[$newdb existsField $nrn 4]} {
	    set nuser [$newdb getFieldValue $nrn 4]
	}

	#
	# See if the current database has a login with the same,
	# group, title and user
	#

	set found 0

	if {[info exists ::gorilla::groupNodes($ngroup)]} {
	    set parent $::gorilla::groupNodes($ngroup)
	    foreach node [$::gorilla::widgets(tree) nodes $parent] {
		set data [$::gorilla::widgets(tree) itemcget $node -data]
		set type [lindex $data 0]

		if {$type != "Login"} {
		    continue
		}

		set rn [lindex $data 1]

		set title ""
		set user ""

		if {[$::gorilla::db existsField $rn 3]} {
		    set title [$::gorilla::db getFieldValue $rn 3]
		}

		if {[$::gorilla::db existsField $rn 4]} {
		    set user [$::gorilla::db getFieldValue $rn 4]
		}

		if {[string equal $ntitle $title] && \
			[string equal $nuser $user]} {
		    set found 1
		    break
		}
	    }
	}

	if {[info exists title]} {
	    pwsafe::int::randomizeVar title user
	}

	#
	# If a record with the same group, title and user was found,
	# see if the other fields are also the same.
	#

	if {$found} {
	    #
	    # See if they both define the same fields. If one defines
	    # a field that the other doesn't have, the logins can not
	    # be identical. This works both ways. However, ignore
	    # timestamps and the UUID, which may go AWOL between
	    # different Password Safe clones.
	    #

	    set nfields [$newdb getFieldsForRecord $nrn]
	    set fields [$::gorilla::db getFieldsForRecord $rn]
	    set identical 1

	    foreach nfield $nfields {
		if {$nfield == 1 || $nfield == 7 || $nfield == 8 || \
			$nfield == 9 || $nfield == 12} {
		    continue
		}
		if {[$newdb getFieldValue $nrn $nfield] == ""} {
		    continue
		}
		if {[lsearch -integer -exact $fields $nfield] == -1} {
		    set reason "existing login is missing "
		    if {$nfield > 0 && \
			    $nfield < [llength $::gorilla::fieldNames]} {
			append reason "the " \
				[lindex $::gorilla::fieldNames $nfield] \
				" field"
		    } else {
			append reason "field number $nfield"
		    }
		    set identical 0
		    break
		}
	    }

	    if {$identical} {
		foreach field $fields {
		    if {$field == 1 || $field == 7 || $field == 8 || \
			    $field == 9 || $field == 12} {
			continue
		    }
		    if {[$::gorilla::db getFieldValue $rn $field] == ""} {
			continue
		    }
		    if {[lsearch -integer -exact $nfields $field] == -1} {
			set reason "merged login is missing "
			if {$field > 0 && \
				$field < [llength $::gorilla::fieldNames]} {
			    append reason "the " \
				    [lindex $::gorilla::fieldNames $field] \
				    " field"
			} else {
			    append reason "field number $field"
			}
			set identical 0
			break
		    }
		}
	    }

	    #
	    # See if fields have the same content
	    #
	    
	    if {$identical} {
		foreach field $fields {
		    if {$field == 1 || $field == 7 || $field == 8 || \
			    $field == 9 || $field == 12} {
			continue
		    }
		    if {[$::gorilla::db getFieldValue $rn $field] == "" && \
			    [lsearch -integer -exact $nfields $field] == -1} {
			continue
		    }
		    if {![string equal [$newdb getFieldValue $nrn $field] \
			    [$::gorilla::db getFieldValue $rn $field]]} {
			set reason ""
			if {$field > 0 && \
				$field < [llength $::gorilla::fieldNames]} {
			    append reason \
				    [lindex $::gorilla::fieldNames $field] \
				    " differs"
			} else {
			    append reason "field number $field differs"
			}
			set identical 0
			break
		    }
		}
	    }
	}

	#
	# If the two records are not identical, then we have a conflict.
	# Add the new record, but with a modified title.
	#
	# If the record has a "Last Modified" field, append that
	# timestamp to the title.
	#
	# Else, append " - merged <timestamp>" to the new record.
	#

	if {$found && !$identical} {
	    set timestampFormat "%Y-%m-%d %H:%M:%S"

	    if {[$newdb existsField $nrn 3]} {
		set title [$newdb getFieldValue $nrn 3]
	    } else {
		set title "<No Title>"
	    }

	    if {[set index [string first " - modified " $title]] >= 0} {
		set title [string range $title 0 [expr {$index-1}]]
	    } elseif {[set index [string first " - merged " $title]] >= 0} {
		set title [string range $title 0 [expr {$index-1}]]
	    }

	    if {[$newdb existsField $nrn 12]} {
		append title " - modified " [clock format \
			[$newdb getFieldValue $nrn 12] \
			-format $timestampFormat]
	    } else {
		append title " - merged " [clock format \
			[clock seconds] \
			-format $timestampFormat]
	    }
	    $newdb setFieldValue $nrn 3 $title
	    pwsafe::int::randomizeVar title
	}

	#
	# Add the record to the database, if this is either a new login
	# that does not exist in this database, or if the login was found,
	# but not identical.
	#

	if {!$found || !$identical} {
	    set rn [$::gorilla::db createRecord]

	    foreach field [$newdb getFieldsForRecord $nrn] {
		$::gorilla::db setFieldValue $rn $field \
			[$newdb getFieldValue $nrn $field]
	    }

	    set node [AddRecordToTree $rn]

	    if {$found && !$identical} {
		#
		# Remember that there was a conflict
		#

		lappend conflictNodes $node

		set report "Conflict for login $ntitle"
		if {$ngroup != ""} {
		    append report " (in group $ngroup)"
		}
		append report ": " $reason "."
		lappend conflictReport $report

		#
		# Make sure that this node is visible
		#

		set parent [$::gorilla::widgets(tree) parent $node]

		while {$parent != "RootNode"} {
		    $::gorilla::widgets(tree) itemconfigure $parent -open 1
		    set parent [$::gorilla::widgets(tree) parent $parent]
		}

	    } else {
		lappend addedNodes $node
		set report "Added login $ntitle"
		if {$ngroup != ""} {
		    append report " (in Group $ngroup)"
		}
		append report "."
		lappend addedReport $report
	    }
	} else {
	    incr identicalLogins
	    set report "Identical login $ntitle"
	    if {$ngroup != ""} {
		append report " (in Group $ngroup)"
	    }
	    append report "."
	    lappend identicalReport $report
	}

	pwsafe::int::randomizeVar ngroup ntitle nuser
    }

    itcl::delete object $newdb
    MarkDatabaseAsDirty

    set numAddedLogins [llength $addedNodes]
    set numConflicts [llength $conflictNodes]

    set message "Merged "
    append message $nativeName "; " $totalLogins " "

    if {$totalLogins == 1} {
	append message "login, "
    } else {
	append message "logins, "
    }

    append message $identicalLogins " identical, "
    append message $numAddedLogins " added, "
    append message $numConflicts " "

    if {$numConflicts == 1} {
	append message "conflict."
    } else {
	append message "conflicts."
    }

    set ::gorilla::status $message

    if {$numConflicts > 0} {
	set default "yes"
	set icon "warning"
    } else {
	set default "no"
	set icon "info"
    }

    set answer [tk_messageBox -parent . -type yesno \
	    -icon $icon -default $default \
	    -title "Merge Results" \
	    -message "$message Do you want to view a\
	    detailed report?"]

    if {$answer != "yes"} {
	return
    }

    set ttop ".mergeReport"

    if {![winfo exists $ttop]} {
	toplevel $ttop
	wm title $ttop "Merge Report for $nativeName"

	set sw [ScrolledWindow $ttop.sw -auto none]
	set text [text $sw.text -relief sunken -width 80 -wrap none \
		-font {Courier}]
	$sw setwidget $text
	pack $sw -side top -fill both -expand yes

	set botframe [frame $ttop.botframe]
	set botbut [button $botframe.but -width 10 -text "Close" \
		-command "destroy $ttop"]
	pack $botbut
	pack $botframe -side top -fill x -pady 10
	
	bind $ttop <Prior> "$text yview scroll -1 pages; break"
	bind $ttop <Next> "$text yview scroll 1 pages; break"
	bind $ttop <Up> "$text yview scroll -1 units"
	bind $ttop <Down> "$text yview scroll 1 units"
	bind $ttop <Home> "$text yview moveto 0"
	bind $ttop <End> "$text yview moveto 1"
	bind $ttop <Return> "destroy $ttop"
    } else {
	wm deiconify $ttop
	set text "$ttop.sw.text"
	set botframe "$ttop.botframe"
    }

    $text configure -state normal
    $text delete 1.0 end

    $text insert end $message
    $text insert end "\n\n"

    $text insert end [string repeat "-" 70]
    $text insert end "\n"
    $text insert end "Conflicts\n"
    $text insert end [string repeat "-" 70]
    $text insert end "\n"
    $text insert end "\n"
    if {[llength $conflictReport] > 0} {
	foreach report $conflictReport {
	    $text insert end $report
	    $text insert end "\n"
	}
    } else {
	$text insert end "None.\n"
    }
    $text insert end "\n"

    $text insert end [string repeat "-" 70]
    $text insert end "\n"
    $text insert end "Added Logins\n"
    $text insert end [string repeat "-" 70]
    $text insert end "\n"
    $text insert end "\n"
    if {[llength $addedReport] > 0} {
	foreach report $addedReport {
	    $text insert end $report
	    $text insert end "\n"
	}
    } else {
	$text insert end "None.\n"
    }
    $text insert end "\n"

    $text insert end [string repeat "-" 70]
    $text insert end "\n"
    $text insert end "Identical Logins\n"
    $text insert end [string repeat "-" 70]
    $text insert end "\n"
    $text insert end "\n"
    if {[llength $identicalReport] > 0} {
	foreach report $identicalReport {
	    $text insert end $report
	    $text insert end "\n"
	}
    } else {
	$text insert end "None.\n"
    }
    $text insert end "\n"

    $text configure -state disabled

    update idletasks
    wm deiconify $ttop
    raise $ttop
    focus $botframe.but
}

#
# ----------------------------------------------------------------------
# Save file
# ----------------------------------------------------------------------
#

proc gorilla::SavePercentTrace {name1 name2 op} {
    if {![info exists ::gorilla::savePercentLastUpdate]} {
	set ::gorilla::savePercentLastUpdate [clock clicks -milliseconds]
	return
    }

    set now [clock clicks -milliseconds]
    set td [expr {$now - $::gorilla::savePercentLastUpdate}]

    if {$td < 200} {
	return
    }

    set ::gorilla::savePercentLastUpdate $now

    if {$::gorilla::savePercent > 0} {
	set ::gorilla::status [format "Saving ... %2.0f %%" $::gorilla::savePercent]
	update idletasks
    }
}

proc gorilla::Save {} {
    ArrangeIdleTimeout

    set myOldCursor [. cget -cursor]
    . configure -cursor watch
    update idletasks

    #
    # Create backup file, if desired
    #

    if {[info exists ::gorilla::preference(keepBackupFile)] && \
	    $::gorilla::preference(keepBackupFile)} {
	set backupFileName [file rootname $::gorilla::fileName]
	append backupFileName ".bak"
	if {[catch {
	    file copy -force -- $::gorilla::fileName $backupFileName
	} oops]} {
	    . configure -cursor $myOldCursor
	    set backupNativeName [file nativename $backupFileName]
	    tk_messageBox -parent . -type ok -icon error -default ok \
		    -title "Error Saving Database" \
		    -message "Failed to make backup copy of password \
		    database as $backupNativeName: $oops"
	    return 0
	}
    }

    set nativeName [file nativename $::gorilla::fileName]

    #
    # Determine file version. If there is a header field of type 0,
    # it should indicate the version. Otherwise, default to version 2.
    #

    set majorVersion 2

    if {[$::gorilla::db hasHeaderField 0]} {
	set version [$::gorilla::db getHeaderField 0]

	if {[lindex $version 0] == 3} {
	    set majorVersion 3
	}
    }

    set ::gorilla::savePercent 0
    trace add variable ::gorilla::savePercent [list "write"] \
	::gorilla::SavePercentTrace

    if {[catch {
	pwsafe::writeToFile $::gorilla::db $nativeName $majorVersion \
	    ::gorilla::savePercent
    } oops]} {
	trace remove variable ::gorilla::savePercent [list "write"] \
	    ::gorilla::SavePercentTrace
	unset ::gorilla::savePercent

	. configure -cursor $myOldCursor
	tk_messageBox -parent . -type ok -icon error -default ok \
		-title "Error Saving Database" \
		-message "Failed to save password database as\
		$nativeName: $oops"
	return 0
    }

    trace remove variable ::gorilla::savePercent [list "write"] \
	::gorilla::SavePercentTrace
    unset ::gorilla::savePercent

    . configure -cursor $myOldCursor
    set ::gorilla::status "Password database saved as $nativeName"
    set ::gorilla::dirty 0
    UpdateMenu
    return 1
}

#
# ----------------------------------------------------------------------
# Save As
# ----------------------------------------------------------------------
#

proc gorilla::SaveAs {} {
    ArrangeIdleTimeout

    if {![info exists ::gorilla::db]} {
	tk_messageBox -parent . -type ok -icon error -default ok \
		-title "Nothing To Save" \
		-message "No password database to save."
	return 1
    }

    #
    # Determine file version. If there is a header field of type 0,
    # it should indicate the version. Otherwise, default to version 2.
    #

    set majorVersion 2

    if {[$::gorilla::db hasHeaderField 0]} {
	set version [$::gorilla::db getHeaderField 0]

	if {[lindex $version 0] == 3} {
	    set majorVersion 3
	}
    }

    if {$majorVersion == 3} {
	set defaultExtension ".psafe3"
    } else {
	set defaultExtension ".dat"
    }

    #
    # Query user for file name
    #

    set types {
	{{Password Database Files} {.psafe3 .dat}}
	{{All Files} *}
    }

    if {![info exists ::gorilla::dirName]} {
	set ::gorilla::dirName [pwd]
    }

    set fileName [tk_getSaveFile -parent . \
	    -title "Save password database ..." \
	    -defaultextension $defaultExtension \
	    -filetypes $types \
	    -initialdir $::gorilla::dirName]

    if {$fileName == ""} {
	return 0
    }

    set nativeName [file nativename $fileName]

    set myOldCursor [. cget -cursor]
    . configure -cursor watch
    update idletasks

    #
    # Create backup file, if desired
    #

    if {[info exists ::gorilla::preference(keepBackupFile)] && \
	    $::gorilla::preference(keepBackupFile) && \
	    [file exists $fileName]} {
	set backupFileName [file rootname $fileName]
	append backupFileName ".bak"
	set ::gorilla::status $backupFileName
	if {[catch {
	    file copy -force -- $fileName $backupFileName
	} oops]} {
	    . configure -cursor $myOldCursor
	    set backupNativeName [file nativename $backupFileName]
	    tk_messageBox -parent . -type ok -icon error -default ok \
		    -title "Error Saving Database" \
		    -message "Failed to make backup copy of password \
		    database as $backupNativeName: $oops"
	    return 0
	}
    }

    set ::gorilla::savePercent 0
    trace add variable ::gorilla::savePercent [list "write"] \
	::gorilla::SavePercentTrace

    if {[catch {
	pwsafe::writeToFile $::gorilla::db $fileName $majorVersion \
	    ::gorilla::savePercent
    } oops]} {
	trace remove variable ::gorilla::savePercent [list "write"] \
	    ::gorilla::SavePercentTrace
	unset ::gorilla::savePercent

	. configure -cursor $myOldCursor
	tk_messageBox -parent . -type ok -icon error -default ok \
		-title "Error Saving Database" \
		-message "Failed to save password database as\
		$nativeName: $oops"
	return 0
    }

    trace remove variable ::gorilla::savePercent [list "write"] \
	::gorilla::SavePercentTrace
    unset ::gorilla::savePercent

    . configure -cursor $myOldCursor
    set ::gorilla::dirty 0
    set ::gorilla::fileName $fileName
    wm title . "Password Gorilla - $nativeName"
    $::gorilla::widgets(tree) itemconfigure "RootNode" \
	-text $nativeName
    set ::gorilla::status "Password database saved as $nativeName"

    #
    # Add file to LRU preference
    #

    if {[info exists ::gorilla::preference(lru)]} {
	set found [lsearch -exact $::gorilla::preference(lru) $nativeName]
	if {$found == -1} {
	    set ::gorilla::preference(lru) \
		    [linsert $::gorilla::preference(lru) 0 $nativeName]
	} elseif {$found != 0} {
	    set tmp [lreplace $::gorilla::preference(lru) $found $found]
	    set ::gorilla::preference(lru) [linsert $tmp 0 $nativeName]
	}
    } else {
	set ::gorilla::preference(lru) [list $nativeName]
    }

    UpdateMenu
    return 1
}

#
# ----------------------------------------------------------------------
# Export Database
# ----------------------------------------------------------------------
#

proc gorilla::DestroyExportDialog {} {
    set ::gorilla::guimutex 2
}

proc gorilla::Export {} {
    ArrangeIdleTimeout
    set top .export

    if {![info exists ::gorilla::preference(exportIncludePassword)]} {
	set ::gorilla::preference(exportIncludePassword) 0
    }

    if {![info exists ::gorilla::preference(exportIncludeNotes)]} {
	set ::gorilla::preference(exportIncludeNotes) 1
    }

    if {![info exists ::gorilla::preference(exportAsUnicode)]} {
	set ::gorilla::preference(exportAsUnicode) 0
    }

    if {![info exists ::gorilla::preference(exportFieldSeparator)]} {
	set ::gorilla::preference(exportFieldSeparator) ","
    }

    if {![info exists ::gorilla::preference(exportShowWarning)]} {
	set ::gorilla::preference(exportShowWarning) 1
    }

    if {$::gorilla::preference(exportShowWarning)} {
	set answer [tk_messageBox -parent . \
			-type yesno -icon warning -default no \
			-title "Export Security Warning" \
			-message "You are about to export the password\
			database to a plain-text file. The file will\
			not be encrypted or password-protected. Anybody\
			with access can read the file, and learn your\
			user names and passwords. Make sure to store the\
			file in a secure location. Do you want to\
			continue?"]
	if {$answer != "yes"} {
	    return
	}
    }

    if {![info exists ::gorilla::dirName]} {
	set ::gorilla::dirName [pwd]
    }

    set types {
	{{Text Files} {.txt}}
	{{CSV Files} {.csv}}
	{{All Files} *}
    }

    set fileName [tk_getSaveFile -parent . \
	    -title "Export password database as text ..." \
	    -defaultextension ".txt" \
	    -filetypes $types \
	    -initialdir $::gorilla::dirName]

    if {$fileName == ""} {
	return
    }

    set nativeName [file nativename $fileName]

    set myOldCursor [. cget -cursor]
    . configure -cursor watch
    update idletasks

    if {[catch {
	set txtFile [open $fileName "w"]
    } oops]} {
	. configure -cursor $myOldCursor
	tk_messageBox -parent . -type ok -icon error -default ok \
	    -title "Error Exporting Database" \
	    -message "Failed to export password database to\
		$nativeName: $oops"
	return
    }

    set ::gorilla::status "Exporting ..."
    update idletasks

    if {$::gorilla::preference(exportAsUnicode)} {
	#
	# Write BOM in binary mode, then switch to Unicode
	#

	fconfigure $txtFile -encoding binary

	if {[info exists ::tcl_platform(byteOrder)]} {
	    switch -- $::tcl_platform(byteOrder) {
		littleEndian {
		    puts -nonewline $txtFile "\xff\xfe"
		}
		bigEndian {
		    puts -nonewline $txtFile "\xfe\xff"
		}
	    }
	}

	fconfigure $txtFile -encoding unicode
    }

    set separator [subst -nocommands -novariables $::gorilla::preference(exportFieldSeparator)]

    foreach rn [$::gorilla::db getAllRecordNumbers] {
	# UUID
	if {[$::gorilla::db existsField $rn 1]} {
	    puts -nonewline $txtFile [$::gorilla::db getFieldValue $rn 1]
	}
	puts -nonewline $txtFile $separator
	# Group
	if {[$::gorilla::db existsField $rn 2]} {
	    puts -nonewline $txtFile [$::gorilla::db getFieldValue $rn 2]
	}
	puts -nonewline $txtFile $separator
	# Title
	if {[$::gorilla::db existsField $rn 3]} {
	    puts -nonewline $txtFile [$::gorilla::db getFieldValue $rn 3]
	}
	puts -nonewline $txtFile $separator
	# Username
	if {[$::gorilla::db existsField $rn 4]} {
	    puts -nonewline $txtFile [$::gorilla::db getFieldValue $rn 4]
	}
	puts -nonewline $txtFile $separator
	# Password
	if {$::gorilla::preference(exportIncludePassword)} {
	    if {[$::gorilla::db existsField $rn 6]} {
		puts -nonewline $txtFile [$::gorilla::db getFieldValue $rn 6]
	    }
	} else {
	    puts -nonewline $txtFile "********"
	}
	puts -nonewline $txtFile $separator
	if {$::gorilla::preference(exportIncludeNotes)} {
	    if {[$::gorilla::db existsField $rn 5]} {
		puts -nonewline $txtFile \
		    [string map {\\ \\\\ \" \\\" \t \\t \n \\n} \
			 [$::gorilla::db getFieldValue $rn 5]]
	    }
	}
	puts $txtFile ""
    }

    catch {close $txtFile}
    . configure -cursor $myOldCursor
    set ::gorilla::status "Database exported."
}

#
# ----------------------------------------------------------------------
# Mark database as dirty
# ----------------------------------------------------------------------
#

proc gorilla::MarkDatabaseAsDirty {} {
    set ::gorilla::dirty 1

    if {[info exists ::gorilla::db]} {
	if {[$::gorilla::db getPreference "SaveImmediately"]} {
	    if {[info exists ::gorilla::fileName]} {
		gorilla::Save
	    } else {
		gorilla::SaveAs
	    }
	}
    }

    UpdateMenu
}

#
# ----------------------------------------------------------------------
# Arrange for an Idle Timeout after a number of minutes
# ----------------------------------------------------------------------
#

proc gorilla::ArrangeIdleTimeout {} {
    if {[info exists ::gorilla::idleTimeoutTimerId]} {
	after cancel $::gorilla::idleTimeoutTimerId
    }

    if {[info exists ::gorilla::db]} {
	set minutes [$::gorilla::db getPreference "IdleTimeout"]

	if {![$::gorilla::db getPreference "LockOnIdleTimeout"] || \
		$minutes <= 0} {
	    catch {unset ::gorilla::idleTimeoutTimerId}
	    return
	}

	set seconds [expr {$minutes * 60}]
	set mseconds [expr {$seconds * 1000}]
	set ::gorilla::idleTimeoutTimerId [after $mseconds ::gorilla::IdleTimeout]
    }
}

#
# ----------------------------------------------------------------------
# Idle Timeout
# ----------------------------------------------------------------------
#

proc gorilla::IdleTimeout {} {
    LockDatabase
}

#
# ----------------------------------------------------------------------
# Lock Database
# ----------------------------------------------------------------------
#

proc gorilla::CloseLockedDatabaseDialog {} {
    set ::gorilla::lockedMutex 2
}

proc gorilla::LockDatabase {} {
    if {![info exists ::gorilla::db]} {
	return
    }

    if {[info exists ::gorilla::isLocked] && $::gorilla::isLocked} {
	return
    }

    if {[info exists ::gorilla::idleTimeoutTimerId]} {
	after cancel $::gorilla::idleTimeoutTimerId
    }

    ClearClipboard
    set ::gorilla::isLocked 1

    set oldGrab [grab current .]

    foreach tl [array names ::gorilla::toplevel] {
	set ws [wm state $tl]
	switch -- $ws {
	    normal -
	    iconic -
	    zoomed {
		set withdrawn($tl) $ws
		wm withdraw $tl
	    }
	}
    }

    $::gorilla::widgets(main) setmenustate all disabled

    set top .lockedDialog
    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top
	TryResizeFromPreference $top

	label $top.splash -bg "#ffffff" \
	    -image $::gorilla::images(splash)
	pack $top.splash -side left -fill both

	Separator $top.vsep -orient vertical
	pack $top.vsep -side left -fill y -padx 3

	set aframe [frame $top.right]
	label $aframe.title -anchor center
	pack $aframe.title -side top -fill x -pady 10

	set sep1 [Separator $aframe.sep1 -orient horizontal]
	pack $sep1 -side top -fill x -pady 10

	frame $aframe.file
	label $aframe.file.l -text "Database" -width 12
	entry $aframe.file.f -width 50 -state disabled
	pack $aframe.file.l -side left -padx 5
	pack $aframe.file.f -side left -padx 5 -fill x -expand yes
	pack $aframe.file -side top -pady 5 -fill x -expand yes

	frame $aframe.pw
	label $aframe.pw.l -text "Password" -width 12 
	entry $aframe.pw.pw -width 20 -show "*" -font {Courier}
	pack $aframe.pw.l -side left -padx 5
	pack $aframe.pw.pw -side left -padx 5 -fill x -expand yes
	pack $aframe.pw -side top -pady 5 -fill x -expand yes

	set sep2 [Separator $aframe.sep2 -orient horizontal]
	pack $sep2 -side top -fill x -pady 10

	frame $aframe.buts
	set but1 [button $aframe.buts.b1 -width 15 -text "OK" \
		-command "set ::gorilla::lockedMutex 1"]
	set but2 [button $aframe.buts.b2 -width 15 -text "Exit" \
		-command "set ::gorilla::lockedMutex 2"]
	pack $but1 $but2 -side left -pady 10 -padx 20
	pack $aframe.buts -side top

	label $aframe.info -relief sunken -anchor w
	pack $aframe.info -side top -fill x -expand yes

	bind $aframe.pw.pw <Return> "set ::gorilla::lockedMutex 1"
	bind $aframe.buts.b1 <Return> "set ::gorilla::lockedMutex 1"
	bind $aframe.buts.b2 <Return> "set ::gorilla::lockedMutex 2"
	pack $aframe -side right -fill both -expand yes

	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::CloseLockedDatabaseDialog
    } else {
	set aframe $top.right
	wm deiconify $top
    }

    wm title $top "Database Locked"
    $aframe.title configure -text "The Database Is Locked"
    $aframe.pw.pw delete 0 end
    $aframe.info configure -text "Enter the Master Password."

    if {[info exists ::gorilla::fileName]} {
	$aframe.file.f configure -state normal
	$aframe.file.f delete 0 end
	$aframe.file.f insert 0 [file nativename $::gorilla::fileName]
	$aframe.file.f configure -state disabled
    } else {
	$aframe.file.f configure -state normal
	$aframe.file.f delete 0 end
	$aframe.file.f insert 0 "<New Database>"
	$aframe.file.f configure -state disabled
    }

    #
    # Run dialog
    #

    focus $aframe.pw.pw
    grab $top

    while {42} {
	set ::gorilla::lockedMutex 0
	vwait ::gorilla::lockedMutex

	if {$::gorilla::lockedMutex == 1} {
	    if {[$::gorilla::db checkPassword [$aframe.pw.pw get]]} {
		break
	    }

	    tk_messageBox -parent $top \
		-type ok -icon error -default ok \
		-title "Wrong Password" \
		-message "That password is not correct."
	} elseif {$::gorilla::lockedMutex == 2} {
	    #
	    # This may return, if the database was modified, and the user
	    # answers "Cancel" to the question whether to save the database
	    # or not.
	    #

	    Exit
	}
    }

    foreach tl [array names withdrawn] {
	wm state $tl $withdrawn($tl)
    }

    if {$oldGrab != ""} {
	grab $oldGrab
    } else {
	grab release $top
    }

    $::gorilla::widgets(main) setmenustate all normal

    wm withdraw $top
    set ::gorilla::status "Welcome back."

    set ::gorilla::isLocked 0

    wm deiconify .
    raise .

    ArrangeIdleTimeout
}

#
# ----------------------------------------------------------------------
# Clear the clipboard after a configurable number of seconds
# ----------------------------------------------------------------------
#

proc gorilla::ArrangeToClearClipboard {} {
    if {[info exists ::gorilla::clipboardClearId]} {
	after cancel $::gorilla::clipboardClearId
    }

    if {![info exists ::gorilla::preference(clearClipboardAfter)] || \
	    $::gorilla::preference(clearClipboardAfter) == 0} {
	catch {unset ::gorilla::clipboardClearId}
	return
    }

    set seconds $::gorilla::preference(clearClipboardAfter)
    set mseconds [expr {$seconds * 1000}]
    set ::gorilla::clipboardClearId [after $mseconds ::gorilla::ClearClipboard]
}

#
# ----------------------------------------------------------------------
# Copy the Username to the Clipboard
# ----------------------------------------------------------------------
#

proc gorilla::GetSelectedRecord {} {
    if {[llength [set sel [$::gorilla::widgets(tree) selection get]]] == 0} {
	error "oops"
    }

    set node [lindex $sel 0]
    set data [$::gorilla::widgets(tree) itemcget $node -data]
    set type [lindex $data 0]

    if {$type != "Login"} {
	error "oops"
    }

    return [lindex $data 1]
}

proc gorilla::GetSelectedUsername {} {
    if {[catch {set rn [gorilla::GetSelectedRecord]}]} {
	return
    }

    if {![$::gorilla::db existsField $rn 6]} {
	return
    }

    return [$::gorilla::db getFieldValue $rn 4]
}

proc gorilla::CopyUsername {} {
    ArrangeIdleTimeout
    clipboard clear
    clipboard append -- [::gorilla::GetSelectedUsername]
    set ::gorilla::activeSelection 1
    selection clear
    selection own .
    ArrangeToClearClipboard
    set ::gorilla::status "Copied user name to clipboard."
}

#
# ----------------------------------------------------------------------
# Copy the Password to the Clipboard
# ----------------------------------------------------------------------
#

proc gorilla::GetSelectedPassword {} {
    if {[catch {set rn [gorilla::GetSelectedRecord]}]} {
	return
    }

    if {![$::gorilla::db existsField $rn 6]} {
	return
    }

    return [$::gorilla::db getFieldValue $rn 6]
}

proc gorilla::CopyPassword {} {
    ArrangeIdleTimeout
    clipboard clear
    clipboard append -- [::gorilla::GetSelectedPassword]
    set ::gorilla::activeSelection 2
    selection clear
    selection own .
    ArrangeToClearClipboard
    set ::gorilla::status "Copied password to clipboard."
}

#
# ----------------------------------------------------------------------
# Copy the URL to the Clipboard
# ----------------------------------------------------------------------
#

proc gorilla::GetSelectedURL {} {
    if {[catch {set rn [gorilla::GetSelectedRecord]}]} {
	return
    }

    #
    # Password Safe v3 has a dedicated URL field.
    #

    if {[$::gorilla::db existsField $rn 13]} {
	return [$::gorilla::db getFieldValue $rn 13]
    }

    #
    # Password Safe v2 kept the URL in the "Notes" field.
    #

    if {![$::gorilla::db existsField $rn 5]} {
	return
    }

    set notes [$::gorilla::db getFieldValue $rn 5]
    if {[set index [string first "url:" $notes]] != -1} {
	incr index 4
	while {$index < [string length $notes] && \
		[string is space [string index $notes $index]]} {
	    incr index
	}
	if {[string index $notes $index] == "\""} {
	    incr index
	    set URL ""
	    while {$index < [string length $notes]} {
		set c [string index $notes $index]
		if {$c == "\\"} {
		    append URL [string index $notes [incr index]]
		} elseif {$c == "\""} {
		    break
		} else {
		    append URL $c
		}
		incr index
	    }
	} else {
	    if {![regexp -start $index -- {\s*(\S+)} $notes dummy URL]} {
		set URL ""
	    }
	}
    } elseif {![regexp -nocase -- {http(s)?://\S*} $notes URL]} {
	set URL ""
    }

    return $URL
}

proc gorilla::CopyURL {} {
    ArrangeIdleTimeout
    clipboard clear
    set URL [gorilla::GetSelectedURL]

    if {$URL == ""} {
	set ::gorilla::status "Can not copy URL to clipboard: no URL defined."
    } else {
	clipboard append -- $URL
	set ::gorilla::activeSelection 3
	selection clear
	selection own .
	ArrangeToClearClipboard
	set ::gorilla::status "Copied URL to clipboard."
    }
}

#
# ----------------------------------------------------------------------
# X Selection Handler
# ----------------------------------------------------------------------
#

proc gorilla::XSelectionHandler {offset maxChars} {
    switch -- $::gorilla::activeSelection {
	0 {
	    set data ""
	}
	1 {
	    set data [gorilla::GetSelectedUsername]
	}
	2 {
	    set data [gorilla::GetSelectedPassword]
	}
	3 {
	    set data [gorilla::GetSelectedURL]
	}
	default {
	    set data ""
	}
    }

    return [string range $data $offset [expr {$offset+$maxChars-1}]]
}

#
# ----------------------------------------------------------------------
# Clear clipboard
# ----------------------------------------------------------------------
#

proc gorilla::ClearClipboard {} {
    clipboard clear
    clipboard append -- ""

    if {[selection own] == "."} {
	selection clear
    }

    set ::gorilla::activeSelection 0
    set ::gorilla::status "Clipboard cleared."
    catch {unset ::gorilla::clipboardClearId}
}

#
# ----------------------------------------------------------------------
# Find
# ----------------------------------------------------------------------
#

proc gorilla::CloseFindDialog {} {
    set top .findDialog
    if {[info exists ::gorilla::toplevel($top)]} {
	wm withdraw $top
    }
}

proc gorilla::Find {} {
    ArrangeIdleTimeout

    if {![info exists ::gorilla::db]} {
	return
    }

    set top .findDialog

    foreach {pref default} {
	caseSensitiveFind 0
	findInAny 0
	findInTitle 1
	findInUsername 1
	findInPassword 0
	findInNotes 1
	findInURL 1
	findThisText ""
    } {
	if {![info exists ::gorilla::preference($pref)]} {
	    set ::gorilla::preference($pref) $default
	}
    }

    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top
	TryResizeFromPreference $top
	wm title $top "Find"

	set title [label $top.title -anchor center -text "Find"]
	pack $title -side top -fill x -pady 10

	set sep1 [Separator $top.sep1 -orient horizontal]
	pack $sep1 -side top -fill x -pady 10

	LabelEntry $top.text -label "Text" \
	    -labelwidth 8 -width 40 -labelanchor w \
	    -textvariable ::gorilla::preference(findThisText)
	pack $top.text -side top -expand yes -fill x -pady 5 -padx 10

	labelframe $top.find -text "Find in ..."
	frame $top.find.t
	frame $top.find.b
	frame $top.find.b.l
	frame $top.find.b.r
	frame $top.find.bb

	checkbutton $top.find.t.any -anchor w \
	    -text "Any field" \
	    -variable ::gorilla::preference(findInAny)
	checkbutton $top.find.b.l.tit -anchor w -text "Title" \
	    -variable ::gorilla::preference(findInTitle)
	checkbutton $top.find.b.l.username -anchor w -text "Username" \
	    -variable ::gorilla::preference(findInUsername)
	checkbutton $top.find.b.l.password -anchor w -text "Password" \
	    -variable ::gorilla::preference(findInPassword)
	checkbutton $top.find.b.r.notes -anchor w -text "Notes" \
	    -variable ::gorilla::preference(findInNotes)
	checkbutton $top.find.b.r.url -anchor w -text "URL" \
	    -variable ::gorilla::preference(findInURL)
	checkbutton $top.find.bb.case -anchor w \
	    -text "Case sensitive find" \
	    -variable ::gorilla::preference(caseSensitiveFind)

	pack $top.find.t.any -side top -pady 3
	pack $top.find.b.l.tit $top.find.b.l.username $top.find.b.l.password \
	    -side top -fill x -padx 5
	pack $top.find.b.r.notes $top.find.b.r.url \
	    -side top -fill x -padx 5
	pack $top.find.t -side top -fill x
	pack $top.find.b.l -side left -fill x
	pack $top.find.b.r -side left -fill x
	pack $top.find.b -side top
	pack $top.find.bb.case -side top -pady 3
	pack $top.find.bb -side top
	pack $top.find -side top -expand yes -fill x -pady 5 -padx 10

	set sep2 [Separator $top.sep2 -orient horizontal]
	pack $sep2 -side top -fill x -pady 10

	frame $top.buts
	set but1 [button $top.buts.b1 -width 15 -text "Find" \
		      -command "::gorilla::RunFind"]
	set but2 [button $top.buts.b2 -width 15 -text "Close" \
		      -command "::gorilla::CloseFindDialog"]
	pack $but1 $but2 -side left -pady 10 -padx 20
	pack $top.buts

	bind $top.text.e <Return> "::gorilla::RunFind"

	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::CloseFindDialog
    } else {
	wm deiconify $top
    }

    #
    # Start with the currently selected node, if any.
    #

    set selection [$::gorilla::widgets(tree) selection get]

    if {[llength $selection] > 0} {
	set ::gorilla::findCurrentNode [lindex $selection 0]
    } else {
	set ::gorilla::findCurrentNode [lindex [$::gorilla::widgets(tree) nodes root] 0]
    }
}

proc gorilla::FindNextNode {node} {
    #
    # If this node has children, return the first child.
    #

    set children [$::gorilla::widgets(tree) nodes $node]

    if {[llength $children] > 0} {
	return [lindex $children 0]
    }

    while {42} {
	#
	# Go to the parent, and find its next child.
	#

	set parent [$::gorilla::widgets(tree) parent $node]
	set children [$::gorilla::widgets(tree) nodes $parent]
	set indexInParent [$::gorilla::widgets(tree) index $node]
	incr indexInParent

	if {$indexInParent < [llength $children]} {
	    set node [lindex $children $indexInParent]
	    break
	}

	#
	# Parent doesn't have any more children. Go up one level.
	#

	set node $parent

	#
	# If we are at the root node, return its first child (wrap around).
	#

	if {$node == "root"} {
	    set node [lindex [$::gorilla::widgets(tree) nodes root] 0]
	    break
	}

	#
	# Find the parent's next sibling.
	#
    }

    return $node
}

proc gorilla::FindCompare {needle haystack caseSensitive} {
    if {$caseSensitive} {
	set cmp [string first $needle $haystack]
    } else {
	set cmp [string first [string tolower $needle] [string tolower $haystack]]
    }

    return [expr {($cmp == -1) ? 0 : 1}]
}

proc gorilla::RunFind {} {
    if {![info exists ::gorilla::findCurrentNode]} {
	set ::gorilla::findCurrentNode [lindex [$::gorilla::widgets(tree) nodes root] 0]
    }

    set text $::gorilla::preference(findThisText)
    set node $::gorilla::findCurrentNode
    set found 0

    set recordsSearched 0
    set totalRecords [llength [$::gorilla::db getAllRecordNumbers]]

    while {!$found} {
	set node [::gorilla::FindNextNode $node]
	set data [$::gorilla::widgets(tree) itemcget $node -data]
	set type [lindex $data 0]

	if {$node == $::gorilla::findCurrentNode} {
	    #
	    # Wrapped around.
	    #
	    break
	}

	if {$type == "Group" || $type == "Root"} {
	    continue
	}

	incr recordsSearched
	set percent [expr {int(100.*$recordsSearched/$totalRecords)}]
	set ::gorilla::status "Searching ... ${percent}%"
	update idletasks

	set rn [lindex $data 1]
	set fa $::gorilla::preference(findInAny)
	set cs $::gorilla::preference(caseSensitiveFind)

	if {($fa || $::gorilla::preference(findInTitle)) && \
		[$::gorilla::db existsField $rn 3]} {
	    if {[FindCompare $text [$::gorilla::db getFieldValue $rn 3] $cs]} {
		set found 3
		break
	    }
	}

	if {($fa || $::gorilla::preference(findInUsername)) && \
		[$::gorilla::db existsField $rn 4]} {
	    if {[FindCompare $text [$::gorilla::db getFieldValue $rn 4] $cs]} {
		set found 4
		break
	    }
	}

	if {($fa || $::gorilla::preference(findInPassword)) && \
		[$::gorilla::db existsField $rn 6]} {
	    if {[FindCompare $text [$::gorilla::db getFieldValue $rn 6] $cs]} {
		set found 6
		break
	    }
	}

	if {($fa || $::gorilla::preference(findInNotes)) && \
		[$::gorilla::db existsField $rn 5]} {
	    if {[FindCompare $text [$::gorilla::db getFieldValue $rn 5] $cs]} {
		set found 5
		break
	    }
	}

	if {($fa || $::gorilla::preference(findInURL)) && \
		[$::gorilla::db existsField $rn 13]} {
	    if {[FindCompare $text [$::gorilla::db getFieldValue $rn 13] $cs]} {
		set found 13
		break
	    }
	}
    }

    if {!$found} {
	set ::gorilla::status "Text not found."
	return
    }

    #
    # Text found.
    #

    #
    # Make sure that all of node's parents are open.
    #

    set parent [$::gorilla::widgets(tree) parent $node]

    while {$parent != "RootNode"} {
	$::gorilla::widgets(tree) itemconfigure $parent -open 1
	set parent [$::gorilla::widgets(tree) parent $parent]
    }

    #
    # Make sure that the node is visible.
    #

    $::gorilla::widgets(tree) see $node
    $::gorilla::widgets(tree) selection set $node

    #
    # Report.
    #

    switch -- $found {
	3 {
	    set ::gorilla::status "Found matching title."
	}
	4 {
	    set ::gorilla::status "Found matching username."
	}
	5 {
	    set ::gorilla::status "Found matching notes."
	}
	6 {
	    set ::gorilla::status "Found matching password."
	}
	13 {
	    set ::gorilla::status "Found matching URL."
	}
	default {
	    set ::gorilla::status "Found match."
	}
    }

    #
    # Remember.
    #

    set ::gorilla::findCurrentNode $node
}

#
# ----------------------------------------------------------------------
# Add a Login
# ----------------------------------------------------------------------
#

proc gorilla::AddLogin {} {
    AddLoginToGroup ""
}

#
# ----------------------------------------------------------------------
# Add a Login to a Group
# ----------------------------------------------------------------------
#

proc gorilla::AddLoginToGroup {group} {
    ArrangeIdleTimeout

    if {![info exists ::gorilla::db]} {
	tk_messageBox -parent . \
		-type ok -icon error -default ok \
		-title "No Database" \
		-message "Please create a new database, or open an existing\
		database first."
	return
    }

    set rn [$::gorilla::db createRecord]

    if {$group != ""} {
	$::gorilla::db setFieldValue $rn 2 $group
    }

    if {![catch {package present uuid}]} {
	$::gorilla::db setFieldValue $rn 1 [uuid::uuid generate]
    }

    $::gorilla::db setFieldValue $rn 7 [clock seconds]

    set res [LoginDialog $rn]

    if {$res == 0} {
	# canceled
	$::gorilla::db deleteRecord $rn
	set ::gorilla::status "Addition of new login canceled."
	return
    }

    set ::gorilla::status "New login added."
    AddRecordToTree $rn
    MarkDatabaseAsDirty
}

#
# ----------------------------------------------------------------------
# Edit a Login
# ----------------------------------------------------------------------
#

proc gorilla::EditLogin {} {
    ArrangeIdleTimeout

    if {[llength [set sel [$::gorilla::widgets(tree) selection get]]] == 0} {
	return
    }

    set node [lindex $sel 0]
    set data [$::gorilla::widgets(tree) itemcget $node -data]
    set type [lindex $data 0]

    if {$type == "Group" || $type == "Root"} {
	return
    }

    set rn [lindex $data 1]

    if {[$::gorilla::db existsField $rn 2]} {
	set oldGroupName [$::gorilla::db getFieldValue $rn 2]
    } else {
	set oldGroupName ""
    }

    set res [LoginDialog $rn]

    if {$res == 0} {
	set ::gorilla::status "Login unchanged."
	# canceled
	return
    }

    if {[$::gorilla::db existsField $rn 2]} {
	set newGroupName [$::gorilla::db getFieldValue $rn 2]
    } else {
	set newGroupName ""
    }

    if {$oldGroupName != $newGroupName} {
	$::gorilla::widgets(tree) delete $node
	AddRecordToTree $rn
    } else {
	if {[$::gorilla::db existsField $rn 3]} {
	    set title [$::gorilla::db getFieldValue $rn 3]
	} else {
	    set title ""
	}

	if {[$::gorilla::db existsField $rn 4]} {
	    append title " \[" [$::gorilla::db getFieldValue $rn 4] "\]"
	}

	$::gorilla::widgets(tree) itemconfigure $node -text $title
    }

    set ::gorilla::status "Login modified."
    MarkDatabaseAsDirty
}

#
# ----------------------------------------------------------------------
# Delete a Login
# ----------------------------------------------------------------------
#

proc gorilla::DeleteLogin {} {
    ArrangeIdleTimeout

    if {[llength [set sel [$::gorilla::widgets(tree) selection get]]] == 0} {
	return
    }

    set node [lindex $sel 0]
    set data [$::gorilla::widgets(tree) itemcget $node -data]
    set type [lindex $data 0]
    set rn [lindex $data 1]

    if {$type != "Login"} {
	error "oops"
    }

    if {0} {
	set answer [tk_messageBox -parent . \
		-type yesno -icon question -default no \
		-title "Delete Login" \
		-message "Are you sure that you want to delete this login?"]

	if {$answer != "yes"} {
	    return
	}
    }

    $::gorilla::db deleteRecord $rn
    $::gorilla::widgets(tree) delete $node
    set ::gorilla::status "Login deleted."
    MarkDatabaseAsDirty
}

#
# ----------------------------------------------------------------------
# Add a new group
# ----------------------------------------------------------------------
#

proc gorilla::AddGroup {} {
    gorilla::AddSubgroupToGroup ""
}

#
# ----------------------------------------------------------------------
# Add a new subgroup (to the selected group)
# ----------------------------------------------------------------------
#

proc gorilla::AddSubgroup {} {
    set sel [$::gorilla::widgets(tree) selection get]

    if {[llength $sel] == 0} {
	#
	# No selection. Add to toplevel
	#

	gorilla::AddSubgroupToGroup ""
    } else {
	set node [lindex $sel 0]
	set data [$::gorilla::widgets(tree) itemcget $node -data]
	set type [lindex $data 0]

	if {$type == "Group"} {
	    gorilla::AddSubgroupToGroup [lindex $data 1]
	} elseif {$type == "Root"} {
	    gorilla::AddSubgroupToGroup ""
	} else {
	    #
	    # A login is selected. Add to its parent group.
	    #
	    set parent [$::gorilla::widgets(tree) parent $node]
	    if {[string equal $parent "RootNode"]} {
		gorilla::AddSubgroupToGroup ""
	    } else {
		set pdata [$::gorilla::widgets(tree) itemcget $node -data]
		gorilla::AddSubgroupToGroup [lindex $pdata 1]
	    }
	}
    }
}

#
# ----------------------------------------------------------------------
# Add a new subgroup
# ----------------------------------------------------------------------
#

proc gorilla::DestroyAddSubgroupDialog {} {
    set ::gorilla::guimutex 2
}

proc gorilla::AddSubgroupToGroup {parentName} {
    ArrangeIdleTimeout

    if {![info exists ::gorilla::db]} {
	tk_messageBox -parent . \
		-type ok -icon error -default ok \
		-title "No Database" \
		-message "Please create a new database, or open an existing\
		database first."
	return
    }

    set top .subgroupDialog

    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top
	TryResizeFromPreference $top
	wm title $top "Add a new Group"

	set title [label $top.title -anchor center -text "Add a new Group"]
	pack $title -side top -fill x -pady 10

	set sep1 [Separator $top.sep1 -orient horizontal]
	pack $sep1 -side top -fill x -pady 10

	LabelEntry $top.parent -label "Parent Group" \
		-labelwidth 16 -width 40 -labelanchor w
	pack $top.parent -side top -expand yes -fill x -pady 5 -padx 10

	LabelEntry $top.group -label "Group Name" \
		-labelwidth 16 -width 40 -labelanchor w
	pack $top.group -side top -expand yes -fill x -pady 5 -padx 10

	set sep2 [Separator $top.sep2 -orient horizontal]
	pack $sep2 -side top -fill x -pady 10

	frame $top.buts
	set but1 [button $top.buts.b1 -width 15 -text "OK" \
		-command "set ::gorilla::guimutex 1"]
	set but2 [button $top.buts.b2 -width 15 -text "Cancel" \
		-command "set ::gorilla::guimutex 2"]
	pack $but1 $but2 -side left -pady 10 -padx 20
	pack $top.buts

	bind $top.parent.e <Shift-Tab> "after 0 \"focus $top.buts.b1\""
	bind $top.group.e <Shift-Tab> "after 0 \"focus $top.parent.e\""
	
	bind $top.parent.e <Return> "set ::gorilla::guimutex 1"
	bind $top.group.e <Return> "set ::gorilla::guimutex 1"
	bind $top.buts.b1 <Return> "set ::gorilla::guimutex 1"
	bind $top.buts.b2 <Return> "set ::gorilla::guimutex 2"

	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::DestroyAddSubgroupDialog
    } else {
	wm deiconify $top
    }

    $top.parent configure -text $parentName
    $top.group configure -text ""

    set oldGrab [grab current .]

    update idletasks
    raise $top
    focus $top.group.e
    grab $top

    while {42} {
	ArrangeIdleTimeout
	set ::gorilla::guimutex 0
	vwait ::gorilla::guimutex

	set parent [$top.parent cget -text]
	set group [$top.group cget -text]

	if {$::gorilla::guimutex != 1} {
	    break
	}

	#
	# The group name must not be empty
	#

	if {$group == ""} {
	    tk_messageBox -parent $top \
		    -type ok -icon error -default ok \
		    -title "Invalid Group Name" \
		    -message "The group name can not be empty."
	    continue
	}

	#
	# See if the parent's group name can be parsed
	#

	if {[catch {
	    set parents [pwsafe::db::splitGroup $parent]
	}]} {
	    tk_messageBox -parent $top \
		    -type ok -icon error -default ok \
		    -title "Invalid Group Name" \
		    -message "The name of the parent group is invalid."
	    continue
	}

	break
    }

    if {$oldGrab != ""} {
	grab $oldGrab
    } else {
	grab release $top
    }

    wm withdraw $top

    if {$::gorilla::guimutex != 1} {
	set ::gorilla::status "Addition of group canceled."
	return
    }

    lappend parents $group
    set fullGroupName [pwsafe::db::concatGroups $parents]
    AddGroupToTree $fullGroupName

    set piter [list]
    foreach parent $parents {
	lappend piter $parent
	set fullParentName [pwsafe::db::concatGroups $piter]
	set node $::gorilla::groupNodes($fullParentName)
	$::gorilla::widgets(tree) itemconfigure $node -open 1
    }

    $::gorilla::widgets(tree) itemconfigure "RootNode" -open 1
    set ::gorilla::status "New group added."
}

#
# ----------------------------------------------------------------------
# Delete Group
# ----------------------------------------------------------------------
#

proc gorilla::DeleteGroup {} {
    ArrangeIdleTimeout

    if {[llength [set sel [$::gorilla::widgets(tree) selection get]]] == 0} {
	return
    }

    set node [lindex $sel 0]
    set data [$::gorilla::widgets(tree) itemcget $node -data]
    set type [lindex $data 0]

    if {$type == "Root"} {
	tk_messageBox -parent . \
		-type ok -icon error -default ok \
		-title "Can Not Delete Root" \
		-message "The root node can not be deleted."
	return
    }

    if {$type != "Group"} {
	error "oops"
    }

    set groupName [$::gorilla::widgets(tree) itemcget $node -text]
    set fullGroupName [lindex $data 1]

    if {[llength [$::gorilla::widgets(tree) nodes $node]] > 0} {
	set answer [tk_messageBox -parent . \
		-type yesno -icon question -default no \
		-title "Delete Group" \
		-message "Are you sure that you want to delete group\
		\"$groupName\" and all its contents?"]

	if {$answer != "yes"} {
	    return
	}
	set hadchildren 1
    } else {
	set hadchildren 0
    }

    set ::gorilla::status "Group deleted."
    gorilla::DeleteGroupRek $node

    if {$hadchildren} {
	MarkDatabaseAsDirty
    }
}

proc gorilla::DeleteGroupRek {node} {
    set children [$::gorilla::widgets(tree) nodes $node]

    foreach child $children {
	set data [$::gorilla::widgets(tree) itemcget $child -data]
	set type [lindex $data 0]

	if {$type == "Login"} {
	    $::gorilla::db deleteRecord [lindex $data 1]
	    $::gorilla::widgets(tree) delete $child
	} else {
	    DeleteGroupRek $child
	}
    }

    set groupName [lindex [$::gorilla::widgets(tree) itemcget $node -data] 1]
    unset ::gorilla::groupNodes($groupName)
    $::gorilla::widgets(tree) delete $node
}

#
# ----------------------------------------------------------------------
# Rename Group
# ----------------------------------------------------------------------
#

proc gorilla::DestroyRenameGroupDialog {} {
    set ::gorilla::guimutex 2
}

proc gorilla::RenameGroup {} {
    ArrangeIdleTimeout

    if {[llength [set sel [$::gorilla::widgets(tree) selection get]]] == 0} {
	return
    }

    set node [lindex $sel 0]
    set data [$::gorilla::widgets(tree) itemcget $node -data]
    set type [lindex $data 0]

    if {$type == "Root"} {
	tk_messageBox -parent . \
		-type ok -icon error -default ok \
		-title "Can Not Rename Root" \
		-message "The root node can not be renamed."
	return
    }

    if {$type != "Group"} {
	error "oops"
    }

    set fullGroupName [lindex $data 1]
    set groupName [$::gorilla::widgets(tree) itemcget $node -text]
    set parentNode [$::gorilla::widgets(tree) parent $node]
    set parentData [$::gorilla::widgets(tree) itemcget $parentNode -data]
    set parentType [lindex $parentData 0]

    if {$parentType == "Group"} {
	set parentName [lindex $parentData 1]
    } else {
	set parentName ""
    }

    set top .renameGroup

    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top
	TryResizeFromPreference $top
	wm title $top "Rename Group"

	set title [label $top.title -anchor center -text "Rename Group"]
	pack $title -side top -fill x -pady 10

	set sep1 [Separator $top.sep1 -orient horizontal]
	pack $sep1 -side top -fill x -pady 10

	LabelEntry $top.parent -label "Parent" \
		-labelwidth 10 -width 40 -labelanchor w
	pack $top.parent -side top -expand yes -fill x -pady 5 -padx 10

	LabelEntry $top.group -label "Name" \
		-labelwidth 10 -width 40 -labelanchor w
	pack $top.group -side top -expand yes -fill x -pady 5 -padx 10
	bind $top.group.e <Shift-Tab> "after 0 \"focus $top.parent.e\""

	set sep2 [Separator $top.sep2 -orient horizontal]
	pack $sep2 -side top -fill x -pady 10

	frame $top.buts
	set but1 [button $top.buts.b1 -width 15 -text "OK" \
		-command "set ::gorilla::guimutex 1"]
	set but2 [button $top.buts.b2 -width 15 -text "Cancel" \
		-command "set ::gorilla::guimutex 2"]
	pack $but1 $but2 -side left -pady 10 -padx 20
	pack $top.buts

	bind $top.parent.e <Return> "set ::gorilla::guimutex 1"
	bind $top.group.e <Return> "set ::gorilla::guimutex 1"
	bind $top.buts.b1 <Return> "set ::gorilla::guimutex 1"
	bind $top.buts.b2 <Return> "set ::gorilla::guimutex 2"

	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::DestroyRenameGroupDialog
    } else {
	wm deiconify $top
    }

    $top.parent configure -text $parentName
    $top.group configure -text $groupName

    set oldGrab [grab current .]

    update idletasks
    raise $top
    focus $top.group.e
    grab $top

    while {42} {
	ArrangeIdleTimeout
	set ::gorilla::guimutex 0
	vwait ::gorilla::guimutex

	if {$::gorilla::guimutex != 1} {
	    break
	}

	set newParent [$top.parent cget -text]
	set newGroup [$top.group cget -text]

	#
	# Validate that both group names are valid
	#

	if {$newGroup == ""} {
	    tk_messageBox -parent $top \
		    -type ok -icon error -default ok \
		    -title "Invalid Group Name" \
		    -message "The group name can not be empty."
	    continue
	}

	if {[catch {
	    set newParents [pwsafe::db::splitGroup $newParent]
	}]} {
	    tk_messageBox -parent $top \
		    -type ok -icon error -default ok \
		    -title "Invalid Group Name" \
		    -message "The name of the group's parent node\
		    is invalid."
	    continue
	}

	#
	# if we got this far, all is well
	#

	break
    }

    if {$oldGrab != ""} {
	grab $oldGrab
    } else {
	grab release $top
    }

    wm withdraw $top

    if {$::gorilla::guimutex != 1} {
	return
    }

    if {$parentName == $newParent && $groupName == $newGroup} {
	#
	# Unchanged
	#
	set ::gorilla::status "Group name unchanged."
	return
    }

    #
    # See if the parent of the new group exists, or create it
    #

    set destparentnode [AddGroupToTree $newParent]
    set destparentdata [$::gorilla::widgets(tree) itemcget $destparentnode -data]
    set destparenttype [lindex $destparentdata 0]

    #
    # Works nearly the same as dragging and dropping
    #

    #
    # When we are moving a group, make sure that destination is not a
    # child of this group
    #

    set destiter $destparentnode
    while {$destiter != "RootNode"} {
	if {$destiter == $node} {
	    break
	}
	set destiter [$::gorilla::widgets(tree) parent $destiter]
    }

    if {$destiter != "RootNode" || $node == "RootNode"} {
	tk_messageBox -parent . \
		-type ok -icon error -default ok \
		-title "Can Not Move Node" \
		-message "Can not move a group to a subgroup\
		of itself."
	return
    }

    #
    # Move recursively
    #

    if {$newGroup != ""} {
	lappend newParents $newGroup
    }

    set newParentName [pwsafe::db::concatGroups $newParents]
    set newParentNode [AddGroupToTree $newParentName]

    foreach child [$::gorilla::widgets(tree) nodes $node] {
	set childdata [$::gorilla::widgets(tree) itemcget $child -data]
	set childtype [lindex $childdata 0]

	if {$childtype == "Login"} {
	    set rn [lindex $childdata 1]
	    $::gorilla::db setFieldValue $rn 2 $newParentName
	    $::gorilla::widgets(tree) delete $child
	    AddRecordToTree $rn
	} else {
	    MoveTreeNodeRek $child $newParents
	}

    }

    unset ::gorilla::groupNodes($fullGroupName)
    $::gorilla::widgets(tree) itemconfigure $newParentNode \
	    -open [$::gorilla::widgets(tree) itemcget $node -open]
    $::gorilla::widgets(tree) delete $node
    set ::gorilla::status "Group renamed."
    MarkDatabaseAsDirty
}

#
# ----------------------------------------------------------------------
# Set the Password Policy
# ----------------------------------------------------------------------
#

proc gorilla::PasswordPolicy {} {
    ArrangeIdleTimeout

    if {![info exists ::gorilla::db]} {
	tk_messageBox -parent . \
		-type ok -icon error -default ok \
		-title "No Database" \
		-message "Please create a new database, or open an existing\
		database first."
	return
    }

    set oldSettings [GetDefaultPasswordPolicy]
    set newSettings [PasswordPolicyDialog "Password Policy" $oldSettings]

    if {[llength $newSettings]} {
	SetDefaultPasswordPolicy $newSettings
	set ::gorilla::status "Password policy changed."
	MarkDatabaseAsDirty
    }
}

#
# ----------------------------------------------------------------------
# Change the password
# ----------------------------------------------------------------------
#

proc gorilla::ChangePassword {} {
    ArrangeIdleTimeout

    if {![info exists ::gorilla::db]} {
	tk_messageBox -parent . \
		-type ok -icon error -default ok \
		-title "No Database" \
		-message "Please create a new database, or open an existing\
		database first."
	return
    }

    if {[catch {
	set currentPassword [GetPassword 0 "Enter Current Master Password"]
    }]} {
	# canceled
	return
    }

    if {![$::gorilla::db checkPassword $currentPassword]} {
	tk_messageBox -parent . \
		-type ok -icon error -default ok \
		-title "Wrong Password" \
		-message "That password is not correct."
	return
    }

    pwsafe::int::randomizeVar currentPassword

    if {[catch {
	set newPassword [GetPassword 1 "Choose New Master Password"]
    }]} {
	tk_messageBox -parent . \
		-type ok -icon info -default ok \
		-title "Password Not Changed" \
		-message "You canceled the setting of a new password.\
		Therefore, the existing password remains in effect."
	return
    }

    $::gorilla::db setPassword $newPassword
    pwsafe::int::randomizeVar newPassword
    set ::gorilla::status "Master password changed."
    MarkDatabaseAsDirty
}

#
# ----------------------------------------------------------------------
# Prompt for a Password
# ----------------------------------------------------------------------
#

proc gorilla::DestroyGetPasswordDialog {} {
    set ::gorilla::guimutex 2
}

proc gorilla::GetPassword {confirm title} {
    set top .passwordDialog-$confirm

    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top
	TryResizeFromPreference $top

	set tit [label $top.title -anchor center]
	pack $tit -side top -fill x -pady 10

	set sep1 [Separator $top.sep1 -orient horizontal]
	pack $sep1 -side top -fill x -pady 10

	LabelEntry $top.password -label "Password" -show "*" \
		-labelwidth 10 -width 40 -labelanchor w \
		-font {Courier}
	pack $top.password -side top -expand yes -fill x -pady 5 -padx 10
	bind $top.password.e <KeyPress> "+::gorilla::CollectTicks"
	bind $top.password.e <KeyRelease> "+::gorilla::CollectTicks"

	if {$confirm} {
	    LabelEntry $top.confirm -label "Confirm" -show "*" \
		    -labelwidth 10 -width 40 -labelanchor w \
		    -font {Courier}
	    pack $top.confirm -side top -expand yes -fill x -pady 5 -padx 10
	    bind $top.confirm.e <KeyPress> "+::gorilla::CollectTicks"
	    bind $top.confirm.e <KeyRelease> "+::gorilla::CollectTicks"
	    bind $top.confirm.e <Shift-Tab> "after 0 \"focus $top.password.e\""
	}

	set sep2 [Separator $top.sep2 -orient horizontal]
	pack $sep2 -side top -fill x -pady 10

	frame $top.buts
	set but1 [button $top.buts.b1 -width 15 -text "OK" \
		-command "set ::gorilla::guimutex 1"]
	set but2 [button $top.buts.b2 -width 15 -text "Cancel" \
		-command "set ::gorilla::guimutex 2"]
	pack $but1 $but2 -side left -pady 10 -padx 20
	pack $top.buts

	bind $top.password.e <Return> "set ::gorilla::guimutex 1"
	bind $top.buts.b1 <Return> "set ::gorilla::guimutex 1"
	bind $top.buts.b2 <Return> "set ::gorilla::guimutex 2"

	if {$confirm} {
	    bind $top.confirm.e <Return> "set ::gorilla::guimutex 1"
	}

	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::DestroyGetPasswordDialog
    } else {
	wm deiconify $top
    }

    wm title $top $title
    $top.title configure -text $title
    $top.password configure -text ""

    if {$confirm} {
	$top.confirm configure -text ""
    }

    #
    # Run dialog
    #

    set oldGrab [grab current .]

    update idletasks
    raise $top
    focus $top.password.e
    grab $top

    while {42} {
	ArrangeIdleTimeout
	set ::gorilla::guimutex 0
	vwait ::gorilla::guimutex

	if {$::gorilla::guimutex != 1} {
	    break
	}

	set password [$top.password cget -text]

	if {$confirm} {
	    set confirmed [$top.confirm cget -text]

	    if {![string equal $password $confirmed]} {
		tk_messageBox -parent $top \
			-type ok -icon error -default ok \
			-title "Passwords Do Not Match" \
			-message "The confirmed password does not match."
	    } else {
		break
	    }
	} else {
	    break
	}
    }

    $top.password configure -text ""

    if {$oldGrab != ""} {
	grab $oldGrab
    } else {
	grab release $top
    }

    wm withdraw $top

    if {$::gorilla::guimutex != 1} {
	error "canceled"
    }

    return $password
}

#
# ----------------------------------------------------------------------
# Edit a Login
# ----------------------------------------------------------------------
#

proc gorilla::DestroyLoginDialog {} {
    set ::gorilla::guimutex 2
}

proc gorilla::LoginDialog {rn} {
    ArrangeIdleTimeout

    #
    # Set up dialog
    #

    set top .loginDialog

    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top
	TryResizeFromPreference $top
	wm title $top "Add/Edit/View Login"

	frame $top.l
	LabelEntry $top.l.group -label "Group:" -labelwidth 12 \
		-width 40 -labelanchor w
	pack $top.l.group -side top -fill x -pady 5 -padx 10

	LabelEntry $top.l.tit -label "Title:" -labelwidth 12 \
		-width 40 -labelanchor w
	pack $top.l.tit -side top -fill x -pady 5 -padx 10

	LabelEntry $top.l.url -label "URL:" -labelwidth 12 \
	    -width 40 -labelanchor w
	pack $top.l.url -side top -fill x -pady 5 -padx 10

	LabelEntry $top.l.user -label "Username:" -labelwidth 12 \
		-width 40 -labelanchor w
	pack $top.l.user -side top -fill x -pady 5 -padx 10

	LabelEntry $top.l.pass -label "Password:" -labelwidth 12 \
		-width 20 -labelanchor w -show "*" \
		-font {Courier}
	pack $top.l.pass -side top -fill x -pady 5 -padx 10

	frame $top.l.notes
	label $top.l.notes.label -width 12 -text "Notes:" -anchor w
	set sw [ScrolledWindow $top.l.notes.notes -auto horizontal]
	text $top.l.notes.notes.t -width 40 -height 5 -wrap word
	$sw setwidget $top.l.notes.notes.t
	pack $top.l.notes.label -side left
	pack $top.l.notes.notes -side left -expand yes -fill both -pady 5
	pack $top.l.notes -side top -expand yes -fill both -pady 5 -padx 10

	LabelEntry $top.l.lpwc -label "Last Password Change:" -labelwidth 20 \
		-width 40 -labelanchor w -state disabled -relief flat \
		-disabledforeground [$top.l.pass cget -fg] \
		-entrybg [$top.l.pass cget -background]
	pack $top.l.lpwc -side top -fill x -padx 10

	LabelEntry $top.l.mod -label "Last Modified:" -labelwidth 20 \
		-width 40 -labelanchor w -state disabled -relief flat \
		-disabledforeground [$top.l.pass cget -fg] \
		-entrybg [$top.l.pass cget -background]
	pack $top.l.mod -side top -fill x -padx 10

	pack $top.l -side left -expand yes -fill both -pady 10

	frame $top.r
	frame $top.r.top
	button $top.r.top.ok -width 16 -text "OK" \
		-command "set ::gorilla::guimutex 1"
	button $top.r.top.c -width 16 -text "Cancel" \
		-command "set ::gorilla::guimutex 2"
	pack $top.r.top.ok $top.r.top.c -side top -padx 10 -pady 5
	pack $top.r.top -side top -pady 20

	frame $top.r.pws
	button $top.r.pws.show -width 16 -text "Show Password" \
		-command "set ::gorilla::guimutex 3"
	button $top.r.pws.gen -width 16 -text "Generate Password" \
		-command "set ::gorilla::guimutex 4"
	checkbutton $top.r.pws.override -text "Override\nPassword Policy" \
		-variable ::gorilla::overridePasswordPolicy \
		-justify left
	pack $top.r.pws.show $top.r.pws.gen $top.r.pws.override \
		-side top -padx 10 -pady 5
	pack $top.r.pws -side top -pady 20

	pack $top.r -side right -fill both

	#
	# Set up bindings
	#

	bind $top.l.group.e <Shift-Tab> "after 0 \"focus $top.r.top.ok\""
	bind $top.l.tit.e <Shift-Tab> "after 0 \"focus $top.l.group.e\""
	bind $top.l.user.e <Shift-Tab> "after 0 \"focus $top.l.tit.e\""
	bind $top.l.pass.e <Shift-Tab> "after 0 \"focus $top.l.user.e\""
	#bind $top.l.notes.notes.t <Tab> "after 0 \"focus $top.r.top.ok\""
	#bind $top.l.notes.notes.t <Shift-Tab> "after 0 \"focus $top.l.pass.e\""

	bind $top.l.group.e <Return> "set ::gorilla::guimutex 1"
	bind $top.l.tit.e <Return> "set ::gorilla::guimutex 1"
	bind $top.l.user.e <Return> "set ::gorilla::guimutex 1"
	bind $top.l.pass.e <Return> "set ::gorilla::guimutex 1"
	bind $top.r.top.ok <Return> "set ::gorilla::guimutex 1"
	bind $top.r.top.c <Return> "set ::gorilla::guimutex 2"

	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::DestroyLoginDialog
    } else {
	wm deiconify $top
    }

    #
    # Configure dialog
    #

    $top.l.group configure -text ""
    $top.l.tit configure -text ""
    $top.l.url configure -text ""
    $top.l.user configure -text ""
    $top.l.pass configure -text ""
    $top.l.notes.notes.t delete 1.0 end
    $top.l.lpwc configure -text "<unknown>"
    $top.l.mod configure -text "<unknown>"

    if {[$::gorilla::db existsRecord $rn]} {
	if {[$::gorilla::db existsField $rn 2]} {
	    $top.l.group configure -text [$::gorilla::db getFieldValue $rn 2]
	}
	if {[$::gorilla::db existsField $rn 3]} {
	    $top.l.tit configure -text [$::gorilla::db getFieldValue $rn 3]
	}
	if {[$::gorilla::db existsField $rn 4]} {
	    $top.l.user configure -text [$::gorilla::db getFieldValue $rn 4]
	}
	if {[$::gorilla::db existsField $rn 5]} {
	    $top.l.notes.notes.t insert 1.0 [$::gorilla::db getFieldValue $rn 5]
	}
	if {[$::gorilla::db existsField $rn 6]} {
	    $top.l.pass configure -text [$::gorilla::db getFieldValue $rn 6]
	}
	if {[$::gorilla::db existsField $rn 8]} {
	    $top.l.lpwc configure -text \
		    [clock format [$::gorilla::db getFieldValue $rn 8] \
		    -format "%Y-%m-%d %H:%M:%S"]
	}
	if {[$::gorilla::db existsField $rn 12]} {
	    $top.l.mod configure -text \
		    [clock format [$::gorilla::db getFieldValue $rn 12] \
		    -format "%Y-%m-%d %H:%M:%S"]
	}
	if {[$::gorilla::db existsField $rn 13]} {
	    $top.l.url configure -text [$::gorilla::db getFieldValue $rn 13]
	}
    }

    if {[$::gorilla::db existsRecord $rn] && \
	    [$::gorilla::db existsField $rn 6]} {
	$top.l.pass configure -show "*"
	$top.r.pws.show configure -text "Show Password"
    } else {
	$top.l.pass configure -show ""
	$top.r.pws.show configure -text "Hide Password"
    }

    if {![info exists ::gorilla::overriddenPasswordPolicy]} {
	set ::gorilla::overriddenPasswordPolicy [GetDefaultPasswordPolicy]
    }

    if {[$::gorilla::db hasHeaderField 0] && \
	    [lindex [$::gorilla::db getHeaderField 0] 0] >= 3} {
	$top.l.url configure -state normal
    } else {
	# Version 2 does not have a separate URL field
	$top.l.url configure -text "(Not available with v2 database format.)"
	$top.l.url configure -state disabled
    }

    #
    # Run dialog
    #

    set oldGrab [grab current .]

    update idletasks
    raise $top
    focus $top.l.tit.e
    grab $top

    while {42} {
	ArrangeIdleTimeout
	set ::gorilla::guimutex 0
	vwait ::gorilla::guimutex

	if {$::gorilla::guimutex == 1} {
	    if {[$top.l.tit cget -text] == ""} {
		tk_messageBox -parent $top \
			-type ok -icon error -default ok \
			-title "Login Needs Title" \
			-message "A login must at least have a title.\
			Please enter a title for this login."
		continue
	    }
	    if {[catch {
		pwsafe::db::splitGroup [$top.l.group cget -text]
	    }]} {
		tk_messageBox -parent $top \
			-type ok -icon error -default ok \
			-title "Invalid Group Name" \
			-message "This login's group name is not valid."
		continue
	    }
	    break
	} elseif {$::gorilla::guimutex == 2} {
	    break
	} elseif {$::gorilla::guimutex == 3} {
	    #
	    # Show Password
	    #
	    set show [$top.l.pass cget -show]
	    if {$show == ""} {
		$top.l.pass configure -show "*"
		$top.r.pws.show configure -text "Show Password"
	    } else {
		$top.l.pass configure -show ""
		$top.r.pws.show configure -text "Hide Password"
	    }
	} elseif {$::gorilla::guimutex == 4} {
	    #
	    # Generate Password
	    #
	    if {$::gorilla::overridePasswordPolicy} {
		set settings [PasswordPolicyDialog \
			"Override Password Policy" \
			$::gorilla::overriddenPasswordPolicy]
		if {[llength $settings] == 0} {
		    continue
		}
		set ::gorilla::overriddenPasswordPolicy $settings
	    } else {
		set settings [GetDefaultPasswordPolicy]
	    }
	    if {[catch {
		set newPassword [GeneratePassword $settings]
	    } oops]} {
		tk_messageBox -parent $top \
			-type ok -icon error -default ok \
			-title "Invalid Password Settings" \
			-message "The password policy settings are invalid."
		continue
	    }
	    $top.l.pass configure -text $newPassword
	    pwsafe::int::randomizeVar newPassword
	}
    }

    if {$oldGrab != ""} {
	grab $oldGrab
    } else {
	grab release $top
    }

    wm withdraw $top

    #
    # Canceled?
    #

    if {$::gorilla::guimutex != 1} {
	$top.l.group configure -text ""
	$top.l.url configure -text ""
	$top.l.tit configure -text ""
	$top.l.user configure -text ""
	$top.l.pass configure -text ""
	$top.l.notes.notes.t delete 1.0 end
	return 0
    }

    #
    # Store fields in the database
    #

    set modified 0
    set now [clock seconds]

    set group [$top.l.group cget -text]
    if {$group != ""} {
	if {![$::gorilla::db existsField $rn 2] || \
		![string equal $group [$::gorilla::db getFieldValue $rn 2]]} {
	    set modified 1
	}
	$::gorilla::db setFieldValue $rn 2 $group
    } elseif {[$::gorilla::db existsField $rn 2]} {
	$::gorilla::db unsetFieldValue $rn 2
	set modified 1
    }
    $top.l.group configure -text ""
    pwsafe::int::randomizeVar group

    set title [$top.l.tit cget -text]
    if {$title != ""} {
	if {![$::gorilla::db existsField $rn 3] || \
		![string equal $title [$::gorilla::db getFieldValue $rn 3]]} {
	    set modified 1
	}
	$::gorilla::db setFieldValue $rn 3 $title
    } elseif {[$::gorilla::db existsField $rn 3]} {
	$::gorilla::db unsetFieldValue $rn 3
	set modified 1
    }
    $top.l.tit configure -text ""
    pwsafe::int::randomizeVar title

    set user [$top.l.user cget -text]
    if {$user != ""} {
	if {![$::gorilla::db existsField $rn 4] || \
		![string equal $user [$::gorilla::db getFieldValue $rn 4]]} {
	    set modified 1
	}
	$::gorilla::db setFieldValue $rn 4 $user
    } elseif {[$::gorilla::db existsField $rn 4]} {
	$::gorilla::db unsetFieldValue $rn 4
	set modified 1
    }
    $top.l.user configure -text ""
    pwsafe::int::randomizeVar user

    set pass [$top.l.pass cget -text]
    if {$pass != ""} {
	if {![$::gorilla::db existsField $rn 6] || \
		![string equal $pass [$::gorilla::db getFieldValue $rn 6]]} {
	    set modified 1
	    $::gorilla::db setFieldValue $rn 8 $now ;# PW mod time
	}
	$::gorilla::db setFieldValue $rn 6 $pass
    } elseif {[$::gorilla::db existsField $rn 6]} {
	$::gorilla::db unsetFieldValue $rn 6
	set modified 1
    }
    pwsafe::int::randomizeVar pass
    $top.l.pass configure -text ""

    set notes [string trim [$top.l.notes.notes.t get 1.0 end]]
    if {$notes != ""} {
	if {![$::gorilla::db existsField $rn 5] || \
		![string equal $notes [$::gorilla::db getFieldValue $rn 5]]} {
	    set modified 1
	}
	$::gorilla::db setFieldValue $rn 5 $notes
    } elseif {[$::gorilla::db existsField $rn 5]} {
	$::gorilla::db unsetFieldValue $rn 5
	set modified 1
    }
    $top.l.notes.notes.t delete 1.0 end
    pwsafe::int::randomizeVar notes

    if {[$top.l.url cget -state] == "normal"} {
	set url [$top.l.url cget -text]

	if {$url != ""} {
	    if {![$::gorilla::db existsField $rn 13] || \
		    ![string equal $url [$::gorilla::db getFieldValue $rn 13]]} {
		set modified 1
		$::gorilla::db setFieldValue $rn 8 $now ;# PW mod time
	    }
	    $::gorilla::db setFieldValue $rn 13 $url
	} elseif {[$::gorilla::db existsField $rn 13]} {
	    $::gorilla::db unsetFieldValue $rn 13
	    set modified 1
	}
	pwsafe::int::randomizeVar url
    }
    $top.l.url configure -text ""

    if {$modified} {
	$::gorilla::db setFieldValue $rn 12 $now
    }

    return $modified
}

#
# ----------------------------------------------------------------------
# Default Password Policy
# ----------------------------------------------------------------------
#

proc gorilla::GetDefaultPasswordPolicy {} {
    array set defaults [list \
	    length [$::gorilla::db getPreference "PWLenDefault"] \
	    uselowercase [$::gorilla::db getPreference "PWUseLowercase"] \
	    useuppercase [$::gorilla::db getPreference "PWUseUppercase"] \
	    usedigits [$::gorilla::db getPreference "PWUseDigits"] \
	    usesymbols [$::gorilla::db getPreference "PWUseSymbols"] \
	    usehexdigits [$::gorilla::db getPreference "PWUseHexDigits"] \
	    easytoread [$::gorilla::db getPreference "PWEasyVision"]]
    return [array get defaults]
}

proc gorilla::SetDefaultPasswordPolicy {settings} {
    array set defaults $settings
    if {[info exists defaults(length)]} {
	$::gorilla::db setPreference "PWLenDefault" $defaults(length)
    }
    if {[info exists defaults(uselowercase)]} {
	$::gorilla::db setPreference "PWUseLowercase" $defaults(uselowercase)
    }
    if {[info exists defaults(useuppercase)]} {
	$::gorilla::db setPreference "PWUseUppercase" $defaults(useuppercase)
    }
    if {[info exists defaults(usedigits)]} {
	$::gorilla::db setPreference "PWUseDigits" $defaults(usedigits)
    }
    if {[info exists defaults(usesymbols)]} {
	$::gorilla::db setPreference "PWUseSymbols" $defaults(usesymbols)
    }
    if {[info exists defaults(usehexdigits)]} {
	$::gorilla::db setPreference "PWUseHexDigits" $defaults(usehexdigits)
    }
    if {[info exists defaults(easytoread)]} {
	$::gorilla::db setPreference "PWEasyVision" $defaults(easytoread)
    }
}

#
# ----------------------------------------------------------------------
# Dialog box for password policy
# ----------------------------------------------------------------------
#

proc gorilla::DestroyPasswordPolicyDialog {} {
    set ::gorilla::guimutex 2
}

proc gorilla::PasswordPolicyDialog {title settings} {
    ArrangeIdleTimeout

    array set ::gorilla::ppd [list \
	    length 8 \
	    uselowercase 1 \
	    useuppercase 1 \
	    usedigits 1 \
	    usehexdigits 0 \
	    usesymbols 0 \
	    easytoread 1]
    array set ::gorilla::ppd $settings

    set top .passPolicyDialog

    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top
	TryResizeFromPreference $top

	frame $top.plen
	label $top.plen.l -text "Password Length"
	spinbox $top.plen.s -from 1 -to 999 -increment 1 \
		-width 4 -justify right \
		-textvariable ::gorilla::ppd(length)
	pack $top.plen.l -side left
	pack $top.plen.s -side left -padx 10
	pack $top.plen -side top -anchor w -pady 3

	checkbutton $top.lower -text "Use lowercase letters" -anchor w \
		-variable ::gorilla::ppd(uselowercase)
	checkbutton $top.upper -text "Use UPPERCASE letters" -anchor w \
		-variable ::gorilla::ppd(useuppercase)
	checkbutton $top.digits -text "Use digits" -anchor w \
		-variable ::gorilla::ppd(usedigits)
	checkbutton $top.hex -text "Use hexadecimal digits" -anchor w \
		-variable ::gorilla::ppd(usehexdigits)
	checkbutton $top.symbols -text "Use symbols (%, \$, @, #, etc.)" -anchor w \
		-variable ::gorilla::ppd(usesymbols)
	checkbutton $top.easy \
		-text "Use easy to read characters only (e.g. no \"0\" or \"O\")" \
		-variable ::gorilla::ppd(easytoread)
	pack $top.lower $top.upper $top.digits $top.hex $top.symbols \
		$top.easy -anchor w -side top -pady 3

	Separator $top.sep -orient horizontal
	pack $top.sep -side top -fill x -pady 10

	frame $top.buts
	set but1 [button $top.buts.b1 -width 15 -text "OK" \
		-command "set ::gorilla::guimutex 1"]
	set but2 [button $top.buts.b2 -width 15 -text "Cancel" \
		-command "set ::gorilla::guimutex 2"]
	pack $but1 $but2 -side left -pady 10 -padx 20
	pack $top.buts

	bind $top.lower <Return> "set ::gorilla::guimutex 1"
	bind $top.upper <Return> "set ::gorilla::guimutex 1"
	bind $top.digits <Return> "set ::gorilla::guimutex 1"
	bind $top.hex <Return> "set ::gorilla::guimutex 1"
	bind $top.symbols <Return> "set ::gorilla::guimutex 1"
	bind $top.easy <Return> "set ::gorilla::guimutex 1"
	bind $top.buts.b1 <Return> "set ::gorilla::guimutex 1"
	bind $top.buts.b2 <Return> "set ::gorilla::guimutex 2"

	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::DestroyPasswordPolicyDialog
    } else {
	wm deiconify $top
    }

    set oldGrab [grab current .]

    update idletasks
    wm title $top $title
    raise $top
    focus $top.plen.s
    grab $top

    set ::gorilla::guimutex 0
    vwait ::gorilla::guimutex

    if {$oldGrab != ""} {
	grab $oldGrab
    } else {
	grab release $top
    }

    wm withdraw $top

    if {$::gorilla::guimutex != 1} {
	return [list]
    }

    return [array get ::gorilla::ppd]
}

#
# ----------------------------------------------------------------------
# Generate a password
# ----------------------------------------------------------------------
#

proc gorilla::GeneratePassword {settings} {
    set easyLowercaseLetters "abcdefghkmnpqrstuvwxyz"
    set notEasyLowercaseLetters "ijlo"
    set easyUppercaseLetters [string toupper $easyLowercaseLetters]
    set notEasyUppercaseLetters [string toupper $notEasyLowercaseLetters]
    set easyDigits "23456789"
    set notEasyDigits "01"
    set easySymbols "+-=_@#\$%^&<>/~\\?"
    set notEasySymbols "!|()"

    array set params [list \
	    length 0 \
	    uselowercase 0 \
	    useuppercase 0 \
	    usedigits 0 \
	    usehexdigits 0 \
	    usesymbols 0 \
	    easytoread 0]
    array set params $settings

    set symbolSet ""

    if {$params(uselowercase)} {
	append symbolSet $easyLowercaseLetters
	if {!$params(easytoread)} {
	    append symbolSet $notEasyLowercaseLetters
	}
    }

    if {$params(useuppercase)} {
	append symbolSet $easyUppercaseLetters
	if {!$params(easytoread)} {
	    append symbolSet $notEasyUppercaseLetters
	}
    }

    if {$params(usehexdigits)} {
	if {!$params(uselowercase)} {
	    append symbolSet "0123456789abcdef"
	} else {
	    append symbolSet "0123456789"
	}
    } elseif {$params(usedigits)} {
	append symbolSet $easyDigits
	if {!$params(easytoread)} {
	    append symbolSet $notEasyDigits
	}
    }

    if {$params(usesymbols)} {
	append symbolSet $easySymbols
	if {!$params(easytoread)} {
	    append symbolSet $notEasySymbols
	}
    }
 
    set numSymbols [string length $symbolSet]

    if {$numSymbols == 0} {
	error "invalid settings"
    }

    set generatedPassword ""
    for {set i 0} {$i < $params(length)} {incr i} {
	set rand [::isaac::rand]
	set randSymbol [expr {int($rand*$numSymbols)}]
	append generatedPassword [string index $symbolSet $randSymbol]
    }

    return $generatedPassword
}

#
# ----------------------------------------------------------------------
# Dialog box for database-specific preferences
# ----------------------------------------------------------------------
#

proc gorilla::DestroyDatabasePreferencesDialog {} {
    set ::gorilla::guimutex 2
}

proc gorilla::DatabasePreferencesDialog {} {
    ArrangeIdleTimeout

    set top .dbPrefsDialog

    if {![info exists ::gorilla::db]} {
	return
    }

    foreach pref {IdleTimeout IsUTF8 LockOnIdleTimeout SaveImmediately} {
	set ::gorilla::dpd($pref) [$::gorilla::db getPreference $pref]
    }

    if {!$::gorilla::dpd(LockOnIdleTimeout)} {
	set ::gorilla::dpd(IdleTimeout) 0
    }

    if {[$::gorilla::db hasHeaderField 0]} {
	set oldVersion [lindex [$::gorilla::db getHeaderField 0] 0]
    } else {
	set oldVersion 2
    }

    set ::gorilla::dpd(defaultVersion) $oldVersion

    set ::gorilla::dpd(keyStretchingIterations) \
	[$::gorilla::db cget -keyStretchingIterations]
    set oldKeyStretchingIterations $::gorilla::dpd(keyStretchingIterations)

    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top
	wm title $top "Database Preferences"
	TryResizeFromPreference $top

	frame $top.il
	label $top.il.l1 -text "Lock when idle after"
	spinbox $top.il.s -from 0 -to 999 -increment 1 \
	    -justify right -width 4 \
	    -textvariable ::gorilla::dpd(IdleTimeout)
	label $top.il.l2 -text "minutes (0=never)"
	pack $top.il.l1 $top.il.s $top.il.l2 -side left -padx 3
	pack $top.il -side top -anchor w -pady 3

	checkbutton $top.si -text "Auto-save database immediately when changed" \
		-variable ::gorilla::dpd(SaveImmediately)
	pack $top.si -anchor w -side top -pady 3

	checkbutton $top.ver -text "Use Password Safe 3 format" \
	    -variable ::gorilla::dpd(defaultVersion) \
	    -onvalue 3 -offvalue 2
	pack $top.ver -anchor w -side top -pady 3

	checkbutton $top.uni -text "V2 Unicode support" -anchor w \
		-variable ::gorilla::dpd(IsUTF8)
	pack $top.uni -anchor w -side top -pady 3

	frame $top.stretch
	spinbox $top.stretch.spin -from 2048 -to 65535 -increment 256 \
	    -justify right -width 8 \
	    -textvariable ::gorilla::dpd(keyStretchingIterations)
	label $top.stretch.label -text "V3 key stretching iterations"
	pack $top.stretch.spin $top.stretch.label -side left -padx 3
	pack $top.stretch -anchor w -side top -pady 3

	Separator $top.sep -orient horizontal
	pack $top.sep -side top -fill x -pady 10

	frame $top.buts
	set but1 [button $top.buts.b1 -width 15 -text "OK" \
		-command "set ::gorilla::guimutex 1"]
	set but2 [button $top.buts.b2 -width 15 -text "Cancel" \
		-command "set ::gorilla::guimutex 2"]
	pack $but1 $but2 -side left -pady 10 -padx 20
	pack $top.buts

	bind $top.uni <Return> "set ::gorilla::guimutex 1"
	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::DestroyDatabasePreferencesDialog
    } else {
	wm deiconify $top
    }

    set oldGrab [grab current .]

    update idletasks
    raise $top
    focus $top.buts.b1
    grab $top

    set ::gorilla::guimutex 0
    vwait ::gorilla::guimutex

    if {$oldGrab != ""} {
	grab $oldGrab
    } else {
	grab release $top
    }

    wm withdraw $top

    if {$::gorilla::guimutex != 1} {
	return
    }

    set isModified 0

    if {$::gorilla::dpd(IdleTimeout) > 0} {
	set ::gorilla::dpd(LockOnIdleTimeout) 1
    } else {
	set ::gorilla::dpd(LockOnIdleTimeout) 0
    }

    foreach pref {IdleTimeout IsUTF8 LockOnIdleTimeout SaveImmediately} {
	set oldPref [$::gorilla::db getPreference $pref]
	if {![string equal $::gorilla::dpd($pref) $oldPref]} {
	    set isModified 1
	    $::gorilla::db setPreference $pref $::gorilla::dpd($pref)
	}
    }

    set newVersion $::gorilla::dpd(defaultVersion)

    if {$newVersion != $oldVersion} {
	$::gorilla::db setHeaderField 0 [list $newVersion 0]
	set isModified 1
    }

    $::gorilla::db configure -keyStretchingIterations \
	$::gorilla::dpd(keyStretchingIterations)

    if {$::gorilla::dpd(keyStretchingIterations) != $oldKeyStretchingIterations} {
	set isModified 1
    }

    if {$isModified} {
	MarkDatabaseAsDirty
    }

    ArrangeIdleTimeout
}

#
# ----------------------------------------------------------------------
# Move Node to a new Group
# ----------------------------------------------------------------------
#

proc gorilla::MoveTreeNode {node dest} {
    set nodedata [$::gorilla::widgets(tree) itemcget $node -data]
    set destdata [$::gorilla::widgets(tree) itemcget $dest -data]
    set nodetype [lindex $nodedata 0]

    if {$nodetype == "Root"} {
	tk_messageBox -parent . \
		-type ok -icon error -default ok \
		-title "Root Node Can Not Be Moved" \
		-message "The root node can not be moved."
	return
    }

    set desttype [lindex $destdata 0]

    if {$desttype == "RootNode"} {
	set destgroup ""
    } else {
	set destgroup [lindex $destdata 1]
    }

    #
    # Move a Login
    #

    if {$nodetype == "Login"} {
	set rn [lindex $nodedata 1]
	$::gorilla::db setFieldValue $rn 2 $destgroup
	$::gorilla::widgets(tree) delete $node
	AddRecordToTree $rn
	MarkDatabaseAsDirty
	return
    }

    #
    # Moving a group to its immediate parent does not have any effect
    #

    if {$dest == [$::gorilla::widgets(tree) parent $node]} {
	return
    }
    
    #
    # When we are moving a group, make sure that destination is not a
    # child of this group
    #

    set destiter $dest
    while {$destiter != "RootNode"} {
	if {$destiter == $node} {
	    break
	}
	set destiter [$::gorilla::widgets(tree) parent $destiter]
    }

    if {$destiter != "RootNode" || $node == "RootNode"} {
	tk_messageBox -parent . \
		-type ok -icon error -default ok \
		-title "Can Not Move Node" \
		-message "Can not move a group to a subgroup\
		of itself."
	return
    }

    #
    # Move recursively
    #

    MoveTreeNodeRek $node [pwsafe::db::splitGroup $destgroup]
    MarkDatabaseAsDirty
}

#
# Moves the children of tree node to the newParents group
#

proc gorilla::MoveTreeNodeRek {node newParents} {
    set nodedata [$::gorilla::widgets(tree) itemcget $node -data]
    set nodename [$::gorilla::widgets(tree) itemcget $node -text]

    lappend newParents $nodename
    set newParentName [pwsafe::db::concatGroups $newParents]
    set newParentNode [AddGroupToTree $newParentName]

    foreach child [$::gorilla::widgets(tree) nodes $node] {
	set childdata [$::gorilla::widgets(tree) itemcget $child -data]
	set childtype [lindex $childdata 0]

	if {$childtype == "Login"} {
	    set rn [lindex $childdata 1]
	    $::gorilla::db setFieldValue $rn 2 $newParentName
	    $::gorilla::widgets(tree) delete $child
	    AddRecordToTree $rn
	} else {
	    MoveTreeNodeRek $child $newParents
	}
    }

    set oldGroupName [lindex $nodedata 1]
    unset ::gorilla::groupNodes($oldGroupName)
    $::gorilla::widgets(tree) itemconfigure $newParentNode \
	    -open [$::gorilla::widgets(tree) itemcget $node -open]
    $::gorilla::widgets(tree) delete $node
}

#
# ----------------------------------------------------------------------
# Rebuild Tree
# ----------------------------------------------------------------------
#

proc gorilla::AddAllRecordsToTree {} {
    foreach rn [$::gorilla::db getAllRecordNumbers] {
	AddRecordToTree $rn
    }
}

proc gorilla::AddRecordToTree {rn} {
    if {[$::gorilla::db existsField $rn 2]} {
	set groupName [$::gorilla::db getFieldValue $rn 2]
    } else {
	set groupName ""
    }

    set parentNode [AddGroupToTree $groupName]

    if {[$::gorilla::db existsField $rn 3]} {
	set title [$::gorilla::db getFieldValue $rn 3]
    } else {
	set title ""
    }

    if {[$::gorilla::db existsField $rn 4]} {
	append title " \[" [$::gorilla::db getFieldValue $rn 4] "\]"
    }

    #
    # Insert the new login in alphabetical order, after all groups
    #

    set childNodes [$::gorilla::widgets(tree) nodes $parentNode]

    for {set i 0} {$i < [llength $childNodes]} {incr i} {
	set childNode [lindex $childNodes $i]
	set childData [$::gorilla::widgets(tree) itemcget $childNode -data]
	if {[lindex $childData 0] != "Login"} {
	    continue
	}

	set childName [$::gorilla::widgets(tree) itemcget $childNode -text]
	if {[string compare $title $childName] < 0} {
	    break
	}
    }

    if {$i >= [llength $childNodes]} {
	set i "end"
    }

    set nodename "node[incr ::gorilla::uniquenodeindex]"
    $::gorilla::widgets(tree) insert $i $parentNode $nodename \
	    -open 0 -drawcross never \
	    -image $::gorilla::images(login) \
	    -text $title \
	    -data [list Login $rn]

    return $nodename
}

proc gorilla::AddGroupToTree {groupName} {
    if {[info exists ::gorilla::groupNodes($groupName)]} {
	set parentNode $::gorilla::groupNodes($groupName)
    } elseif {$groupName == ""} {
	set parentNode "RootNode"
    } else {
	set parentNode "RootNode"
	set partialGroups [list]
	foreach group [pwsafe::db::splitGroup $groupName] {
	    lappend partialGroups $group
	    set partialGroupName [pwsafe::db::concatGroups $partialGroups]
	    if {[info exists ::gorilla::groupNodes($partialGroupName)]} {
		set parentNode $::gorilla::groupNodes($partialGroupName)
	    } else {
		set childNodes [$::gorilla::widgets(tree) nodes $parentNode]
		
		#
		# Insert group in alphabetical order, before all logins
		#

		for {set i 0} {$i < [llength $childNodes]} {incr i} {
		    set childNode [lindex $childNodes $i]
		    set childData [$::gorilla::widgets(tree) itemcget $childNode -data]
		    if {[lindex $childData 0] != "Group"} {
			break
		    }

		    set childName [$::gorilla::widgets(tree) itemcget $childNode -text]
		    if {[string compare $group $childName] < 0} {
			break
		    }
		}
		
		if {$i >= [llength $childNodes]} {
		    set i "end"
		}
		
		set nodename "node[incr ::gorilla::uniquenodeindex]"
		
		$::gorilla::widgets(tree) insert $i $parentNode $nodename \
			-open 0 -drawcross auto \
			-image $::gorilla::images(group) \
			-text $group \
			-data [list Group $partialGroupName]
		
		set parentNode $nodename
		set ::gorilla::groupNodes($partialGroupName) $nodename
	    }
	}
    }

    return $parentNode
}

#
# ----------------------------------------------------------------------
# Preferences Dialog
# ----------------------------------------------------------------------
#

proc gorilla::DestroyPreferencesDialog {} {
    set ::gorilla::guimutex 2
}

proc gorilla::PreferencesDialog {} {
    ArrangeIdleTimeout

    set top .preferencesDialog

    foreach {pref default} {clearClipboardAfter 0 \
	    defaultVersion 3 \
	    doubleClickAction nothing \
	    exportAsUnicode 0 \
	    exportFieldSeparator "," \
	    exportIncludeNotes 0 \
	    exportIncludePassword 0 \
	    exportShowWarning 1 \
	    idleTimeoutDefault 5 \
	    keepBackupFile 0 \
	    lruSize 10 \
	    lockDatabaseAfter 0 \
	    rememberGeometries 1 \
	    saveImmediatelyDefault 0 \
	    unicodeSupport 1} {
	if {[info exists ::gorilla::preference($pref)]} {
	    set ::gorilla::prefTemp($pref) $::gorilla::preference($pref)
	} else {
	    set ::gorilla::prefTemp($pref) $default
	}
    }

    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top
	TryResizeFromPreference $top
	wm title $top "Preferences"

	NoteBook $top.nb

	#
	# First NoteBook tab: general preferences
	#

	set gpf [$top.nb insert end gpf -text "General"]

	frame $gpf.cc
	label $gpf.cc.l1 -text "Clear clipboard after"
	spinbox $gpf.cc.s -from 0 -to 999 -increment 1 \
		-justify right -width 4 \
		-textvariable ::gorilla::prefTemp(clearClipboardAfter)
	label $gpf.cc.l2 -text "seconds (0=never)"
	pack $gpf.cc.l1 $gpf.cc.s $gpf.cc.l2 -side left -padx 3
	pack $gpf.cc -side top -anchor w -pady 3

	frame $gpf.lru
	label $gpf.lru.l1 -text "Remember"
	spinbox $gpf.lru.s -from 0 -to 32 -increment 1 \
		-justify right -width 4 \
		-textvariable ::gorilla::prefTemp(lruSize)
	label $gpf.lru.l2 -text "database names"
	button $gpf.lru.c -width 10 -text "Clear" \
		-command "set ::gorilla::guimutex 3"
	pack $gpf.lru.l1 $gpf.lru.s $gpf.lru.l2 -side left -padx 3
	pack $gpf.lru.c -side left -padx 10
	pack $gpf.lru -side top -anchor w -pady 3

	labelframe $gpf.dca -text "When double clicking a login ..."
	radiobutton $gpf.dca.cp -text "Copy password to clipboard" \
		-variable ::gorilla::prefTemp(doubleClickAction) \
		-value "copyPassword" -anchor w
	radiobutton $gpf.dca.ed -text "Edit login" \
		-variable ::gorilla::prefTemp(doubleClickAction) \
		-value "editLogin" -anchor w
	radiobutton $gpf.dca.nop -text "Do nothing" \
		-variable ::gorilla::prefTemp(doubleClickAction) \
		-value "nothing" -anchor w
	pack $gpf.dca.cp $gpf.dca.ed $gpf.dca.nop -side top -anchor w
	pack $gpf.dca -side top -pady 3 -fill x -expand yes

	checkbutton $gpf.bu -text "Backup database on save" \
		-variable ::gorilla::prefTemp(keepBackupFile)
	pack $gpf.bu -side top -anchor w -pady 3

	checkbutton $gpf.geo -text "Remember sizes of dialog boxes" \
		-variable ::gorilla::prefTemp(rememberGeometries)
	pack $gpf.geo -side top -anchor w -pady 3

	#
	# Second NoteBook tab: database defaults
	#

	set dpf [$top.nb insert end dpf -text "Defaults"]

	frame $dpf.il
	label $dpf.il.l1 -text "Lock when idle after"
	spinbox $dpf.il.s -from 0 -to 999 -increment 1 \
	    -justify right -width 4 \
	    -textvariable ::gorilla::prefTemp(idleTimeoutDefault)
	label $dpf.il.l2 -text "minutes (0=never)"
	pack $dpf.il.l1 $dpf.il.s $dpf.il.l2 -side left -padx 3
	pack $dpf.il -side top -anchor w -pady 3

	checkbutton $dpf.si -text "Auto-save database immediately when changed" \
	        -variable ::gorilla::prefTemp(saveImmediatelyDefault)
	pack $dpf.si -side top -anchor w -pady 3

	checkbutton $dpf.ver -text "Use Password Safe 3 format" \
	    -variable ::gorilla::prefTemp(defaultVersion) \
	    -onvalue 3 -offvalue 2
	pack $dpf.ver -side top -anchor w -pady 3

	checkbutton $dpf.uni -text "V2 Unicode support" \
		-variable ::gorilla::prefTemp(unicodeSupport)
	pack $dpf.uni -side top -anchor w -pady 3

	label $dpf.note -justify center -anchor w -wraplen 300 \
		-text "Note: these defaults will be applied to\
		new databases. To change a setting for an existing\
		database, go to \"Database Preferences\" in the \"Manage\"\
		menu."
	pack $dpf.note -side bottom -anchor center -pady 3

	#
	# Third NoteBook tab: export preferences
	#

	set epf [$top.nb insert end epf -text "Export"]

	checkbutton $epf.password -text "Include password field" \
	    -variable ::gorilla::prefTemp(exportIncludePassword) \
	    -anchor w
	pack $epf.password -anchor w -side top -pady 3

	checkbutton $epf.notes -text "Include \"Notes\" field" \
	    -variable ::gorilla::prefTemp(exportIncludeNotes) \
	    -anchor w
	pack $epf.notes -anchor w -side top -pady 3

	checkbutton $epf.unicode -text "Save as Unicode text file" \
	    -variable ::gorilla::prefTemp(exportAsUnicode) \
	    -anchor w
	pack $epf.unicode -anchor w -side top -pady 3

	LabelEntry $epf.separator -label "Field separator" \
	    -textvariable ::gorilla::prefTemp(exportFieldSeparator) \
	    -labelwidth 16 -width 4 -labelanchor w
	pack $epf.separator -anchor w -side top -pady 3

	checkbutton $epf.warning -text "Show security warning" \
	    -variable ::gorilla::prefTemp(exportShowWarning) \
	    -anchor w
	pack $epf.warning -anchor w -side top -pady 3

	#
	# End of NoteBook tabs
	#

	$top.nb compute_size
	$top.nb raise gpf
	pack $top.nb -fill both -expand yes -pady 10

	#
	# Bottom
	#

	# Separator $top.sep -orient horizontal
	# pack $top.sep -side top -fill x -pady 10

	frame $top.buts
	set but1 [button $top.buts.b1 -width 15 -text "OK" \
		-command "set ::gorilla::guimutex 1"]
	set but2 [button $top.buts.b2 -width 15 -text "Cancel" \
		-command "set ::gorilla::guimutex 2"]
	pack $but1 $but2 -side left -pady 10 -padx 20
	pack $top.buts

	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::DestroyPreferencesDialog
    } else {
	wm deiconify $top
    }

    set oldGrab [grab current .]

    update idletasks
    raise $top
    focus $top.buts.b1
    grab $top

    while {42} {
	ArrangeIdleTimeout
	set ::gorilla::guimutex 0
	vwait ::gorilla::guimutex

	if {$::gorilla::guimutex == 1} {
	    break
	} elseif {$::gorilla::guimutex == 2} {
	    break
	} elseif {$::gorilla::guimutex == 3} {
	    set ::gorilla::preference(lru) [list]
	}
    }

    if {$oldGrab != ""} {
	grab $oldGrab
    } else {
	grab release $top
    }

    wm withdraw $top

    if {$gorilla::guimutex != 1} {
	return
    }

    foreach pref {clearClipboardAfter \
	    defaultVersion \
	    doubleClickAction \
	    exportAsUnicode \
	    exportFieldSeparator \
	    exportIncludeNotes \
	    exportIncludePassword \
	    exportShowWarning \
	    idleTimeoutDefault \
	    keepBackupFile \
	    lruSize \
	    rememberGeometries \
	    saveImmediatelyDefault \
	    unicodeSupport} {
	set ::gorilla::preference($pref) $::gorilla::prefTemp($pref)
    }
}

proc gorilla::Preferences {} {
    PreferencesDialog
}

#
# ----------------------------------------------------------------------
# Load Preferences
# ----------------------------------------------------------------------
#

proc gorilla::LoadPreferencesFromRegistry {} {
    if {![info exists ::tcl_platform(platform)] || \
	    $::tcl_platform(platform) != "windows" || \
	    [catch {package require registry}]} {
	return 0
    }

    set key {HKEY_CURRENT_USER\Software\FPX\Password Gorilla}

    if {[catch {registry values $key}]} {
	return 0
    }

    if {![regexp {Revision: ([0-9.]+)} $::gorillaVersion dummy revision]} {
	set revision "<unmatchable>"
    }

    if {[llength [registry values $key revision]] == 1} {
	set prefsRevision [registry get $key revision]
    } else {
	set prefsRevision "<unknown>"
    }

    if {[llength [registry values $key lru]] == 1} {
	set ::gorilla::preference(lru) [registry get $key lru]
    }

    foreach {pref type} {caseSensitiveFind boolean \
	    clearClipboardAfter integer \
	    defaultVersion integer \
	    doubleClickAction ascii \
	    exportAsUnicode boolean \
	    exportFieldSeparator ascii \
	    exportIncludeNotes boolean \
	    exportIncludePassword boolean \
	    exportShowWarning boolean \
	    findInAny boolean \
	    findInNotes boolean \
	    findInPassword boolean \
	    findInTitle boolean \
	    findInURL boolean \
	    findInUsername boolean \
	    findThisText ascii \
	    idleTimeoutDefault integer \
	    keepBackupFile boolean \
	    lruSize integer \
	    rememberGeometries boolean \
	    saveImmediatelyDefault boolean \
	    unicodeSupport integer} {
	if {[llength [registry values $key $pref]] == 1} {
	    set value [registry get $key $pref]
	    if {[string is $type $value]} {
		set ::gorilla::preference($pref) $value
	    }
	}
    }

    if {[info exists ::gorilla::preference(rememberGeometries)] && \
	    $::gorilla::preference(rememberGeometries)} {
	foreach value [registry values $key geometry,*] {
	    set data [registry get $key $value]
	    if {[scan $data "%dx%d" width height] == 2} {
		set ::gorilla::preference($value) "${width}x${height}"
	    }
	}
    }

    #
    # If the revision numbers of our preferences don't match, forget
    # about window geometries, as they might have changed.
    #

    if {![string equal $revision $prefsRevision]} {
	foreach geo [array names ::gorilla::preference geometry,*] {
	    unset ::gorilla::preference($geo)
	}
    }

    return 1
}

proc gorilla::LoadPreferencesFromRCFile {} {
    if {[info exists ::gorilla::preference(rc)]} {
	set fileName $::gorilla::preference(rc)
    } else {
	if {[info exists ::env(HOME)] && [file isdirectory $::env(HOME)]} {
	    set homeDir $::env(HOME)
	} else {
	    set homeDir "~"
	}

	#
	# On the Mac, use $HOME/Library/Preferences/gorilla.rc
	# Elsewhere, use $HOME/.gorillarc
	#

	if {[info exists ::tcl_platform(platform)] && \
		$::tcl_platform(platform) == "macintosh" && \
		[file isdirectory [file join $homeDir "Library" "Preferences"]]} {
	    set fileName [file join $homeDir "Library" "Preferences" "gorilla.rc"]
	} else {
	    set fileName [file join $homeDir ".gorillarc"]
	}
    }

    if {![regexp {Revision: ([0-9.]+)} $::gorillaVersion dummy revision]} {
	set revision "<unmatchable>"
    }

    set prefsRevision "<unknown>"

    if {[catch {
	set f [open $fileName]
    }]} {
	return 0
    }

    while {![eof $f]} {
	set line [string trim [gets $f]]
	if {[string index $line 0] == "#"} {
	    continue
	}

	if {[set index [string first "=" $line]] < 1} {
	    continue
	}

	set pref [string trim [string range $line 0 [expr {$index-1}]]]
	set value [string trim [string range $line [expr {$index+1}] end]]

	if {[string index $value 0] == "\""} {
	    set i 1
	    set prefValue ""

	    while {$i < [string length $value]} {
		set c [string index $value $i]
		if {$c == "\\"} {
		    set c [string index $value [incr i]]
		    switch -exact -- $c {
			t {
			    set d "\t"
			}
			default {
			    set d $c
			}
		    }
		    append prefValue $c
		} elseif {$c == "\""} {
		    break
		} else {
		    append prefValue $c
		}
		incr i
	    }

	    set value $prefValue
	}

	switch -glob -- $pref {
	    clearClipboardAfter -
	    defaultVersion {
		if {[string is integer $value]} {
		    if {$value >= 0} {
			set ::gorilla::preference($pref) $value
		    }
		}
	    }
	    doubleClickAction {
		set ::gorilla::preference($pref) $value
	    }
	    caseSensitiveFind -
	    exportAsUnicode -
	    exportIncludeNotes -
	    exportIncludePassword -
	    exportShowWarning -
	    findInAny -
	    findInNotes -
	    findInPassword -
	    findInTitle -
	    findInURL -
	    findInUsername {
		if {[string is boolean $value]} {
		    set ::gorilla::preference($pref) $value
		}
	    }
	    exportFieldSeparator {
		if {[string length $value] == 1 && \
			$value != "\"" && $value != "\\"} {
		    set ::gorilla::preference($pref) $value
		}
	    }
	    findThisText {
		set ::gorilla::preference($pref) $value
	    }
	    idleTimeoutDefault {
		if {[string is integer $value]} {
		    if {$value >= 0} {
			set ::gorilla::preference($pref) $value
		    }
		}
	    }
	    keepBackupFile {
		if {[string is boolean $value]} {
		    set ::gorilla::preference($pref) $value
		}
	    }
	    lru {
		lappend ::gorilla::preference($pref) $value
	    }
	    lruSize {
		if {[string is integer $value]} {
		    set ::gorilla::preference($pref) $value
		}
	    }
	    rememberGeometries {
		if {[string is boolean $value]} {
		    set ::gorilla::preference($pref) $value
		}
	    }
	    revision {
		set prefsRevision $value
	    }
	    saveImmediatelyDefault {
		if {[string is boolean $value]} {
		    set ::gorilla::preference($pref) $value
		}
	    }
	    unicodeSupport {
		if {[string is integer $value]} {
		    set ::gorilla::preference($pref) $value
		}
	    }
	    geometry,* {
		if {[scan $value "%dx%d" width height] == 2} {
		    set ::gorilla::preference($pref) "${width}x${height}"
		}
	    }
	}
    }

    #
    # If the revision numbers of our preferences don't match, forget
    # about window geometries, as they might have changed.
    #

    if {![string equal $revision $prefsRevision]} {
	foreach geo [array names ::gorilla::preference geometry,*] {
	    unset ::gorilla::preference($geo)
	}
    }

    catch {close $f}
    return 1
}

proc gorilla::LoadPreferences {} {
    if {[info exists ::gorilla::preference(norc)] && \
	    $::gorilla::preference(norc)} {
	return 0
    }

    if {![info exists ::gorilla::preference(rc)] && \
	    [LoadPreferencesFromRegistry]} {
	return 1
    } elseif {[LoadPreferencesFromRCFile]} {
	return 1
    }

    return 0
}

#
# ----------------------------------------------------------------------
# Save Preferences
# ----------------------------------------------------------------------
#

proc gorilla::SavePreferencesToRegistry {} {
    if {![info exists ::tcl_platform(platform)] || \
	    $::tcl_platform(platform) != "windows" || \
	    [catch {package require registry}]} {
	return 0
    }

    set key {HKEY_CURRENT_USER\Software\FPX\Password Gorilla}

    if {![regexp {Revision: ([0-9.]+)} $::gorillaVersion dummy revision]} {
	set revision "<unknown>"
    }

    registry set $key revision $revision sz

    #
    # Note: findInText omitted on purpose. It might contain a password.
    #

    foreach {pref type} {caseSensitiveFind dword \
	    clearClipboardAfter dword \
	    defaultVersion dword \
	    doubleClickAction sz \
	    exportAsUnicode dword \
	    exportFieldSeparator sz \
	    exportIncludeNotes dword \
	    exportIncludePassword dword \
	    exportShowWarning dword \
	    findInAny dword \
	    findInNotes dword \
	    findInPassword dword \
	    findInTitle dword \
	    findInURL dword \
	    findInUsername dword \
	    idleTimeoutDefault dword \
	    keepBackupFile dword \
	    lruSize dword \
	    rememberGeometries dword \
	    saveImmediatelyDefault dword \
	    unicodeSupport dword} {
	if {[info exists ::gorilla::preference($pref)]} {
	    registry set $key $pref $::gorilla::preference($pref) $type
	}
    }

    if {[info exists ::gorilla::preference(lru)]} {
	if {[info exists ::gorilla::preference(lruSize)]} {
	    set lruSize $::gorilla::preference(lruSize)
	} else {
	    set lruSize 10
	}

	if {[llength $::gorilla::preference(lru)] > $lruSize} {
	    set lru [lrange $::gorilla::preference(lru) 0 [expr {$lruSize-1}]]
	} else {
	    set lru $::gorilla::preference(lru)
	}

	registry set $key lru $lru multi_sz
    }

    if {![info exists ::gorilla::preference(rememberGeometries)] || \
	    $::gorilla::preference(rememberGeometries)} {
	foreach top [array names ::gorilla::toplevel] {
	    if {[scan [wm geometry $top] "%dx%d" width height] == 2} {
		registry set $key "geometry,$top" "${width}x${height}"
	    }
	}
    } elseif {[info exists ::gorilla::preference(rememberGeometries)] && \
	    !$::gorilla::preference(rememberGeometries)} {
	foreach value [registry values $key geometry,*] {
	    registry delete $key $value
	}
    }

    return 1
}

proc gorilla::SavePreferencesToRCFile {} {
    if {[info exists ::gorilla::preference(rc)]} {
	set fileName $::gorilla::preference(rc)
    } else {
	if {[info exists ::env(HOME)] && [file isdirectory $::env(HOME)]} {
	    set homeDir $::env(HOME)
	} else {
	    set homeDir "~"
	}

	#
	# On the Mac, use $HOME/Library/Preferences/gorilla.rc
	# Elsewhere, use $HOME/.gorillarc
	#

	if {[info exists ::tcl_platform(platform)] && \
		$::tcl_platform(platform) == "macintosh" && \
		[file isdirectory [file join $homeDir "Library" "Preferences"]]} {
	    set fileName [file join $homeDir "Library" "Preferences" "gorilla.rc"]
	} else {
	    set fileName [file join $homeDir ".gorillarc"]
	}
    }

    if {[catch {
	set f [open $fileName "w"]
    }]} {
	return 0
    }

    if {![regexp {Revision: ([0-9.]+)} $::gorillaVersion dummy revision]} {
	set revision "<unknown>"
    }

    puts $f "revision=$revision"

    #
    # Note: findInText omitted on purpose. It might contain a password.
    #

    foreach pref {caseSensitiveFind \
	    clearClipboardAfter \
	    defaultVersion \
	    doubleClickAction \
	    exportAsUnicode \
	    exportIncludeNotes \
	    exportIncludePassword \
	    exportShowWarning \
	    findInAny \
	    findInNotes \
	    findInPassword \
	    findInTitle \
	    findInURL \
	    findInUsername \
	    idleTimeoutDefault \
	    keepBackupFile \
	    lruSize \
	    rememberGeometries \
	    saveImmediatelyDefault \
	    unicodeSupport} {
	if {[info exists ::gorilla::preference($pref)]} {
	    puts $f "$pref=$::gorilla::preference($pref)"
	}
    }

    if {[info exists ::gorilla::preference(exportFieldSeparator)]} {
	puts $f "exportFieldSeparator=\"[string map {\t \\t} $::gorilla::preference(exportFieldSeparator)]\""
    }

    if {[info exists ::gorilla::preference(lru)]} {
	if {[info exists ::gorilla::preference(lruSize)]} {
	    set lruSize $::gorilla::preference(lruSize)
	} else {
	    set lruSize 10
	}

	if {[llength $::gorilla::preference(lru)] > $lruSize} {
	    set lru [lrange $::gorilla::preference(lru) 0 [expr {$lruSize-1}]]
	} else {
	    set lru $::gorilla::preference(lru)
	}

	foreach file $lru {
	    puts $f "lru=\"[string map {\\ \\\\ \" \\\"} $file]\""
	}
    }

    if {![info exists ::gorilla::preference(rememberGeometries)] || \
	    $::gorilla::preference(rememberGeometries)} {
	foreach top [array names ::gorilla::toplevel] {
	    if {[scan [wm geometry $top] "%dx%d" width height] == 2} {
		puts $f "geometry,$top=${width}x${height}"
	    }
	}
    }

    if {[catch {close $f}]} {
	return 0
    }

    return 1
}

proc gorilla::SavePreferences {} {
    if {[info exists ::gorilla::preference(norc)] && \
	    $::gorilla::preference(norc)} {
	return 0
    }

    if {![info exists ::gorilla::preference(rc)] && \
	    [SavePreferencesToRegistry]} {
	return 1
    } elseif {[SavePreferencesToRCFile]} {
	return 1
    }

    return 0
}

#
# ----------------------------------------------------------------------
# Miscellaneous
# ----------------------------------------------------------------------
#

proc gorilla::DestroyAboutDialog {} {
    ArrangeIdleTimeout
    set top .about
    catch {destroy $top}
    unset ::gorilla::toplevel($top)
}

proc gorilla::About {} {
    ArrangeIdleTimeout
    set top .about

    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top -bg "#ffffff"

	wm title $top "Password Gorilla"

	frame $top.top -bg "#ffffff"
	frame $top.top.pg -bg "#ffffff"
	label $top.top.pg.title -bg "#ffffff" -text "Password Gorilla"
	pack $top.top.pg.title -side top -fill x -pady 3

	if {![regexp {Revision: ([0-9.]+)} $::gorillaVersion dummy revision]} {
	    set revision "<unknown>"
	}

	label $top.top.pg.rev -bg "#ffffff" -text "Revision: $revision"
	pack $top.top.pg.rev -side top -fill x -padx 3

	label $top.top.pg.url -bg "#ffffff" \
	    -text "http://www.fpx.de/fp/Software/Gorilla/"
	pack $top.top.pg.url -side top -fill x -pady 10
	pack $top.top.pg -side left -fill x -expand yes

	label $top.top.splash -bg "#ffffff" \
	    -image $::gorilla::images(splash)
	pack $top.top.splash -side right
	pack $top.top -side top -fill both -expand yes

	Separator $top.topsep -orient horizontal
	pack $top.topsep -side top -fill x

	set midsection [frame $top.mid -bg "#ffffff"]

	set imgframe [frame $midsection.imgs -bg "#ffffff"]
	label $imgframe.lab \
	    -font {Helvetica 10 bold} -bg "#ffffff" \
	    -text "Copyright \u00a9 2005"
	label $imgframe.img -bg "#ffffff" \
	    -image $::gorilla::images(wfpxsm)
	label $imgframe.bot \
	    -font {Helvetica 10 bold} -bg "#ffffff" \
	    -text "Frank Pilhofer"
	label $imgframe.botbot \
	    -font {Helvetica 10 bold} -bg "#ffffff" \
	    -text "fp@fpx.de"
	pack $imgframe.lab $imgframe.img $imgframe.bot $imgframe.botbot -side top
	pack $imgframe -side left -padx 10 -pady 10

	Separator $midsection.sep -orient vertical
	pack $midsection.sep -side left -fill both

	set txtframe [frame $midsection.txt -bg "#ffffff"]
	label $txtframe.t1 -wraplength 450 -justify left \
	    -anchor w -bg "#ffffff" \
	    -text "Based on the \"Password Safe\" program, copyright\
	    \u00a9 1997-1998 by Counterpane Systems, now maintained\
	    as an Open Source project at\
	    http://passwordsafe.sourceforge.net/"
	label $txtframe.t2 -wraplength 450 -justify left \
	    -anchor w -bg "#ffffff" \
	    -text "Released under the GNU General Public License.\
	    Please read the file \"LICENSE.txt,\" or choose \"License\"\
	    from the \"Help\" menu, for more information."
	label $txtframe.t3 -wraplength 450 -justify left \
	    -anchor w -bg "#ffffff" \
	    -text "This software would not be possible without the\
	    excellent Open Source tools that it is based on. Uses\
	    Tcl/Tk, \[incr Tcl\], BWidget, and parts of tcllib. May\
	    use Tclkit. All packages are copyrighted by their\
	    respective authors and contributors, and released\
	    under BSD license."
	label $txtframe.t4 -wraplength 450 -justify left \
	    -anchor w -bg "#ffffff" \
	    -text "Gorilla artwork contributed by Andrew J. Sniezek."
	pack $txtframe.t1 $txtframe.t2 $txtframe.t3 $txtframe.t4 \
	    -side top -fill both -expand yes \
	    -padx 10 -pady 5
	pack $txtframe -side left -fill both

	pack $midsection -side top -fill both -expand yes

	Separator $top.botsep -orient horizontal
	pack $top.botsep -side top -fill x

	set botframe [frame $top.botframe -bg "#ffffff"]
	button $botframe.but -width 10 -text "OK" \
	    -command "gorilla::DestroyAboutDialog"
	pack $botframe.but
	pack $botframe -side top -fill x -pady 10

	bind $top <Return> "gorilla::DestroyAboutDialog"

	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW gorilla::DestroyAboutDialog
    } else {
	set botframe "$top.botframe"
    }

    update idletasks
    wm deiconify $top
    raise $top
    focus $botframe.but
    wm resizable $top 0 0
}

proc gorilla::Help {} {
    ArrangeIdleTimeout
    ShowTextFile .help "Using Password Gorilla" "help.txt"
}

proc gorilla::License {} {
    ArrangeIdleTimeout
    ShowTextFile .license "Password Gorilla License" "LICENSE.txt"
}

proc gorilla::DestroyTextFileDialog {top} {
    ArrangeIdleTimeout
    catch {destroy $top}
    unset ::gorilla::toplevel($top)
}

proc gorilla::ShowTextFile {top title fileName} {
    if {![info exists ::gorilla::toplevel($top)]} {
	toplevel $top

	wm title $top $title

	set sw [ScrolledWindow $top.sw -auto horizontal]
	set text [text $sw.text -relief sunken -width 80 -font {Courier}]
	$sw setwidget $text
	pack $sw -side top -fill both -expand yes

	set botframe [frame $top.botframe]
	set botbut [button $botframe.but -width 10 -text "Close" \
			-command "gorilla::DestroyTextFileDialog $top"]
	pack $botbut
	pack $botframe -side top -fill x -pady 10

	bind $top <Prior> "$text yview scroll -1 pages; break"
	bind $top <Next> "$text yview scroll 1 pages; break"
	bind $top <Up> "$text yview scroll -1 units"
	bind $top <Down> "$text yview scroll 1 units"
	bind $top <Home> "$text yview moveto 0"
	bind $top <End> "$text yview moveto 1"
	bind $top <Return> "gorilla::DestroyTextFileDialog $top"

	$text configure -state normal
	$text delete 1.0 end

	set filename [file join $::gorillaDir $fileName]
	if {[catch {
	    set file [open $filename]
	    $text insert 1.0 [read $file]
	    close $file
	}]} {
	    $text insert 1.0 "Oops: file not found: $fileName"
	}

	$text configure -state disabled

	set ::gorilla::toplevel($top) $top
	wm protocol $top WM_DELETE_WINDOW "gorilla::DestroyTextFileDialog $top"
    } else {
	set botframe "$top.botframe"
    }

    update idletasks
    wm deiconify $top
    raise $top
    focus $botframe.but
    wm resizable $top 0 0
}

proc gorilla::Reload {} {
    set ::gorilla::status "Reloading [file tail $::argv0] ..."
    update idletasks
    uplevel #0 {source $::argv0}
    set ::gorilla::status "Reloading Done."
}

proc gorilla::ToggleConsole {} {
    catch {console show}
}

proc gorilla::Refresh {} {
    set ::gorilla::status "Refreshing ..."
    update idletasks

    #
    # Delete "geometry" keys from registry
    #

    if {[info exists ::tcl_platform(platform)] && \
	    $::tcl_platform(platform) == "windows" && \
	    ![catch {package require registry}]} {
	set key {HKEY_CURRENT_USER\Software\FPX\Password Gorilla}
	foreach value [registry values $key geometry,*] {
	    registry delete $key $value
	}
    }

    #
    # Delete Geometry preferences
    #

    foreach geo [array names ::gorilla::preference geometry,*] {
	unset ::gorilla::preference($geo)
    }

    #
    # Destroy all (withdrawn) windows, so that they will be redrawn
    #

    if {[array exists ::gorilla::toplevel]} {
	foreach top [array names ::gorilla::toplevel] {
	    if {$top != "."} {
		destroy $top
	    }
	}
    }

    catch {unset ::gorilla::toplevel}

    #
    # Destroy GUI Tree and its state
    #

    catch {unset ::gorilla::groupNodes}
    catch {destroy $::gorilla::widgets(main)}

    #
    # Re-initialize main window and its data
    #

    InitGui

    if {[info exists ::gorilla::db]} {
	if {[info exists ::gorilla::fileName]} {
	    set fileName $::gorilla::fileName
	} else {
	    set fileName "<New Database>"
	}

	$::gorilla::widgets(tree) insert end root "RootNode" \
		-open 1 -drawcross auto \
		-image $::gorilla::images(group) \
		-text [file nativename $fileName] \
		-data [list Root]

	AddAllRecordsToTree
    }

    #
    # Okay, we are borne again
    #

    set ::gorilla::status "Refreshing Done."
}

proc gorilla::Exit {} {
    ArrangeIdleTimeout

    #
    # Protect against reentrancy, i.e., if the user clicks on the "X"
    # window manager decoration multiple times.
    #

    if {[info exists ::gorilla::exiting] && $::gorilla::exiting} {
	return
    }

    set ::gorilla::exiting 1

    #
    # If the current database was modified, give user a chance to think
    #

    if {$::gorilla::dirty} {
	set myParent [grab current .]

	if {$myParent == ""} {
	    set myParent "."
	}

	set answer [tk_messageBox -parent $myParent \
		-type yesnocancel -icon warning -default yes \
		-title "Save changes?" \
		-message "The current password database is modified.\
		Do you want to save the database?\n\
		\"Yes\" saves the database, and exits.\n\
		\"No\" discards all changes, and exits.\n\
		\"Cancel\" returns to the main menu."]
	if {$answer == "yes"} {
	    if {[info exists ::gorilla::fileName]} {
		if {![::gorilla::Save]} {
		    set ::gorilla::exiting 0
		}
	    } else {
		if {![::gorilla::SaveAs]} {
		    set ::gorilla::exiting 0
		}
	    }
	} elseif {$answer != "no"} {
	    set ::gorilla::exiting 0
	}
	if {!$::gorilla::exiting} {
	    return 0
	}
    }

    #
    # Save preferences
    #

    SavePreferences

    #
    # Clear the clipboard, if we were meant to do that sooner or later.
    #

    if {[info exists ::gorilla::clipboardClearId]} {
	after cancel $::gorilla::clipboardClearId
	ClearClipboard
    }

    #
    # Goodbye, cruel world
    #

    destroy .
    exit
}

#
# ----------------------------------------------------------------------
# Icons
# ----------------------------------------------------------------------
#

set ::gorilla::images(group) [image create photo -data "
R0lGODlhDQANAJEAANnZ2QAAAP//AP///yH5BAEAAAAALAAAAAANAA0AAAIyhI+pe0EJ4VuE
iFBC+EGyg2AHyZcAAKBQvgQAAIXyJQh2kOwg+BEiEhTCt4hAREQIHVIAOw==
"]

set ::gorilla::images(login) [image create photo -data "
R0lGODlhDQANAJEAANnZ2QAAAAD/AP///yH5BAEAAAAALAAAAAANAA0AAAI0hI+pS/EPACBI
vgQAAIXyJQAAKJQvAQBAoXwJAAAK5UsAAFAoXwIAgEL5EgAAFP8IPqZuAQA7
"]

set ::gorilla::images(wfpxsm) [image create photo -data "
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
"]

set ::gorilla::images(splash) [image create photo -data "
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
TIISU8PgCVGYQhWukIX+CwgAOw==

"]

#
# ----------------------------------------------------------------------
# Init
# ----------------------------------------------------------------------
#

proc usage {} {
    puts stdout "usage: $::argv0 \[Options\] \[<database>\]"
    puts stdout "  Options:"
    puts stdout "    --rc <name>   Use <name> as configuration file (not the Registry)."
    puts stdout "    --norc        Do not use a configuration file (or the Registry)."
    puts stdout "    <database>    Open <database> on startup."
}

if {$::gorilla::init == 0} {
    if {[string first "-norc" $argv0] != -1} {
	set ::gorilla::preference(norc) 1
    }

    set haveDatabaseToLoad 0
    set databaseToLoad ""

    set argc [llength $argv]

    for {set i 0} {$i < $argc} {incr i} {
	switch -- [lindex $argv $i] {
	    --norc -
	    -norc {
		set ::gorilla::preference(norc) 1
	    }
	    --rc -
	    -rc {
		if {$i+1 >= $argc} {
		    puts stderr "Error: [lindex $argv $i] needs a parameter."
		    exit 1
		}
		incr i
		set ::gorilla::preference(rc) [lindex $argv $i]
	    }
	    --help {
		usage
		exit 0
	    }
	    default {
		if {$haveDatabaseToLoad} {
		    usage
		    exit 0
		}
		set haveDatabaseToLoad 1
		set databaseToLoad [lindex $argv $i]
	    }
	}
    }


    gorilla::Init
    gorilla::LoadPreferences
    gorilla::InitGui
    set ::gorilla::init 1

    if {$haveDatabaseToLoad} {
	gorilla::Open $databaseToLoad
    } else {
	gorilla::Open
    }

    wm deiconify .
    raise .
    update

    set ::gorilla::status "Welcome to the Password Gorilla."
}
