Roland B. Roberts - When I originally started using
Oratcl, it included an application call wosql which provided a simple windowed interface to
Oracle. wosql included a logon dialog that I liked so much that I stole it :-). More recent versions os
Oratcl do not come with wosql, but below is my current version of the logon dialog widget. Although this is functional enough to use as is, it can almost certainly stand some improvement. From a purely stylistic standpoint, I originally wrote it using StudlyCapitalizedNames which I don't particularly like anymore. Even apart from that, if you have suggestions, enhancements or bug fixes, please let me know.
This should work with
Oratcl 3.x; it certainly works with
Oratcl 3.3. It
may work with
Oratcl 2.7; I don't know of anything incompatible with
Oratcl 2.7 or Tcl 8.0, but I haven't paid close enough attention to be sure. I will
not work with
Oratcl 4.x. Among other things, there are are references to
oramsg to retrieve the error message and that array has been removed in version 4.0. Working around that is probably trivial, but I don't have
Oracle 9 libraries for testing.
After sourcing the file below, you will call
::tkora::getSignOn callback to launch the dialog. Here is a sample extracted from one of my applications. If you run this from a shell window, or from
TkCon,
showLogon will be called with the
logon-handle, the handle used by
Oratcl's
oraopen call. You will then see the contents of your
Oracle signon (user, password, TNS name) and a message indicating the logon dialog was destroyed.
#!/bin/sh
#
# The following is a required on unix and harmless elsewhere
# *** DO NOT TOUCH THIS OR THE FOLLOWING LINE! *** \
exec wish $0 ${1+"$@"}
# Not all of these are required by everything, but...better to find
# out at the beginning that something is missing.
package require Tk
package require Oratcl
wm withdraw .
source logon.tcl
proc showLogon args {
foreach k $args {
puts "Oracle handle => $k"
}
foreach {k v} [array get ::tkora::logon::logon] {
puts "$k ==> $v"
}
}
# Fire up sign-on screen and wait.
set w [::tkora::logon::getSignOn ::showLogon]
tkwait window $w
puts "$w destroyed!"
And here is the file "logon.tcl" referenced above.
#
# Copyright © 2000-2002, FT/Interactive Data
# Copyright © 1998-1999, Muller Data Corporation
# Copyright © 1993?, Tom Poindexter <URL:http://www.nyx.net/~tpoindex/tcl.html>
# Roland B. Roberts <[email protected]>
#
#************************************************************************
# Copyright Info:
#
# This code is derived from wosql, distributed with Oratcl 2.7
#
#************************************************************************
#
# RCS Revision
# $Id: 4588,v 1.11 2002-11-15 09:02:26 jcw Exp $
# $Source: /home/kennykb/Tcl/wiki/cvsroot/twhist/4588,v $
#
# Required by old TRCS scripts
# $Header: /home/kennykb/Tcl/wiki/cvsroot/twhist/4588,v 1.11 2002-11-15 09:02:26 jcw Exp $
#
#
# Variables
# logon - array holding logon information after successful
# logon
# widget - name of the toplevel widget containing the login
# dialog
# usropt - dialog (display) options
# serverList - list of servers obtained from tnsnames.ora
#
# Procedures
# getSignOn - Oracle login dialog
# reconnect - reconnect using old (or specified) Oracle
# connect-string
# getServerList -
# getFile - Suck a whole file into a string
# showSignOnError - Update logon dialog to display Oracle error
# connect - called by getSignOn when user enters Oracle logon
# information. Retrieves dialog strings for actual
# Oracle logon attempt.
# connect_1 - called by connect to do "real" Oracle logon
# attempt.
#
# Description
#
# Only getSignOn and reconnect are intended to be public funcions.
#
# getSignOn displays a dialog for entering an Oracle username,
# password, and DB name. getSignOn takes a single argument, the name
# of a callback to be called when the signon completes successfully.
# It returns the name of the toplevel widget containing the entire
# signon dialog.
#
# reconnect takes a single optional argument which is a connect
# string of any form which is acceptable to oralogon with the default
# to reuse the previous connect string, the one generated by
# getSignOn in ::tkora::logon::logon(connect-string).
#
# usropt contains logon-dialog specific options. Eventually, I hope
# to change getSignOn to take Tcl-style options for the values in
# this array, something like "getSignon -title $text ...."
#
namespace eval ::tkora::logon {
# Holds actual logon toplevel widget name.
variable widget
# Eye candy
variable usropt
array set usropt [list title "MIPS Operations" \
iconname "MIPS/OPS" \
geometry 275x300]
# Cached copy of valid Oracle TNS names.
variable serverList
variable logon
# o s.m is the contains the message header, this is a message widget.
# o Menubutton and Button takes care of the labels on the buttons.
# o Label takes care of the text (entry) labels.
# o Entry takes care of the text entry widgets.
# o s.err is the error message; this is a message widget.
option add *MIPSLogon*s.m*font {-*-helvetica-bold-o-*-*-20-*-*-*-*-*-*-*}
option add *MIPSLogon*Menubutton*font {-*-helvetica-bold-*-*-*-14-*-*-*-*-*-*-*}
option add *MIPSLogon*Button*font {-*-helvetica-bold-*-*-*-14-*-*-*-*-*-*-*}
option add *MIPSLogon*Label*font {-*-helvetica-bold-o-*-*-14-*-*-*-*-*-*-*}
option add *MIPSLogon*Entry*font {-*-helvetica-normal-*-*-*-14-*-*-*-*-*-*-*}
option add *MIPSLogon*s.err*font {-*-helvetica-bold-*-*-*-12-*-*-*-*-*-*-*}
option add *MIPSLogon*s.err*foreground red
# Should make a more OS-friendly name here.
catch {option readfile [file join $env(HOME) .mipslogon]}
}
# getServerlist
# figure out what Oracle servers are available
#
proc ::tkora::logon::getServerList {} {
global env
set serverList ""
# On Windows 95, look in these locations, depending on what version
# of the Oracle libraries you have.
foreach {ffile fremote} [list $env(ORACLE_HOME)/network/admin/tnsnames.ora 1 \
$env(ORACLE_HOME)/net80/admin/tnsnames.ora 1] {
set lines ""
if {[file isfile $ffile]} {
set ifile [split [::tkora::logon::getFile $ffile] \n]
foreach line $ifile {
if {[regexp -nocase {sid = ([a-z_]*)} $line m s1]} {
if {$fremote} {
lappend serverList @$s1
} else {
set s1 [lindex [split $s1 :] 0]
lappend serverList $s1
}
}
}
}
}
foreach {ffile fremote} [list /etc/oratab 0 \
/etc/sqlnet 1 \
$env(HOME)/.sqlnet 1 \
$env(HOME)/.tnsnames.ora 1] {
set lines ""
if {[file isfile $ffile]} {
set ifile [split [::tkora::logon::getFile $ffile] \n]
foreach line $ifile {
if {[regexp -nocase "(^\[a-z_]\[^ \t\r]*).*$" $line m s1]} {
if {$fremote} {
lappend serverList @$s1
} else {
set s1 [lindex [split $s1 :] 0]
lappend serverList $s1
}
}
}
}
}
# nothing found? put in serverList what names should look like
if {[llength $serverList] == 0} {
lappend serverList "(localdb)"
lappend serverList "(@remote_alias)"
lappend serverList "(@T:host:remotedb)"
}
return $serverList
}
# getSignOn
# The first window, get logon info and trys to connect to the server
#
proc ::tkora::logon::getSignOn {callback} {
global env
global tcl_platform
variable logon
variable serverList
variable widget
variable usropt
# set ORACLE_HOME if not already set
set ora_home [lsearch [array names env] ORACLE_HOME]
if {$ora_home == -1} {
set ora_home ""
if {[string compare $tcl_platform(platform) windows] == 0} {
# Okay, I really only know that this works for Win2k. Under
# Win95, the environment variable ORACLE_HOME was set.
set ora_home [registry get "HKEY_LOCAL_MACHINE\\SOFTWARE\\ORACLE" ORACLE_HOME]
} else {
catch {set ora_home [exec ypcat passwd | egrep ^oracle: ]}
if {[string length $ora_home] > 0} {
set ora_home [lindex [split $ora_home :] 5]
} else {
catch {set ora_home [exec egrep ^oracle: < /etc/passwd ]}
if {[string length $ora_home] > 0} {
set ora_home [lindex [split $ora_home :] 5]
} else {
set ora_home ""
}
}
}
set env(ORACLE_HOME) $ora_home
} else {
set ora_home $env(ORACLE_HOME)
}
# Get valid servers from various files
if {![info exists serverList]} {
set serverList [getServerList]
} elseif {[llength serverList] == 0} {
set serverList [getServerList]
}
# Maybe someone else likes the name ".logon" for their logon screen
set mytop .logon
for {set i 0} {$i < 100} {incr i} {
if [catch {toplevel $mytop -class MIPSLogon} widget] {
set mytop .logon$i
} else {
break;
}
}
wm title $widget $usropt(title)
wm iconname $widget $usropt(iconname)
wm geometry $widget $usropt(geometry)
frame $widget.s
message $widget.s.m -justify center -text "Oracle Server Sign-On" -aspect 2000
frame $widget.s.i
entry $widget.s.i.uid -relief sunken -width 12
label $widget.s.i.id -text " User Id" -anchor e
frame $widget.s.p
entry $widget.s.p.pw -relief sunken -width 12 -show * -exportselection false
label $widget.s.p.p -text " Password" -anchor e
frame $widget.s.s
entry $widget.s.s.ser -relief sunken -width 12
menubutton $widget.s.s.s -text " Servers " -anchor e -menu $widget.s.s.s.m \
-relief raised
menu $widget.s.s.s.m
foreach s $serverList {
$widget.s.s.s.m add command -label $s \
-command "$widget.s.s.ser delete 0 end; $widget.s.s.ser insert 0 $s "
}
if {[lsearch [array names env] ORACLE_SID] >= 0} {
$widget.s.s.s.m add command -label $env(ORACLE_SID)
}
message $widget.s.err -text " " -justify center -aspect 500
frame $widget.s.b
button $widget.s.b.ok -text "Sign on" -command "::tkora::logon::connect $callback"
button $widget.s.b.can -text "Cancel" -command "destroy $widget"
pack $widget.s.b -side bottom -fill x -expand 0
pack $widget.s.b.ok $widget.s.b.can -side left -fill x -expand 1
pack $widget.s -side top -fill both -expand 1
pack $widget.s.m -side top -fill x -pady 5
pack $widget.s.i.uid -side right -expand 1 -padx 20
pack $widget.s.i.id -side left
pack $widget.s.i -side top -pady 10 -anchor e
pack $widget.s.p.pw -side right -expand 1 -padx 20
pack $widget.s.p.p -side left
pack $widget.s.p -side top -pady 10 -anchor e
pack $widget.s.err -side top -fill both
pack $widget.s.s.ser -side right -expand 1 -padx 20
pack $widget.s.s.s -side left
pack $widget.s.s -side bottom -pady 10 -anchor se -expand 1
$widget.s.i.uid delete 0 end
if [info exists logon(userid)] {
$widget.s.i.uid insert 0 $logon(userid)
$widget.s.i.uid selection range 0 end
}
$widget.s.s.ser delete 0 end
if [info exists logon(server)] {
$widget.s.s.ser insert 0 $logon(server)
} else {
$widget.s.s.ser insert 0 [lindex $serverList 0]
}
focus $widget.s.i.uid
bind $widget.s.i.uid <KeyPress-Return> "focus $widget.s.p.pw"
bind $widget.s.p.pw <KeyPress-Return> "$widget.s.b.ok invoke"
bind $widget.s.s.ser <KeyPress-Return> "$widget.s.b.ok invoke"
# allow Entry and Text to paste selections
bind Entry <ButtonRelease-2> {
set tk_s_rc [catch {set tk_s_s [selection get]} ]
if {$tk_s_rc == 0} {%W insert insert $tk_s_s}
}
return $widget
}
# getFile
# read a file, return contents as string
#
proc ::tkora::logon::getFile {afile} {
set contents {}
catch {
set fd [open $afile]
set contents [read $fd]
close $fd
}
return $contents
}
proc ::tkora::logon::showSignOnError {w1 w2} {
global oramsg
$w1 configure -text $oramsg(errortxt)
focus $w2
}
proc ::tkora::logon::connect {callback} {
variable widget
set userid [$widget.s.i.uid get]
set passwd [$widget.s.p.pw get]
set server [$widget.s.s.ser get]
::tkora::logon::connect_1 $userid $passwd $server $callback \
[list ::tkora::logon::showSignOnError \
${::tkora::logon::widget}.s.err ${::tkora::logon::widget}.s.p.pw]
}
# logon::connect_1
# Try a connection to the Oracle server.
#
proc ::tkora::logon::connect_1 {userid passwd server cbokay cbfail} {
global env
variable logon
variable widget
# Check for local DB specification
if {[string first @ $server] != 0} {
set oldsid $env(ORACLE_SID)
set env(ORACLE_SID) $server
set server ""
}
set retcode [catch {oralogon ${userid}/${passwd}${server}} lda]
if {$retcode == 0} {
destroy $widget
set dbh $lda
set logon(connect-string) ${userid}/${passwd}${server}
set logon(server) $server
set logon(userid) $userid
set logon(passwd) $passwd
eval $cbokay $dbh
} else {
if [info exists oldsid] {
set env(ORACLE_SID) $oldsid
}
eval $cbfail
}
}
proc ::tkora::logon::reconnect {{connect-string {}}} {
variable logon
if {[string length ${connect-string}] == 0} {
set connect-string $logon(connect-string)
}
set retcode [catch {oralogon ${connect-string}} lda]
if {$retcode == 0} {
return $lda
} else {
return
}
}