#! /usr/bin/perl
eval 'exec /usr/bin/perl -S $0 ${1+"$@"}' if 0; # not running under some shell
BEGIN { $main::DEFA = "/usr/share/dbman"; }





 ####################################################################### 
######################################################################### 
###                                                                   ###
###   dbMan 0.1.0     (c) 1999-2000 by Milan Sorm <sorm@fi.muni.cz>   ###
###                                                                   ###
###   If you will plan to use dbMan you must respect all paragraphs   ###
###   GNU General Public License which is provided in file Copying    ###
###   or all paragraphs Artistic License listed in Artistic file.     ###
###                                                                   ###
#########################################################################
 ####################################################################### 






############################ GLOBAL DEFINITION ###############################

use strict;
use vars (
	'$DEFA',	# default value for $DEFAULT_DIR
	'$DEBUG',	# debugging mode - setting -w, log etc.
	'$DEFAULT_DIR',	# default directory for dbMan library (/usr/lib/dbman)
	'$ICONDIR',	# directory with icons - from @INC
	'$LANG', 	# current language ('en', 'cz' etc.)
	'$ORIG_0', 	# original $0 parameter
	'$OUTPUT_LEN_WARN', # num of chars for output buf. length exceeding
	'$VERNAME', 	# name + version
	'$VERSION',	# version of dbMan
	'$balloon', 	# balloon help - object
	'$bottom_line',	# bottom Frame (single or multi column line inside)
	'$close_icon', 	# foldex.xbm icon
	'$cmd_pattern',	# last searched /pattern for repeat / search in history
	'$command_line', # command line interface = TRUE or X interface = FALSE
	'$counting', 	# show counting rows in SQL output ?
	'$current_font', # current font for SQL output windows
	'$curses', 	# use curses in command line interface ?
	'$database_icon', # database.xbm icon
	'$dbi', 	# main DBI object
	'$dbname', 	# database name for connecting
	'$dbsys', 	# database driver name for connecting (e.g. Pg, Oracle)
	'$dont_buffer', # don't save query output to buffer - single query
	'$empty_null',	# show NULL values as empty string
	'$external_edit', # use external editor instead internal Tk editor
	'$full_sql_transaction', # use SQL92 commands for transactions
	'$hbox',	# history box in history window
	'$helpdir', 	# directory with help files for SQL - from @INC
	'$historyfile', # name of history file (default ~/.dbman-history)
	'$hlist',	# hiearchical list (HList) for Table Manager
	'$hwnd',	# history window
	'$last_sql_rows', # number of lines of last SQL output
	'$lastsaved',	# last saved SQL query to history buffer
	'$list',	# list of table listbox component
	'$list_filter',	# filter (RE) for list of tables
	'$ltables',	# list of tables window
	'$manager', 	# Table Manager window
	'$mylib', 	# library from which was dbMan executed
	'$ndbi_proxy', 	# use connection to dbman-proxy rather than DBD::Proxy
	'$number_of_sqlline_rows', # number of SQL lines in multiline SQL input 
	'$only_execute', # only execute command, not interface
	'$open_icon', 	# openfolder.xbm icon
	'$output', 	# output buffer
	'$plain_format', # use plain format rather than table format for outputs
	'$plugin_interface', # universal Plugin object for plugins connection
	'$pos_cmd_search', # position of search in history (through /)
	'$pos_history', # position in history buffer (for up and down cursor)
	'$proxy_host', 	# proxy host for dbman-proxy
	'$proxy_port', 	# proxy port for dbman-proxy (default 2401)
	'$quiet', 	# quiet output - don't show messages like 'Starting...'
	'$readline', 	# can I use Term::ReadLine object for input ?
	'$save_history', # save history to file ?
	'$search',	# searching in output buffer through Find function
	'$slang', 	# use slang library in command line interface ?
	'$splash',	# splash screen window handler
	'$sql', 	# SQL query for executing (current)
	'$sql_num_line', # TRUE if multiline SQL query input, FALSE if single l.
	'$sqlline', 	# own SQL input line query
	'$strict_import', # quick import (without showing) ?
	'$summary_info', # show summary info after select queries ?
	'$tabdesonly',	# only Table Designer
	'$tagOutput', 	# current tag for output -> 'trans' or ''
	'$term', 	# current terminal (Term::ReadLine)
	'$top', 	# main window of dbMan (top window)
	'$trans_start', # start position of transaction in output window
	'$type_info', 	# all types information from db driver
	'$view_icon', 	# viewfolder.xbm icon
	'$warning_updates', # warning before danger SQL commands ?
	'$win', 	# curses Window
	'%Config', 	# hash with configuration
	'%Field_sep', 	# field separators for table format (Column, Row, Cross)
	'%Macros', 	# macro definitions (two sides of regexp) for queries
	'%Message', 	# current messages for current language (from dbManLang)
	'%Types', 	# Type conversion name for Table Manager (use Pg)
	'@BROWSERS',	# browse query windows
	'@PLUGINS', 	# plugins
	'@WINDOWS', 	# SQL output standalone windows
	'@cl_buffer',	# output buffer for command line interface
	'@driver_names', # names of all drivers with preffered drivers first
	'@fnt', 	# font addition for output Tk object
	'@freeze_auth', # last authentification items
	'@history',	# history buffer
	'@listtableswin_tabs', # list of tables list with tables
	'@dsn');	# DSN for -xdbish start

BEGIN {
	$DEBUG = 0;
	# ++$DEBUG;
	
	require 5.003;
	++$^W if $DEBUG;

	sub help_cl {
		print <<EOF;
Usage: dbman [OPTIONS...] [AUTH INFO]
	-c	Command line shell via NCurses
	-d	Debug on
	-e	Direct execute SQL query
	-h	This little help
	-l	Command line shell
	-q	Quite output (only with -d option)
	-s	Command line shell via SLang
	-t	Only Table Designer
	-x	xdbish compatible start - DSN
	-v	Print version
EOF
	}

	$LANG = 'en';
	$LANG = $ENV{LANG} if defined $ENV{LANG};
	$LANG = $ENV{DBMAN_LANG} if defined $ENV{DBMAN_LANG};

	use FindBin;
	$VERSION = '0.1.0';
	$VERNAME = "dbMan $VERSION";  $ORIG_0 = $FindBin::RealScript;  
	$0 = "dbMan";
	$quiet = 1;  $command_line = 0;  $slang = 0;  $readline = 0;
	$only_execute = 0;  $tabdesonly = 0;
	while (@ARGV and $ARGV[0] =~ /^-(.*)/) {
		if (uc $1 eq 'Q' || uc $1 eq 'QUIET' || uc $1 eq '-QUIET') {
			++$quiet;  shift @ARGV;
		} elsif (uc $1 eq 'D' || uc $1 eq 'DEBUG'
				|| uc $1 eq '-DEBUG') {
			++$DEBUG; --$quiet;  shift @ARGV;
		} elsif (uc $1 eq 'T' || uc $1 eq 'TABDES'
				|| uc $1 eq '-TABDES') {
			++$tabdesonly;  shift @ARGV;
		} elsif (uc $1 eq 'C' || uc $1 eq 'CURSES'
				|| uc $1 eq '-CURSES') {
			++$command_line;  ++$curses;  $slang = 0;  shift @ARGV;
	       	} elsif (uc $1 eq 'S' || uc $1 eq 'SLANG'
				|| uc $1 eq '-SLANG') {
			++$command_line;  ++$slang;  $curses = 0;  shift @ARGV;
	       	} elsif (uc $1 eq 'L' || uc $1 eq '-SHELL' || uc $1 eq 'SHELL'
				|| uc $1 eq 'LINE' || uc $1 eq '-LINE') {
			++$command_line;  shift @ARGV;
		} elsif (uc $1 eq 'V' || uc $1 eq 'VERSION'
				|| uc $1 eq '-VERSION') {
			print "$VERNAME\n\n";  exit;
		} elsif (uc $1 eq 'H' || uc $1 eq 'HELP' || uc $1 eq '-HELP') {
			print "$VERNAME\n\n";  help_cl();  exit;
		} elsif (uc $1 eq 'E' || uc $1 eq 'EXECUTE' || uc $1 eq 'SQL'
				|| uc $1 eq '-SQL' || uc $1 eq '-EXECUTE') {
			shift @ARGV;  $only_execute = shift @ARGV;
			++$command_line;
		} elsif (uc $1 eq 'X' || uc $1 eq 'XDBISH' || uc $1 eq 'DSN'
				|| uc $1 eq '-XDBISH' || uc $1 eq '-DSN') {
			shift @ARGV;  @dsn = @ARGV;  @ARGV = ();
		} else { last; }
	}
	++$quiet if $command_line;

	if ($LANG eq 'cz' or $LANG eq 'cs') {
		print "$VERNAME\n\nStartuji Perl...\n" unless $quiet;
	} elsif ($LANG eq 'de') {
		print "$VERNAME\n\nIch starte Perl...\n" unless $quiet;
	} else {
		print "$VERNAME\n\nStarting Perl...\n" unless $quiet;
	}
	if ($ORIG_0 !~ /\//) { $ORIG_0 = './' . $ORIG_0; }
	$mylib = $ORIG_0;  $mylib =~ s/\/[^\/]*$//;
	$DEFAULT_DIR = $DEFA;
	unshift @INC,".";
	unshift @INC,$DEFAULT_DIR;
	unshift @INC,$mylib;
	unshift @INC,$ENV{DBMAN_LIB} if $ENV{DBMAN_LIB};
	if ($command_line) {
		unshift @INC,"./nonTk";
		unshift @INC,$DEFAULT_DIR . '/nonTk';
		unshift @INC,$mylib . "/nonTk";
		unshift @INC,$ENV{DBMAN_LIB} . '/nonTk' if $ENV{DBMAN_LIB};
	}
}

use nDBI;

unless ($command_line) {
	use Tk;
} else {
	eval {
		require Term::ReadLine;
	};
	++$readline unless $@;
}

require Plugin;
require dbManLang;  dbManLang->import($LANG);
require dbManEval;

command_line_intro() if $command_line and not $only_execute;
print "$Message{test}\n" unless $quiet;

sub DEBUG {
	print STDERR join "\n",map { "DEBUG: $_"; } @_ if $DEBUG;
	print STDERR "\n" if $DEBUG;
}

sub sexit {
	print "\n\n" if $command_line;
	unload_plugins();  exit;
}

$SIG{__WARN__} = sub { };

$SIG{QUIT} = \&sexit;  $SIG{ABRT} = \&sexit;  $SIG{KILL} = \&sexit;
$SIG{INT} = \&sexit;   $SIG{TERM} = \&sexit;  $SIG{HUP} = \&sexit;
$SIG{CHLD} = sub { wait; };

$ICONDIR = ".";
for my $t (".",$mylib,split (/:/,$ENV{PATH}),$DEFAULT_DIR,@INC) {
	if (-e "$t/openfolder.xbm") { $ICONDIR = $t;  last; }
}
$OUTPUT_LEN_WARN = 1000000;  @BROWSERS = ();  @WINDOWS = ();

for my $t (".",$mylib,split (/:/,$ENV{PATH}),$DEFAULT_DIR,@INC) {
	if (-e "$t/dbman.help.".dbManLang::lang()) {
		$helpdir = $t;  last;
	}
}

if ($readline) {
	$term = new Term::ReadLine 'dbMan';
} else {
	$term = undef;
}

################################## SUBS ######################################

sub input_cl {
	my ($default,$prompt,$noreadline) = @_;
	my $read;
	if ($readline and not $noreadline) {
		$read = $term->readline($prompt);
	} else {
		print $prompt;  $read = <STDIN>;
	}
	$read =~ s/\n//g;
	return '' if $read =~ "\'\'";
	return $read?$read:$default;
}

sub show_input {
	system('stty echo');
}

sub hidden_input {
	system('stty -echo');
}

sub drivers {
	my $proxy = shift;
	my @drivers = $proxy ? nDBI->all_drivers : sort nDBI->available_drivers;
	@driver_names = ();
	my @temporary = ();  my $preff = '';  my $cfg = $Config{driver};
	for (@drivers) {
		if (/^$cfg$/i) { $preff = $_;
		} elsif (/^(Pg|Oracle|mysql)$/i) {
			push @driver_names,$_; $dbsys = $_;
		} else { push @temporary,$_; }
	}
	if ($preff) {
		@driver_names = ($preff,@driver_names,@temporary);
		$dbsys = $preff;
	} else { @driver_names = (@driver_names,@temporary); }
	$dbsys = $driver_names[0] unless $dbsys;
}

my @hist_save = ();

sub savehistory_term {
	@hist_save = $term->GetHistory();
	$term->SetHistory();
}

sub restorehistory_term {
	$term->SetHistory(@hist_save);
}

sub dbauthdialog {
	my ($title,$driver,$proxy,$dbname_s,$login_s) = @_;
	my ($hostname,$port,$database,$login,$password);

	if ($driver eq 'Pg') {
		($hostname,$port,$login) = ('localhost','5432',$ENV{USER});
	} else { ($hostname,$port,$login) = ('','',$ENV{USER}); }
	$hostname = $Config{host} if $Config{host};
	$port = $Config{port} if $Config{port};
	$database = $dbname_s;  $login = $login_s if $login_s;

	if (@dsn) {
		my ($d,$log,$pass) = @dsn;
		my ($o,$drv,$par) = split /:/,$d;
		@dsn = ();
		unless ($pass) {
			print "\n";
			hidden_input();
			$password = input_cl($password,$Message{password}.": ",1);
	      		show_input();  print "\n\n";
		}
		return ('','.dsn',$par,$log,$pass,$drv);
	} elsif ($Config{auth} and $login_s) {
		return ($hostname,$port,$database,$login,$Config{auth},$driver);
	}

	if ($command_line) {
		savehistory_term();
		$hostname = input_cl($hostname,
				$Message{hostname}." [$hostname]: ");
		$port = input_cl($port,$Message{port}." [$port]: ");
		$term->SetHistory(reverse @driver_names);
		$driver = input_cl($driver,$Message{driver}." [$driver]: ");
		$database = input_cl($database,
				$Message{database}." [$database]: ");
		$login = input_cl($login,$Message{login}." [$login]: ");
		hidden_input();
		$password = input_cl($password,$Message{password}.": ",1);
	      	show_input();  print "\n\n";
		my $pr = $Message{N};
		$pr = input_cl($pr,$Message{wantproxy}." [".$Message{Y}."/".
				$Message{N}."]: ");
		restorehistory_term();
		$ndbi_proxy = 0;
		if (uc $pr eq $Message{Y}) {
			$ndbi_proxy = 1;  $proxy = 1;
			$proxy_host = input_cl($proxy_host,$Message{proxyhost}.
					" [$proxy_host]: ");
			$proxy_port = input_cl($proxy_port,$Message{proxyport}.
					" [$proxy_port]: ");
		}
		return ($hostname,$port,$database,$login,$password,
				$driver,$proxy);
	} else {
		my $dialog = $top->DialogBox( -title => $title,
			-buttons => [ $Message{open}, $Message{cancel} ]);

		my $label_1 = $dialog->Label(-justify => 'left',
			-text => $Message{hostname}.':');
		my $entry_1 = $dialog->Entry(-textvariable => \$hostname);
		my $label_2 = $dialog->Label(-justify => 'left',
			-text => $Message{port} . ':');
		my $entry_7 = $dialog->Entry(-textvariable => \$port);
		my $label_3 = $dialog->Label(-justify => 'left',
			-text => $Message{driver} . ':');
		my $entry_8 = $dialog->Optionmenu(-options => \@driver_names,
			-variable => \$driver);
		my $label_4 = $dialog->Label(-justify => 'left',
			-text => $Message{database} . ':');
		my $entry_9 = $dialog->Entry(-textvariable => \$database);
		my $label_5 = $dialog->Label(-text => $Message{login}.':');
		my $entry_10 = $dialog->Entry(-textvariable => \$login);
		my $label_6 = $dialog->Label(-text => $Message{password}.':');
		my $entry_11 = $dialog->Entry(-textvariable => \$password,
				-show => '*');
		$entry_11->bind('<KeyPress-Return>',sub { });
		my ($entry_12,$label_7,$entry_13,$label_8,$entry_14);
		if ($proxy) {
			$entry_12 = $dialog->Checkbutton(
				-text => $Message{useproxy},
			       	-variable => \$ndbi_proxy);
			$label_7 = $dialog->Label(-text =>$Message{proxyhost}.':');
			$entry_13 = $dialog->Entry(-textvariable => \$proxy_host);
			$label_8 = $dialog->Label(-text =>$Message{proxyport}.':');
			$entry_14 = $dialog->Entry(-textvariable => \$proxy_port);
		}

		$label_1->grid(-column => 2, -row => 1, -sticky => 'w');
		$entry_1->grid(-column => 3, -row => 1, -sticky => 'ew');
		$label_2->grid(-column => 2, -row => 2, -sticky => 'w');
		$entry_7->grid(-column => 3, -row => 2, -sticky => 'ew');
		$label_3->grid(-column => 2, -row => 3, -sticky => 'w');
		$entry_8->grid(-column => 3, -row => 3, -sticky => 'ew');
		$label_4->grid(-column => 2, -row => 4, -sticky => 'w');
		$entry_9->grid(-column => 3, -row => 4, -sticky => 'ew');
		$label_5->grid(-column => 2, -row => 5, -sticky => 'w');
		$entry_10->grid(-column => 3, -row => 5, -sticky => 'ew');
		$label_6->grid(-column => 2, -row => 6, -sticky => 'w');
		$entry_11->grid(-column => 3, -row => 6, -sticky => 'ew');
		if ($proxy) {
			$entry_12->grid(-column => 3, -row => 7, -sticky => 'ew');
			$label_7->grid(-column => 2, -row => 8, -sticky => 'w');
			$entry_13->grid(-column => 3, -row => 8, -sticky => 'ew');
			$label_8->grid(-column => 2, -row => 9, -sticky => 'w');
			$entry_14->grid(-column => 3, -row => 9, -sticky => 'ew');
		}
		$dialog->gridRowconfigure(1, -weight => 0, -minsize  => 30);
		$dialog->gridRowconfigure(2, -weight => 0, -minsize  => 30);
		$dialog->gridRowconfigure(3, -weight => 0, -minsize  => 30);
		$dialog->gridRowconfigure(4, -weight => 0, -minsize  => 30);
		$dialog->gridRowconfigure(5, -weight => 0, -minsize  => 30);
		$dialog->gridRowconfigure(6, -weight => 0, -minsize  => 30);
		if ($proxy) {
			$dialog->gridRowconfigure(7, -weight  => 0,
					-minsize => 30);
			$dialog->gridRowconfigure(8, -weight  => 0,
					-minsize => 30);
			$dialog->gridRowconfigure(9, -weight  => 0,
					-minsize => 30);
			$dialog->gridRowconfigure(10, -weight  => 0,
					-minsize => 17);
		} else {
			$dialog->gridRowconfigure(7, -weight => 0,
					-minsize => 17);
		}
		$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
		$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
		$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 166);
		$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

		$entry_11->focusForce;
		if ($dialog->Show eq $Message{open}) {
			return ($hostname,$port,$database,$login,$password,
					$driver);
		} else { return (); }
	}
}

sub error {
	if ($command_line) {
		output_cl("ERROR: ".join("\n",@_)."\n");
	} else {
		$top->messageBox(-icon => 'error', -type => $Message{ok},
			-title => $Message{error}, -message => join "\n",@_);
	}
}

sub opendb {
	my $attr = {};
	$attr = shift if ref $_[0];
	my $proxy = $attr->{proxy};
	drivers($proxy);
	unless ($command_line) {
		if (Exists $manager) { $manager->destroy; $manager = undef; }
		if (Exists $hwnd) { 
			$hwnd->destroy; $hwnd = undef; $hbox = undef; 
		}
		if (Exists $ltables) { $ltables->destroy; $ltables = undef; }
		for (@BROWSERS,@WINDOWS) { 
			if (Exists $_) { $_->destroy; $_ = undef; } 
		}
	}
	@BROWSERS = ();  @WINDOWS = ();

	$dbi->disconnect if defined $dbi;  $dbi = undef;
	$dbname = '';  $top->title('dbMan') unless $command_line;
	unless ($command_line) {
		$output->delete('1.0','end'); $output->see('end');
	}
	$plugin_interface->set_dbi('','',undef) if defined $plugin_interface;
	if (my @lparams = dbauthdialog($Message{open_database},$dbsys,$proxy,
				@_)) {
		$proxy = $ndbi_proxy if $command_line;
		@freeze_auth = (@lparams,$proxy);
		$dbsys = $lparams[5];  my $logstr;
		if ($lparams[1] eq '.dsn') {
			$logstr = $lparams[2];  $lparams[1] = '';
		} else {
			if ($proxy) {
				$logstr = nDBI::ndbi_login_string($dbsys,
					$lparams[2]);
			} else {
				$logstr = nDBI::ndbi_login_string($dbsys,
					$lparams[2],$lparams[0],$lparams[1]);
			}
		}

		my $ls = "dbi:$dbsys:$logstr";
		if ($proxy and not $ndbi_proxy) {
			$ls = "dbi:Proxy:hostname=$lparams[0];".
				"port=$lparams[1];dsn=DBI:$dbsys:$logstr";
		}
		my $attr = { PrintError => 0,RaiseError => 0,AutoCommit => 1 };
		my $f = 'connect';  my @st = ();
		if ($ndbi_proxy and $proxy)
			{ $f = 'proxy';  @st = ($proxy_host,$proxy_port); }
		if ($dbi = nDBI->$f(@st,$ls,$lparams[3],$lparams[4],$attr)) {
			$dbi->ndbi_set(LongReadLen => $Config{longreadlen});
			$plugin_interface->set_dbi($dbsys,$lparams[2],$dbi)
				if defined $plugin_interface;
			$dbname = $lparams[2];
			$top->title('dbMan ('.($f eq 'proxy'?'proxy:':'').$dbsys
				.": ".lc($dbname).')') unless $command_line;
			$type_info = $dbi->type_info_all;
			prepare_type();
		} else { error(nDBI->errstr); }
	}
}

sub closedb {
	unless ($command_line) {
		if (Exists $manager) { $manager->destroy; $manager = undef; }
		if (Exists $hwnd) { 
			$hwnd->destroy; $hwnd = undef; $hbox = undef; 
		}
		if (Exists $ltables) { $ltables->destroy; $ltables = undef; }
		for (@BROWSERS,@WINDOWS) { 
			if (Exists $_) { $_->destroy; $_ = undef;} 
		}
	}
	@BROWSERS = ();  @WINDOWS = ();
	$dbi->disconnect if defined $dbi;  $dbi = undef;
	$dbname = '';  $top->title('dbMan') unless $command_line;
	$plugin_interface->set_dbi('','',undef) if defined $plugin_interface;
	@freeze_auth = ();
}

sub about {
	$top->messageBox(-icon => 'info', -type => $Message{ok},
		-title => $Message{about},
		-message => "$VERNAME\n\n(c) 1999-2000 by " . $Message{milan_sorm}.
			"\nsorm\@fi.muni.cz");
}

END {
	$dbi->disconnect if defined $dbi;
	$plugin_interface->set_dbi('','',undef) if defined $plugin_interface;
}

sub length_exceed_warn {
	if ($command_line) {
		output_cl(sprintf($Message{too_much_characters_cl},
                        $OUTPUT_LEN_WARN));  return;
	}
	if ($top->messageBox(-icon => 'questhead', -type => 'YesNo',
		-title => $Message{warning},
		-message => sprintf($Message{too_much_characters},
			$OUTPUT_LEN_WARN)) eq 'Yes') {
		clear_window();
	}
}

sub help_sql {
	my $cmd = shift;
	if (open HELP,$helpdir."/dbman.help.".dbManLang::lang()) {
		my $res = '';
		unless ($cmd) {
			my @index;
			while (<HELP>) {
				chomp;
				push @index,$1 if /^\*\*\*(.*)/;
			}
			@index = sort @index;
			use integer;
			my $d = @index/2;
			no integer;
			my $zb = (@index/2 == $d)?0:1;
			my @pomocne = @index[@index/2+$zb..$#index];
			@index = @index[0..@index/2+$zb-1];

			$res = $Message{usehelp};
			for (@index) {
				my $druha = shift @pomocne;
				$res .= sprintf("   %-20s    %-20s\n",$_,$druha);
			}
		} else {
			while (<HELP>) { chomp;  next unless /^\*\*\*$cmd/i;  last; }
			while (<HELP>) { chomp;  last if /^\*\*\*/;  $res .= "$_\n"; }
		}
		close HELP;  $res = $Message{unknown_sql} unless $res;
		return $res;
	} else {
		return sprintf($Message{dbman_help_unknown},"dbman.help.".
			dbManLang::lang());
	}
}

sub helpsql {
	my $r = help_sql;
        if ($command_line) { output_cl($r."\n"); }
        else { $output->insert('end',$r . "\n"); $output->see('end'); }
}

sub sql_comms_load {
	my @index = ();
	if (open HELP,$helpdir."/dbman.help.".dbManLang::lang()) {
		while (<HELP>) { chomp;  push @index,$1 if /^\*\*\*(.*)/; }
		@index = sort @index;  close HELP;
	}
	return @index;
}

sub helpsqlcomm {
	my $cmd = '';
	my $dialog = $top->DialogBox( -title => $Message{help_sql},
			-buttons => [ $Message{help}, $Message{cancel} ]);

	my $label_1 = $dialog->Label(-justify => 'left',
			-text => $Message{sql_command}.':');
	my @sqlcomms = sql_comms_load;
	my $entry_1;
	if (@sqlcomms) {
		$cmd = $sqlcomms[0];
		$entry_1 = $dialog->Optionmenu(-options => \@sqlcomms, -variable => \$cmd);
	} else {
		$entry_1 = $dialog->Entry(-textvariable => \$cmd);
	}

	my $separate = 0;
	my $entry_2 = $dialog->Checkbutton(-text => $Message{sepwnd},
			-variable => \$separate);

	$label_1->grid(-column => 2,-row => 1, -sticky => 'w');
	$entry_1->grid(-column => 3,-row => 1, -sticky => 'ew');
	$entry_2->grid(-column => 3,-row => 2, -sticky => 'ew');
	$dialog->gridRowconfigure(1, -weight  => 0, -minsize  => 30);
	$dialog->gridRowconfigure(2, -weight  => 0, -minsize  => 30);
	$dialog->gridRowconfigure(3, -weight  => 0, -minsize  => 17);
	$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
	$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
	$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 300);
	$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

	if ($dialog->Show eq $Message{help} and $cmd) {
		my $r = help_sql($cmd);
		if ($separate) {
			new_window_with("help $cmd\n",$r);
		} else {
			$output->insert('end',"$r\n");
			$output->see('end');
		}
	}
}

sub inlinehelp {
        if (shift) { new_window_with("\\?\n",$Message{inline_help}); }
        else {
		if ($command_line) { output_cl($Message{inline_help}."\n"); }
		else {
			$output->insert('end',$Message{inline_help}."\n");
			$output->see('end');
		}
	}
}

sub output_to {
	my $where = shift;
	$where =~ s/~/$ENV{HOME}/eg;
 	if (open OUTPUT,">>$where") {
		if ($command_line) {
			print OUTPUT join("\n",map { chomp; s/ +$//; $_; } @cl_buffer);
		} else {
			print OUTPUT join("\n",map { s/ +$//; $_; }
				split(/\n/,$output->get('1.0','end')));
		}
		close OUTPUT;
	} else {
		$where = $Message{stdout} if $where eq '-';
		error($Message{append_error} . " $where: $!");
	}
}

sub output_to_file {
	my $dialog = $top->DialogBox( -title => $Message{output_to_file},
			-buttons => [ $Message{choose}, $Message{cancel} ]);
	my $filename = '-';

	my $label_1 = $dialog->Label(-justify => 'left',
			-text => $Message{filename}.':');
	my $entry_1 = $dialog->Entry(-textvariable => \$filename);

	$label_1->grid(-column => 2, -row => 1, -sticky => 'w');
	$entry_1->grid(-column => 3, -row => 1, -sticky => 'ew');
	$dialog->gridRowconfigure(1, -weight => 0, -minsize => 30);
	$dialog->gridRowconfigure(7, -weight => 0, -minsize => 17);
	$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
	$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
	$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 300);
	$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

	if ($dialog->Show eq $Message{choose} and $filename) { output_to($filename); }
}

sub print_history {
	my $counter = 0;
	my $res = join "\n",
		map { $counter++;  sprintf "%4i| $_",$counter; } @history;

	if ($command_line) {
		output_cl($res."\n");
	} else {
		$output->insert('end',$res."\n");
		$output->see('end');
	}
}

sub count_rows {
	if (defined $last_sql_rows) {
		if ($command_line) {
			output_cl(sprintf($Message{count_rows_msg},$last_sql_rows));
		} else {
			$top->messageBox(-icon => 'info', -type => $Message{ok},
				-title => $Message{count_rows},
				-message => sprintf($Message{count_rows_msg},
					$last_sql_rows));
		}
	} else {
		if ($command_line) {
			output_cl($Message{count_rows_error});
		} else {
			$top->messageBox(-icon => 'error',
				-type => $Message{ok},
				-title => $Message{count_rows},
				-message => $Message{count_rows_error});
		}
	}
}

sub sqlget {
	if ($sql_num_line)
		{ $sql = $sqlline->get('1.0','end');  $sql =~ s/\n/ /g; }
	return $sql;
}

sub sqlwrite {
	my $sql = shift;  $sql =~ s/^\s+//; $sql =~ s/\s+$//;
	if ($sql_num_line) {
		$sqlline->delete('1.0','end');  $sqlline->insert('1.0',$sql);
		$sqlline->see('1.0');
	}
	return $sql;
}

sub execute_command {
	my $cmd = shift;
	my $res = '';

	eval {
		if ($cmd) {
			if (open CMD,"$cmd|") { $res = join '',<CMD>;  close CMD; }
			else { error($Message{cmdfail}); $res = $Message{cmdfail}; }
			if ($command_line) { output_cl($res); }
			else { $output->insert('end',$res); $output->see('end'); }
		} else {
			if ($command_line) {
				my $start = $ENV{SHELL};
				$start = '/bin/sh' unless $start;
				$start = $ENV{DBMAN_SHELL} if $ENV{DBMAN_SHELL};
				output_cl($Message{shell_cl}."\n\n");
				system($start);
			} else {
				unless (fork) {
					my $start = 'xterm';
					$start = $ENV{DBMAN_SHELL} if $ENV{DBMAN_SHELL};
					{ exec($start) };
					error($Message{cmdfail});  exit;
				}
			}
		}
	};
	error($@) if $@;
}

sub listtables {
	return unless defined $dbi;
	my @tabs = $dbi->ndbi_tables;
	my $res = join "\n",sort @tabs;
	if ($command_line) {
		output_cl($res."\n");
	} else {
		$output->insert('end',$res."\n");
		$output->see('end');
	}
}

sub do_sql {
	my $query = shift;  $sql = sqlwrite($query);  execute_sql();
}

sub sql_select {
	my ($query,@binds) = @_;  return $dbi->selectall_arrayref($query,{},@binds);
}

sub execute {
	my ($query,@binds) = @_;
	print "$query\n";
	return $dbi->do($query,{},@binds);
}

sub eval_perl {
	my $code = shift;  $code .= ";";
	eval {
		open F,">/tmp/dbman.$$" or die "Can't create temporary file";
		*SAVE = *STDOUT;  *STDOUT = *F;
		my $result = dbManEval::eval($code);
		*STDOUT = *SAVE;
		if ($result) {
			$result =~ s/ ?at.*//;
			if ($command_line) {
				output_cl("\nERROR: $result\n");
			} else {
				$output->insert('end',"\nERROR: $result\n");
				$output->see('end');
			}
		}
		close F;
		open F,"/tmp/dbman.$$" or die "Can't open temporary file";
		while (<F>) {
			if ($command_line) { output_cl($_); }
			else { $output->insert('end',$_); }
		}
		$output->see('end');  close F;  unlink "/tmp/dbman.$$";
	};
}

sub version {
	my $res = sprintf($Message{thisis},$VERSION)."\n";
	$res .= "\n" if $_[0];
	if ($command_line) { output_cl($res); }
	else { $output->insert('end',$res);  $output->see('end'); }
}

sub author {
	my $res = $Message{author}."\n";
	if ($command_line) { output_cl($res); }
	else { $output->insert('end',$res);  $output->see('end'); }
}

sub warn_query {
	my $text = shift;

	if ($command_line) {
		print sprintf($Message{warn_query},$text)."\n";
                if (uc input_cl($Message{Y},$Message{execute}." [".$Message{Y}.
                                        "/".$Message{N}."]: ") eq $Message{Y}) { return 1; }
	} else {
		if ($top->messageBox(-icon => 'questhead', -type => 'YesNo',
			-title => $Message{warning},
			-message => sprintf($Message{warn_query},$text)) eq 'Yes') {
			return 1;
		}
	}
	return 0;
}

sub execute_sql {
	eval { execute_sql_wrapped(@_);	};
	if ($@) {
		$@ =~ s/ at \S+ line.*$/./ unless $DEBUG;
		error(sprintf($Message{execute_error},$@));
	}
}

sub execute_sql_wrapped {
	$sql = sqlget;
	return unless $sql;
	$sql =~ s/^\s+//g;  $sql =~ s/\s+$//g;  $sql =~ s/\s*;$//g;
	my $sql_answer = 0;

	study $sql;
	if ($sql =~ /^\/(.*)/) {
		if ($1) {
			$cmd_pattern = $1;
			$pos_cmd_search = 0;
		} else {
			return unless defined $cmd_pattern;
			++$pos_cmd_search;
		}
		while ($pos_cmd_search <= $#history) {
			if ($history[$pos_cmd_search] =~ /$cmd_pattern/) {
				$sql = sqlwrite($history[$pos_cmd_search]);
				output_cl($sql."\n") if $command_line; return;
			}
			++$pos_cmd_search;
		}
		$pos_cmd_search = 0;
		while ($pos_cmd_search <= $#history) {
			if ($history[$pos_cmd_search] =~ /$cmd_pattern/) {
				$sql = sqlwrite($history[$pos_cmd_search]);
				output_cl($sql."\n") if $command_line; return;
			}
			++$pos_cmd_search;
		}
		$sql = sqlwrite('');  return;
	} elsif ($sql =~ /^\\\.( \d+)?$/) {
		if ($1) {
			if (defined $history[$1-1]) {
				$sql = sqlwrite($history[$1-1]);
			} else { $sql = sqlwrite('');  return; }
		} else {
			if ($pos_history > 0) {
				$pos_history--;
				$sql = sqlwrite($history[$pos_history]);
			} else { $sql = sqlwrite('');  return; }
		}
		$sql =~ s/^\s+//g;  $sql =~ s/\s+$//g;  $sql =~ s/\s*;$//g;
		$sql = sqlwrite($sql);
		return unless $sql;
	}

	unless ($sql =~ /^(exit|quit|logout|\\q)$/i) {
		unless ($command_line) {
			if (length $output->get('1.0','end') > $OUTPUT_LEN_WARN)
       				{ length_exceed_warn; }
		} else {
			if (length(join '',@cl_buffer) > $OUTPUT_LEN_WARN)
				{ length_exceed_warn; }
		}
	}
	push @history,$sql;  savehistory($sql);  $pos_history = @history;
	clear_window() if $dont_buffer and not $command_line;
	$output->insert('end',$sql."\n",'query') unless $command_line;
	my $res_string = '';  my $ok = 1;  my $tO = $tagOutput;

	if ($sql =~ /^\\/) { $tO = ''; }

	my $outsql = undef;
	my $do_sql = 0;
	my $outsqlformat = '';

	study $sql;
	for my $tag (keys %Macros) {
		my $val = $Macros{$tag};
		$sql =~ s/$tag/$val/g;
       	}
	if ($full_sql_transaction) {
		$sql =~ s/^rollback$/\\tr/i;
		$sql =~ s/^begin(\s+(work|transaction))?$/\\tb/i;
		$sql =~ s/^end(\s+(work|transaction))?$/\\ta/i;
		$sql =~ s/^commit$/\\tc/i;
	}

	study $sql;
	if ($sql =~ /^(exit|quit|logout|\\q)$/i) { exit; }
	  elsif ($sql =~ /^\\!(\s+.*)?/) {
		  my $a = $1;  $a =~ s/^\s+//;  execute_command($a);
	} elsif ($sql =~ /^\\hw$/) {
		if ($command_line) { print_history; }
		              else { history_window(); }
	} elsif ($sql =~ /^eval\s+(.*)$/i) { eval_perl($1);
	} elsif ($sql =~ /^about$/i) { version(1); author();
	} elsif ($sql =~ /^authors?$/i) { author();
	} elsif ($sql =~ /^version$/i) { version();
	} elsif (defined $dbi and $ndbi_proxy && $sql =~ s/^proxy//i) {
		$sql =~ s/^\s//;  $res_string = $dbi->proxycall($sql);
	} elsif ($sql =~ /^\\ph$/) { print_history; }
	  elsif ($sql =~ /^\\wb$/) { $warning_updates = $warning_updates?0:1; }
	  elsif ($sql =~ /^\\fa$/) { freezeconf(); }
	  elsif ($sql =~ /^\\fap$/) { freezeconfwp(); }
	  elsif ($sql =~ /^\\fp$/) { $plain_format = $plain_format?0:1; }
	  elsif ($sql =~ /^(\\w\s+)?(help|\\h)\s?(.*)/i) {
		$res_string = help_sql($3);  $tO = '';  $outsql = '' if $1;
	} elsif (defined $dbi && $sql =~ /^\\m$/i) {
		table_manager() unless $command_line; }
	  elsif ($sql =~ /^\\db$/) {
		  $dont_buffer = $dont_buffer?0:1;
		  erase_history() if $dont_buffer;
	} elsif ($sql =~ /^\\ew$/) { clear_window(); }
	  elsif ($sql =~ /^\\es$/) { edsql(); return; }
	  elsif ($sql =~ /^\\eh$/) { erase_history(); }
	  elsif ($sql =~ /^\\ex(?:\(([^)]*)\))?\s+(.*)$/) {
		  my $fn = $1;  $fn = '-' if $fn eq '';
		  my @tab = split /,/,$2;  export($fn,@tab);
	} elsif (defined $dbi && $sql =~ /^\\e\s+(.*)/) {
		edit_window($1) unless $command_line;
	} elsif (defined $dbi && $sql =~ /^\\b\s+(.*)/) {
		browse_window($1) unless $command_line;
	} elsif (defined $dbi && $sql =~ /^\\d\s+(.*)/) {
		browse_window("describe $1") unless $command_line;
	} elsif ($sql =~ /^\\f(\s+.*)?/) {
		  my $a = $1;  $a =~ s/^\s+//;  find($a);
	} elsif (defined $dbi && $sql =~ /^\\dt/) { listtables; }
	  elsif (defined $dbi && $sql =~ /^\\lt/) {
		  listtableswin() unless $command_line; }
	  elsif ($sql =~ /^\\sc$/) { savecfg(); }
	  elsif ($sql =~ /^\\si$/) { $summary_info = $summary_info?0:1 }
	  elsif ($sql =~ /^\\iq$/) { $strict_import = $strict_import?0:1 }
	  elsif ($sql =~ /^\\en$/) { $empty_null = $empty_null?0:1 }
	  elsif ($sql =~ /^\\ec$/) { edcfg(); }
	  elsif (defined $dbi && $sql =~ /^\\tb/) { begin_trans(); }
	  elsif (defined $dbi && $sql =~ /^\\ta/) { auto_trans(); }
	  elsif (defined $dbi && $sql =~ /^\\tc/) { commit_trans(); }
	  elsif (defined $dbi && $sql =~ /^\\tr/) { rollback_trans(); }
	  elsif ($sql =~ /^\\c[rl]$/) { count_rows; }
	  elsif ($sql =~ /^\\co$/) { $counting = $counting?0:1; }
	  elsif ($sql =~ /^\\c/) { opendb; }
	  elsif ($sql =~ /^\\ip\s+(.*)/) {
		if (-d $1) { load_plugin_dir($1); } else { load_plugin($1); }
	} elsif ($sql =~ /^\\i(\s+.*)?/) {
		$output->insert('end',"\n") unless $command_line;
		my $a = $1;  $a =~ s/^\s+//;  $a = '-' unless $a;  import($a);
		$sql = '';  return;
	} elsif ($sql =~ s/^\\s(t?)(\(.*?\))?\s+//) {
		$outsqlformat = $1;
		my $a = $2;  $a =~ s/^\s+//;  $a =~ s/^\(|\)$//g;
		$a = '-' unless $a;  $outsql = $a;  ++$do_sql;
	} elsif ($sql =~ /^\\o(\s+.*)?/) {
		my $a = $1;  $a =~ s/^\s+//;  $a = '-' unless $a;
		output_to($a);
	} elsif ($sql =~ /^(\\w\s+)?\\\?/) {
		inlinehelp($1); $sql = sqlwrite(''); return;
	} elsif ($sql =~ s/^\\w\s+//) {
		$outsql = '';  ++$do_sql;
	} else { ++$do_sql; }

	my $result;  my @nameres = ();

	if ($do_sql) {
		my $correct_rows = 0;
		if (defined $dbi) {
			unless ($command_line) { mouse_hour($top);  mouse_hour($output); }
			if ($sql =~ /^(select|describe|show)\s/i) {
				$sql = $dbi->ndbd_describe($1)
					if $sql =~ /^describe\s+(.*)/i;
				my $sth = $dbi->prepare_cached($sql);
				$sth->{mysql_use_result} = 1 if $dbsys eq 'mysql';
				if (defined ($last_sql_rows = $sth->execute)) {
					++$sql_answer;
                                        if (defined (my $rf = $sth->{NAME})) { @nameres = @{$rf}; }
                                        else {
						@nameres = ();
						for ($sth->{NUM_OF_FIELDS}) { push @nameres,' '; }
					}
					if ($plain_format) {
						$res_string = '';
						$res_string .= '    ' if $counting;
						$res_string .= '| ' if $command_line && $counting;
						$res_string .= join ',', map { uc $_; } @nameres;
						my $ln = 1;
						$res_string .= "\n";
						while (my @result = $sth->fetchrow_array) {
							++$correct_rows;
							$res_string .= sprintf("%4i%s",$ln++,$command_line?'| ':'') if $counting;
							$_ =~ s/\n/\\n/g;  $_ =~ s/\r/\\r/g;
							$res_string .= join ',', map {
								$_ = $empty_null?'':'NULL' unless defined $_;  nDBI::looks_like_number($_)?$_:"'$_'";
							} @result;
							$res_string .= "\n";
						}
						$sth->finish;
					} else {
						my @delky = map { defined $_?length $_:4 } @nameres;
						while (my @result = $sth->fetchrow_array) {
							my $i = 0;
							for (@result) {
					  			for (split /\n/,$_) {
									my $z = $_;  $z = $empty_null?'':'NULL' unless defined $z;  $z =~ s/\r/aa/g;
									$delky[$i] = length $z if length $z > $delky[$i];
								}
							} continue { $i++; }
						}
						my $i = 0;
						$res_string .= '    ' if $counting;  $res_string .= '| ' if $command_line && $counting;
						for (@nameres) {
						       	$res_string .= uc sprintf("%*s%s",-$delky[$i++],$_,$Field_sep{Column});
						}
						$res_string =~ s/.$// if $res_string =~ /$Field_sep{Column}$/;
						$res_string =~ s/ +$//;  $res_string .= "\n";  $res_string .= '    ' if $counting;
						$res_string .= '| ' if $command_line && $counting;
						$res_string .= join ($Field_sep{Cross}, map { $Field_sep{Row} x $_ } @delky) . "\n";
						my @next_lines = ();  my $ln = 1;
						$sth->execute;
						while (my @result = $sth->fetchrow_array) {
							++$correct_rows;
							$i = 0;  my $fs = $Field_sep{Column};
							$res_string .= sprintf("%4i%s",$ln++,$command_line?'| ':'') if $counting;
							for (@result) {
								$_ =~ s/\r/\\r/g;
								$_ = $empty_null?'':'NULL' unless defined $_;
						    		my @strings = split /\n/,$_;  $_ = shift @strings;  my $just = -1;
						    		$just = 1 if nDBI::looks_like_number $_;
						    		$res_string .= sprintf("%*s%s",$just*$delky[$i++],$_,$fs);
						    		$next_lines[$i-1] = \@strings;
							}
							$res_string =~ s/.$// if $res_string =~ /$fs$/;
							$res_string =~ s/ +$//;  $res_string .= "\n";
							my $change = 0;  ++$change if @next_lines;
							while ($change) {
						    		$i = 0;  $change = 0;  my $out = '';
								$out .= '    ' . ($command_line?'| ':'') if $counting;
						    		for (@next_lines) {
									my $l = '';
									if (defined $_ and @$_) { $l = shift @$_;  ++$change; }
									$out .= sprintf("%*s%s",-$delky[$i++],$l,$fs);
						    		}
						    		$out =~ s/.$// if $out =~ /$fs$/;  $out .= "\n";
						    		$res_string .= $out if $change;
							}
						}
						$sth->finish;
					}
					if ($summary_info) {
						$last_sql_rows = $correct_rows;
						if ($counting) { $res_string .= '    ';  $res_string .= '| ' if $command_line; }
						$res_string .= dbManLang::suminfo(scalar @nameres,$last_sql_rows,\%Message) . "\n";
					}
				} else { $ok = 0; }
			} else {
				if ($sql =~ /^(delete|update|drop|vacuum|shutdown|truncate)(\s.*)?/ and $warning_updates) {
					if (warn_query($sql)) {
						$ok = defined $dbi->do($sql);  $last_sql_rows = undef;
					}
				} else {
					$ok = defined $dbi->do($sql);  $last_sql_rows = undef;
				}
			}
			unless ($command_line) { mouse_normal($top);  mouse_normal($output); }
			$res_string =~ s/ +$//;  $res_string = $dbi->state." ".$dbi->errstr."\n" unless $ok;
		} else { $res_string = $Message{no_database}."\n"; }
	}

	if ($outsql) {
		if ($outsqlformat eq 't') {
			$res_string = join ',', map { uc $_; } @nameres;
			$res_string .= "\n";
			for (@$result) {
				$res_string .= join ',', map {
					$_ = $empty_null?'':'NULL' unless defined $_;  nDBI::looks_like_number($_)?$_:"'$_'";
				} @$_;
				$res_string .= "\n";
			}
			$res_string .= dbManLang::suminfo(scalar @nameres,$last_sql_rows,\%Message) . "\n" if $summary_info;
		}
		if (open FILE,">>$outsql") {
			print FILE $sql . "\n" . '-' x length($sql) . "\n";
			print FILE $res_string . "\n";
			close FILE;
		} else {
			error sprintf($Message{cant_append},$outsql);
		}
	} elsif (defined $outsql) {
		new_window_with($sql . "\n", $res_string);
	} else {
		if ($command_line) { output_cl($res_string,$tO); } else {
			if ($counting && $sql_answer) {
				for (split /\n/,$res_string) {
					$output->insert('end',substr($_,0,4)
							. ' ','query');
					$output->insert('end',' ');
					$output->insert('end',substr($_."\n",4),							$tO);
				}
			} else {
				$output->insert('end',$res_string,$tO);
			}
	       	}
	}
	if ($command_line) { output_cl("\n"); }
      		else { $output->insert('end',"\n");  $output->see('end'); }
	$sql = sqlwrite('');
}

sub edsql {
	$sql = sqlget($sql);
	if ($external_edit or $command_line) {
		my $editor = 'vi';
		$editor = $ENV{EDITOR} if $ENV{EDITOR};
		my $worked = "/tmp/dbman.edsql.$$";
		if ($sql and $sql ne '\es') {
			if (open ED,">$worked") {
				print ED $sql;
				close ED;
			}
		}
		eval {
			if ($command_line) {
				system("$editor $worked");
			} else {
				system("xterm -e $editor $worked");
			}
			my $sqll;
			if (open ED,$worked) {
				$sqll = join "\n",<ED>;
				close ED;
			} else { $sqll = ''; }
			unlink $worked;
			$sql = sqlwrite($sqll);
		};
	} else {
		my $sqll = $sql;
		if (internal_editor(\$sqll)) {
			$sql = sqlwrite($sqll);
		}
	}
}

sub internal_editor {
	my $rdata = shift;
	my $wnd = $top->DialogBox( -title => $Message{editor},
	        -buttons => [ $Message{save}, $Message{cancel} ]);
	my $area = $wnd->Scrolled('Text', -scrollbars => 'sre',
		-wrap => 'word', @fnt)->pack;
	$area->insert('end',$$rdata);
	$area->see('1.0');  $area->focusForce;
	if ($wnd->Show eq $Message{save}) {
		$$rdata = $area->get('1.0','end');
		return 1;
	}
	return 0;
}

sub new_window_with {
	my ($main,$text) = @_;
	if ($command_line) { output_cl($text);  return; }
	my $m = $main;  $m =~ s/\n//g;
	my $wnd = $top->Toplevel(-title => $Message{wnd} . " ($m)");
        my $button_bar = $wnd->Frame(-borderwidth => 2, -relief => 'raised')
	                ->pack(-side => 'bottom', -fill => 'x');
	my $val = @WINDOWS;

	my $txt = $wnd->Scrolled('ROText', -scrollbars => 'sre',
			-wrap => 'none', @fnt)
		->pack(-fill => 'both', -expand => 'y');

	$txt->tagConfigure('query',-relief => 'raised', -borderwidth => 1);
	$txt->insert('end',$main,'query');
	$txt->insert('end',$text,'');
	$txt->see('1.0');

        $button_bar->Button(-text => $Message{close},
                        -command => sub { $WINDOWS[$val]->destroy;
			  $WINDOWS[$val] = undef })
		->pack;

	push @WINDOWS,$wnd;
}

sub history_back {
	$sql = sqlget;
	if ($pos_history > 0) {
		if ($sql ne $history[$pos_history])
			{ push @history,$sql;  savehistory($sql); }
		$pos_history--;  $sql = sqlwrite($history[$pos_history]);
	}
}

sub history_forward {
	$sql = sqlget;
	if ($pos_history <= $#history) {
		if ($sql ne $history[$pos_history])
			{ push @history,$sql;  savehistory($sql); }
		$pos_history++;  $sql = sqlwrite($history[$pos_history]);
	} else {
		if ($sql ne '') {
			push @history,$sql;  savehistory($sql);
			$sql = '';  $pos_history++;
		}
	}
}

my $oldcursor;

sub mouse_hour {
	my $w = shift;  $oldcursor = $w->cget('cursor');
	$w->configure(-cursor => 'watch');  $w->update;
}

sub mouse_normal {
	my $w = shift; $w->configure(-cursor => $oldcursor);
}

sub add_content {
	my $context = shift;
	my $res = 1;
	mouse_hour($manager);
	if ($context eq '/') { # schemas (or tables without schema)
		my %Schemas = ();
		for ($dbi->ndbi_tables) {
			if (/^(.*?)\./) {
				my $schema = $1;
				unless (exists $Schemas{uc $schema}) {
					$hlist->add("/$schema".'.',
						-itemtype => 'imagetext',
						-image => $close_icon,
						-text => uc $schema);
					++$Schemas{uc $schema};
				}
			} else {
				$hlist->add("/$_", -itemtype => 'imagetext',
					-image => $close_icon, -text => uc $_);
			}
		}
	} elsif ($context =~ /^([^\.]*\.)[^\/]*$/) { # tables
		my $schema = $1;  $schema =~ s/^\///;
		for ($dbi->ndbi_tables) {
			if (/^$schema(.*)/) {
				my $name = $1;
				$hlist->add("/$schema/$name",
					-itemtype => 'imagetext',
					-image => $close_icon,
					-text => uc $name);
			}
		}
	} else { # items
		my $table = $context;
		$table =~ s/\///g;
		eval {
			my $sth = $dbi->prepare('select * from '.$table.
					' where 0=1;');
			$sth->execute;
			unless ($dbi->errstr) {
				my @typ = @{$sth->{TYPE}};
				my @scales = @{$sth->{SCALE}};
				for (@{$sth->{NAME}}) {
					my $type = shift(@typ);
					my $scale = shift(@scales);
					$type = $Types{$type} if $Types{$type};
					$type .= "($scale)" if $scale;
					$hlist->add($context."/$_".'|',
						-itemtype => 'text',
						-text => lc($_) . " ($type)");
				}
				$sth->finish;
			} else { $res = 0; }
		};
		if ($@) { };
	}
	mouse_normal($manager);
	return $res;
}

sub hlist_expand {
	my $context = shift;
	if ($context =~ /\|$/) {
		# possible operations over items
		return;
	}
	if ($hlist->info('exists',$context)) {
		return if $context eq '/';
		my $next_context = $hlist->info('next',$context);
		if (not $next_context or
				(index ($next_context,"$context/") == -1)) {
			# open context
			$hlist->entryconfigure($context, -image => $open_icon);
			unless (add_content($context)) {
				$hlist->entryconfigure($context,
						-image => $view_icon);
			};
		} else {	# close context
			$hlist->entryconfigure($context, -image => $close_icon);
			$hlist->delete('offsprings',$context);
		}
	} else {	# new
		$hlist->add($context, -itemtype => 'imagetext',
				-image => $database_icon,
				-text => $dbsys . ': ' . uc($dbname));
		add_content($context);
	}
}

sub table_manager {
	return unless defined $dbi;  return if Exists $manager;

	$manager = $top->Toplevel(-title => $Message{table_manager});
	my $button_bar = $manager->Frame(-borderwidth => 2, -relief => 'raised')
		->pack(-side => 'bottom', -fill => 'x');
	$button_bar->Button(-text => $Message{close},
			-command => sub {
				$manager->destroy; $manager = undef
			})->pack;

	$hlist = $manager->Scrolled('HList', -drawbranch => 1, -width => 60,
			-separator => '/', -indent => 15, -height => 35,
			-command => \&hlist_expand, -scrollbars => 'se')
				->pack(-fill => 'both', -expand => 'y');

	$open_icon = $manager->Bitmap(-file => $ICONDIR.'/openfolder.xbm');
	$close_icon = $manager->Bitmap(-file => $ICONDIR.'/folder.xbm');
	$view_icon = $manager->Bitmap(-file => $ICONDIR.'/viewfolder.xbm');
	$database_icon = $manager->Bitmap(-file => $ICONDIR.'/database.xbm');

	hlist_expand('/');
}

sub select_font {
	my $dialog = $top->DialogBox(-title => $Message{select_font},
			-buttons => [ $Message{select}, $Message{cancel} ]);
	my $font = $current_font;

	my $label_1 = $dialog->Label(-justify => 'left', -text => $Message{font}
			.':');
	my $entry_1 = $dialog->Entry(-textvariable => \$font);

	$label_1->grid(-column => 2,-row => 1,-sticky => 'w');
	$entry_1->grid(-column => 3,-row => 1,-sticky => 'ew');
	$dialog->gridRowconfigure(1, -weight  => 0, -minsize  => 30);
	$dialog->gridRowconfigure(7, -weight  => 0, -minsize  => 17);
	$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
	$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
	$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 300);
	$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

	if ($dialog->Show eq $Message{select}) {
		$current_font = $font;
		$output->configure(-font => $current_font);
		$sqlline->configure(-font => $current_font);
		if ($current_font) { @fnt = (-font => $current_font); }
		$top->update;
	}
}

sub read_config  {
	my $config = $ENV{HOME}."/.dbmanrc";
	$historyfile = $ENV{HOME}."/.dbman_history";
	unless (-e $config) {
		$config = $ORIG_0;  $config =~ s%/[^/]*$%%;
		$config .= 'dbmanrc';  return unless -e $config;
	}
	if (open CONFIG,$config) {
		while (<CONFIG>) {
			chomp;  s/#.*//;  s/^\s+//;  s/\s+$//;  next unless $_;
			my ($tag,$value) = ('','');
			if (/^(.*?)\s+(.*)/) {
				($tag,$value) = ($1,$2);
			}
			$Config{lc $tag} .= "\n" if exists $Config{lc $tag};
			$Config{lc $tag} .= $value;
		}
		close CONFIG;
		$current_font = $Config{font};
		$historyfile = $Config{history} if $Config{history};
		$historyfile =~ s/~/$ENV{HOME}/eg;
	}
}

sub erase_history {
	@history = ();  $sql = sqlwrite('');
	eval { unlink($historyfile); } if -e $historyfile;
	unless ($command_line) {
		$hbox->delete(0,'end') if Exists $hwnd;
	} elsif ($readline) {
		eval { $term->SetHistory(); };  # clear history buffer
	}
}

sub savehistory {
	my $what = shift;
	$what =~ s/\n/ /g;  $what =~ s/^\s+//;  $what =~ s/\s+$//;
	return unless $what;  return unless $save_history;
	return if $lastsaved eq $what;
	if (open HISTORY,">>$historyfile")
		{ print HISTORY "$what\n";  close HISTORY; }
	$lastsaved = $what;
	$hbox->insert('end',$what) if Exists $hwnd;
}

sub finddialog {
	my $rsearch = shift;

	my $dialog = $top->DialogBox( -title => $Message{findtext},
		-buttons => [ $Message{find},$Message{cancel} ]);

	my $label_1 = $dialog->Label(-justify => 'left',
			-text => $Message{pattern}.':');
	my $entry_1 = $dialog->Entry(-textvariable => $rsearch);

	$label_1->grid(-column => 2,-row => 1,-sticky => 'w');
	$entry_1->grid(-column => 3,-row => 1,-sticky => 'ew');
	$dialog->gridRowconfigure(1, -weight  => 0, -minsize  => 30);
	$dialog->gridRowconfigure(7, -weight  => 0, -minsize  => 17);
	$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
	$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
	$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 300);
	$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

	return $dialog->Show;
}

sub find {
	my $dpattern = shift;
	my $response;
	if ($dpattern) { $response = $Message{find};  $search = $dpattern; }
		  else { return if $command_line;
			 $response = finddialog(\$search); }
	if ($response eq $Message{find}) {
		return if $command_line;     #### NOW BUT I MUST MAKE FIND
		$output->tagDelete('search');
		if ($search) {
			$output->tagConfigure('search',
				-background => 'yellow', -foreground => 'red');
			my $current = '1.0';  my $length = 0;
			while (1) {
				$current = $output->search(-count => \$length,
					-regexp, -nocase, '--', $search,
					$current, 'end');
				last unless $current;
				$output->tagAdd('search',$current,
					"$current + $length char");
				$current = $output
					->index("$current + $length char");
			}
		}
	}
}

sub prepare_type {
	if ($dbsys eq 'Pg') {
		my $rt = $dbi->selectall_arrayref(
			q!select typname,oid from pg_type where typtype = 'b'!);
		for (@$rt) { $Types{$_->[1]} = $_->[0]; }
	}

	my %info = %{shift @$type_info};
	my $data_type = $info{DATA_TYPE};
	my $type_name = $info{TYPE_NAME};
	for (@$type_info) {
		my @type = @$_;
		$type[$data_type] = 0 unless $type[$data_type];
		$Types{$type[$data_type]} = $type[$type_name];
	}
}

sub load_plugin {
	my $plugfile = shift;
	my $show = $plugfile;
	$show =~ s/.*\///;  $show =~ s/\.pm//;
	print sprintf($Message{load},$show) unless $quiet or $command_line;

	my $dir = $plugfile;  $dir =~ s/\/[^\/]+$//;
	unshift @INC,$dir;  require "$show.pm";
	if (my $pl = register $show ($plugin_interface)) {
		print "\n" unless $quiet or $command_line;
		push @PLUGINS,{ file => $plugfile, obj => $pl };
	} else {
		print " ".$Message{failed}."\n" unless $quiet or $command_line;
	}
}

sub unload_plugin {
	my $plugitem = shift;
	my $show = $$plugitem{file};
	$show =~ s/.*\///;  $show =~ s/\.pm//;
	print sprintf($Message{unload},$show) unless $quiet;

	my $off = 0;
	for (@PLUGINS) { last if $_ eq $plugitem; $off++ }
	if ($off < @PLUGINS) {
		if ($$plugitem{obj}->deregister) {
			print "\n" unless $quiet;
		} else { print " ".$Message{failed}."\n" unless $quiet; }
		splice(@PLUGINS,$off,1);
	} else { print " ".$Message{failed}."\n" unless $quiet; }
}

sub load_plugin_dir {
	my $dir = shift;  opendir PLUGIN_DIR,$dir;
	my @plugs = grep /^plug.+\.pm$/,readdir PLUGIN_DIR;
	closedir PLUGIN_DIR;
	$dir =~ s/\/$//;
	for (@plugs) { load_plugin("$dir/$_"); }
}

sub load_plugins {
	if (defined $Config{plugin_dir}) {
		for (split /\n/,$Config{plugin_dir}) { load_plugin_dir($_); }
	}
	if (defined $Config{plugin}) {
		for (split /\n/,$Config{plugin}) { load_plugin($_); }
	}
}

sub unload_plugins {
	my @ALL = @PLUGINS;  for (reverse @ALL) { unload_plugin($_); }
}

sub install_plugins {
	my $dialog = $top->DialogBox( -title => $Message{install_p},
			-buttons => [ $Message{install}, $Message{cancel} ]);
	my $pinput = '';

	my $label_1 = $dialog->Label(-justify => 'left',
			-text => $Message{fp});
	my $entry_1 = $dialog->Entry(-textvariable => \$pinput);

	$label_1->grid(-column => 2,-row => 1,-sticky => 'w');
	$entry_1->grid(-column => 3,-row => 1,-sticky => 'ew');
	$dialog->gridRowconfigure(1, -weight  => 0, -minsize  => 30);
	$dialog->gridRowconfigure(7, -weight  => 0, -minsize  => 17);
	$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
	$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
	$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 300);
	$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

	if ($dialog->Show eq $Message{install}) {
		if (-d $pinput) { load_plugin_dir($pinput); }
			   else { load_plugin($pinput); }
	}
}

sub reload_plugins {
	unload_plugins();  load_plugins;
}

sub select_start_trans {
	$tagOutput = 'trans';
	$trans_start = $output->index('end') unless $command_line;
}

sub begin_trans {
	$dbi->ndbi_set(AutoCommit => 0);
	select_start_trans;
}

sub clear_trans_tag {
	$trans_start =~ s/(\d+)\./($1-1) . "."/e;
	$output->tagRemove('trans',$trans_start,'end') unless $command_line;
}

sub auto_trans {
	$dbi->commit;
	$dbi->ndbi_set(AutoCommit => 1);
	clear_trans_tag;
	$tagOutput = '';
}

sub commit_trans {
	$dbi->commit;
	clear_trans_tag;
	select_start_trans;
}

sub rollback_trans {
	$dbi->rollback;
	$trans_start =~ s/(\d+)\./($1-1) . "."/e;
	unless ($command_line) {
		$output->delete($trans_start,'end');
		$output->insert('end',"\n",'query');
	}
	select_start_trans;
}

sub clear_window {
	if ($command_line) {
		@cl_buffer = ();
	} else {
		$output->delete('1.0','end'); $output->see('end');
	}
	$trans_start = '1.0';
}

sub browse_refresh {
	my ($browse,$grid,$query,$browse_search) = @_;
	mouse_hour($browse);
	$grid->deleteColumn(0,'end');
	my $search = $grid->ItemStyle('text', -foreground => 'red',
		-bg => 'yellow', @fnt);
	if ($query =~ /^describe (.*)$/i) {
		$query = $dbi->ndbd_describe($1);
	}
	my $sth = $dbi->prepare($query);
	my $ok = 1;
	if (defined $sth->execute) {
		my $x = 0;
		for (@{$sth->{NAME}}) {
			$grid->set($x, 0, -itemtype => 'text', -text => uc $_);
			$grid->sizeColumn($x++, -size => 'auto');
		}
		my $y = 1;  my @style;
		while (my @result = $sth->fetchrow_array) {
			$x = 0;
			for (@result) {
				@style = ();
				if ($browse_search) {
					@style = (-style => $search)
						if /$browse_search/;
				}
				$grid->set($x++, $y, -itemtype => 'text',
					-text => $_, @style);
			}
			++$y;
		}
		$sth->finish;
	} else { $ok = 0; }
	mouse_normal($browse);
	unless ($ok) {
		error($dbi->state." ".$dbi->errstr);
		$browse->destroy;
		return 0;
	}
}

sub edit {
	my ($grid,$table,$eoids,$columns,$cols,$x,$y) = @_;
	my $oid = $dbi->ndbd_oid($table);
	my $oidnum = $dbi->ndbd_oid_numeric($table);
	return unless defined $oid;

	my $rowid = $eoids->[$y-1];
	my $column = $columns->[$x+1];
	my $value = $grid->entryconfigure($x,$y,'-text');

	if ($y > (scalar @$eoids)) {
		my $query = "insert into $table ($column) values (?)";
		error($Message{cantinsert})
			unless defined $dbi->do($query,{},$value);
		return 0;
	} else {
		my $rowid = $eoids->[$y-1];
		my $query = "update $table set $column = ? where " .
			$oid . " = ?";
		$rowid += 0 if $oidnum;
		error($Message{cantedit})
			unless defined $dbi->do($query,{},$value,$rowid);
	}
	return 1;
}

sub can_edit {
	my ($grid,$eoids,$cols,$x,$y) = @_;
	if ($y >= 1 and $y <= (scalar @$eoids)+1 and $x <= $cols) {
		return 1;
	}
	return 0;
}

sub edit_refresh {
	my ($browse,$grid,$query,$order_by,$browse_search) = @_;
	my @edit_oids = ();
	my @columns = ();
	mouse_hour($browse);
	$query .= " order by $order_by" if $order_by;
	$grid->deleteColumn(0,'end');
	my $search = $grid->ItemStyle('text', -foreground => 'red',
		-bg => 'yellow', @fnt);
	my $sth = $dbi->prepare($query);
	my $ok = 1;  my $cols = 0;
	if (defined $sth->execute) {
		my $x = 0;  my $fst = 1;
		for (@columns = @{$sth->{NAME}}) {
			if ($fst) { $fst = 0; next; }
			$grid->set($x, 0, -itemtype => 'text', -text => uc $_);
			$grid->sizeColumn($x++, -size => 'auto');
		}
		$cols = scalar(@columns)-2;
		my $y = 1;  my @style;
		while (my @result = $sth->fetchrow_array) {
			$x = 0;  my $fst = 1;
			for (@result) {
				if ($fst) {
					$fst = 0;  push @edit_oids,$_;  next;
				}
				@style = ();
				if ($browse_search) {
					@style = (-style => $search)
						if /$browse_search/;
				}
				$grid->set($x++, $y, -itemtype => 'text',
					-text => $_, @style);
			}
			++$y;
		} 
		$sth->finish;
		$grid->set(0, $y, -itemtype => 'text', -text => '');
	} else { $ok = 0; }
	mouse_normal($browse);
	unless ($ok) {
		error($dbi->state." ".$dbi->errstr);
		$browse->destroy;
		return (0,undef,undef);
	}
	return ($cols,\@edit_oids,\@columns);
}

sub edit_delete {
	my ($browse,$grid,$table,$eoids,$cols) = @_;
	my $y = $grid->anchor('get');      # why ???
	$grid->editApply;
	return unless $y >= 1 and $y <= scalar @$eoids;
	if ($top->messageBox(-icon => 'questhead', -type => 'YesNo',
		-title => $Message{warning},
		-message => $Message{delete_table_row}) eq 'Yes') {
		my $rowid = $eoids->[$y-1];
	        my $query = "delete from $table where " .
			$dbi->ndbd_oid($table) . " = ?";
		$rowid += 0 if $dbi->ndbd_oid_numeric($table);
		error($Message{cantdelete})
			unless defined $dbi->do($query,{},$rowid);
	}
}

sub edit_window {
	my $table = shift;  return unless defined $dbi;

	if ($tagOutput) {	# transaction
		if ($top->messageBox(-icon => 'questhead', -type => 'YesNo',
	                -title => $Message{warning},
		        -message => sprintf($Message{transaction_stop}))
			     	eq 'Yes') {
			auto_trans;
		}
	}

	my $query = "select ".$dbi->ndbd_oid($table).",$table.* from $table";
        my $order_by = $dbi->ndbd_oid($table);
	my $browse = $top->Toplevel(-title => $Message{edit} . " ($table)");
        my $button_bar = $browse->Frame(-borderwidth => 2, -relief => 'raised')
	                ->pack(-side => 'bottom', -fill => 'x');
	my $val = @BROWSERS;
	my $eoids = [];  my $columns = [];  my $cols = 0;

	my $grid = $browse->Scrolled('TixGrid', -scrollbars => 'se',
			-topmargin => 1, -leftmargin => 0, @fnt)
		->pack(-fill => 'both', -expand=>'y');
	$grid->configure(-formatcmd => sub { gformat($grid,@_); } );
	$grid->configure(-editdonecmd => sub {
			($cols,$eoids,$columns)
		                = edit_refresh($browse,$grid,$query)
				unless edit($grid,$table,$eoids,$columns,$cols,
					@_);
		} );
	$grid->configure(-editnotifycmd => sub {
			if ($_[1] == 0) {
				$order_by = ($_[0] <= $cols)
					? uc($columns->[$_[0]+1])
					: $dbi->ndbd_oid($table);
				($cols,$eoids,$columns)
			       		= edit_refresh($browse,$grid,$query,
						$order_by);
				return 0;
			} else {
				return can_edit($grid,$eoids,$cols,@_);
			}
		} );

        $button_bar->Button(-text => $Message{refresh},
                        -command => sub {
				($cols,$eoids,$columns)
					= edit_refresh($browse,$grid,$query,
						$order_by); })
		->grid(-row => 0, -column => 0);
        $button_bar->Button(-text => $Message{delete},
                        -command => sub {
				if (edit_delete($browse,$grid,$table,$eoids,
						$cols)) {
					($cols,$eoids,$columns)
						= edit_refresh($browse,$grid,
							$query,$order_by);
				}
		})->grid(-row => 0, -column => 1);
        $button_bar->Button(-text => $Message{find},
                        -command => sub {
				my $what = '';
				if (finddialog(\$what) eq $Message{find}
					and $what) {
					($cols,$eoids,$columns) = edit_refresh(
						$browse,$grid,$query,$order_by,
						$what);
				}
			})->grid(-row => 0, -column => 2);
        $button_bar->Button(-text => $Message{close},
                        -command => sub { $BROWSERS[$val]->destroy;
			  $BROWSERS[$val] = undef })
		->grid(-row => 0, -column => 3);

	($cols,$eoids,$columns) = edit_refresh($browse,$grid,$query,$order_by);

	push @BROWSERS,$browse;
}

sub browse_window {
	my $query = shift;
	return unless defined $dbi;
	$query =~ s/^\s+//g;  $query =~ s/\s+$//g;  $query =~ s/\s*;$//g;
	if ($query !~ /^(select|describe|show)\s/i) {
		if ($query !~ /\s/) {
			$query = 'select * from '.$query;
		} else { error($Message{onlyselect_error});  return; }
	}
	my $browse = $top->Toplevel(-title => $Message{browse} . " ($query)");
        my $button_bar = $browse->Frame(-borderwidth => 2, -relief => 'raised')
	                ->pack(-side => 'bottom', -fill => 'x');
	my $val = @BROWSERS;

	my $grid = $browse->Scrolled('TixGrid', -scrollbars => 'se',
			-topmargin => 1, -leftmargin => 0, @fnt)
		->pack(-fill => 'both', -expand=>'y');
	$grid->configure(-formatcmd => sub { gformat($grid,@_); } );

        $button_bar->Button(-text => $Message{refresh},
                        -command => sub {
				browse_refresh($browse,$grid,$query); })
		->grid(-row => 0, -column => 0);
        $button_bar->Button(-text => $Message{find},
                        -command => sub {
				my $what = '';
				if (finddialog(\$what) eq $Message{find}
					and $what) {
					browse_refresh($browse,$grid,$query,
						$what);
				}
			})->grid(-row => 0, -column => 1);
        $button_bar->Button(-text => $Message{close},
                        -command => sub { $BROWSERS[$val]->destroy;
			  $BROWSERS[$val] = undef })
		->grid(-row => 0, -column => 2);

	if (browse_refresh($browse,$grid,$query)) {
		push @BROWSERS,$browse;
	}
}

sub gformat {
	my ($grid,$type,$x1,$y1,$x2,$y2) = @_;

	if ($type eq 'main') {
		$grid->formatGrid($x1,$y1,$x2,$y2, -relief=>'raised',
			-bd => 1, -bordercolor => 'gray20', -filled => 0,
			-bg => 'red', -xon => 1, -yon => 1, -xoff => 0,
			-yoff => 0, -anchor => 'se');
	} elsif ($type =~ /margin/) {
		$grid->formatBorder($x1,$y1,$x2,$y2, -fill => 1,
			-relief => 'raised', -bd => 1, -bg => 'gray65',
			-selectbackground => 'gray80');
	}
}

sub browse {
	my $query = 'select * from ';
	my $dialog = $top->DialogBox( -title => $Message{browse_sq},
			-buttons => [ $Message{browse}, $Message{cancel} ]);

	my $label_1 = $dialog->Label(-justify => 'left',
			-text => $Message{query}.':');
	my $entry_1 = $dialog->Entry(-textvariable => \$query);

	$label_1->grid(-column => 2,-row => 1,-sticky => 'w');
	$entry_1->grid(-column => 3,-row => 1,-sticky => 'ew');
	$dialog->gridRowconfigure(1, -weight  => 0, -minsize  => 30);
	$dialog->gridRowconfigure(7, -weight  => 0, -minsize  => 17);
	$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
	$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
	$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 300);
	$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

	if ($dialog->Show eq $Message{browse}) { browse_window($query); }
}

sub listtableswin_refresh {
	@listtableswin_tabs = $dbi->ndbi_tables;	
	$list->delete(0,'end');
	print $list_filter . "\n";
	if ($list_filter) {
		my @t = @listtableswin_tabs;
		@listtableswin_tabs = ();
		for (@t) {
			push @listtableswin_tabs,$_ if /$list_filter/;	
		}

	} 
	$list->insert(0,@listtableswin_tabs);
}

sub setfilter {
	my $rfilter = $list_filter;

	my $dialog = $top->DialogBox( -title => $Message{setfilter},
		-buttons => [ $Message{set},$Message{cancel} ]);

	my $label_1 = $dialog->Label(-justify => 'left',
			-text => $Message{filterre}.':');
	my $entry_1 = $dialog->Entry(-textvariable => \$rfilter);

	$label_1->grid(-column => 2,-row => 1,-sticky => 'w');
	$entry_1->grid(-column => 3,-row => 1,-sticky => 'ew');
	$dialog->gridRowconfigure(1, -weight  => 0, -minsize  => 30);
	$dialog->gridRowconfigure(7, -weight  => 0, -minsize  => 17);
	$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
	$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
	$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 300);
	$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

	if ($dialog->Show eq $Message{set}) {
		$list_filter = $rfilter;
		listtableswin_refresh(); 
	}
}

sub listtableswin {
	return if Exists $ltables or not defined $dbi;

	$ltables = $top->Toplevel(-title => $Message{loft});
	my $bot_bar = $ltables->Frame(-borderwidth => 2, -relief => 'raised')
		->pack(-side => 'bottom', -fill => 'x');
	my $button_bar = $bot_bar->Frame(-borderwidth => 0)
		->pack(-side => 'bottom', -fill => 'x');
	my $search_tab = '';
	my $search_l = $bot_bar->Entry(-textvariable => \$search_tab)
		->pack(-side => 'top', -fill => 'x', -expand => 1);
	$search_l->bind('<KeyPress>',sub {
		       	$list->selectionClear(0,'end');
			my $first = undef;
			if ($search_tab) {
				my $i = 0;
				for (@listtableswin_tabs) {
					if (/$search_tab/) {
						$list->selectionSet($i);
						$first = $i 
							unless defined $first;
					}
				} continue {
					++$i;
				}
				$list->yview($first) if defined $first;
			}
		});
	$button_bar->Button(-text => $Message{browse}, -underline => 0,
			-width => 10,
			-command => sub {
				my $r = join ',', 
					map { $listtableswin_tabs[$_] }
					$list->curselection;
				browse_window('select * from '. $r) if $r;
		} )->grid(-row => 0, -column => 0);
	$button_bar->Button(-text => $Message{edit}, -underline => 0,
			-width => 10,
			-command => sub {
				if (defined $list->curselection) {
					my $r = $listtableswin_tabs
						[($list->curselection)[0]];
					edit_window($r) if $r;
				}
		} )->grid(-row => 0, -column => 1);
	$button_bar->Button(-text => $Message{describe}, -underline => 0,
			-width => 10,
			-command => sub {
				if (defined $list->curselection) {
					my $r = $listtableswin_tabs
						[($list->curselection)[0]];
					browse_window('describe '. $r) if $r;
				}
		} )->grid(-row => 0, -column => 2);
	$button_bar->Button(-text => $Message{export}, -underline => 1,
			-width => 10,
			-command => sub {
				my @r = map { $listtableswin_tabs[$_] } 
					$list->curselection;
				export_sql(@r) if @r;
		} )->grid(-row => 0, -column => 3);
	$button_bar->Button(-text => $Message{refresh}, -underline => 0,
			-width => 10,
			-command => \&listtableswin_refresh )
		->grid(-row => 1, -column => 0);
	$button_bar->Button(-text => $Message{filter}, -underline => 0,
			-width => 10,
			-command => \&setfilter)
		->grid(-row => 1, -column => 1);
	$button_bar->Button(-text => $Message{unselect}, -underline => 0,
			-width => 10,
			-command => sub { 
				$list->selectionClear(0,'end');
			} )->grid(-row => 1, -column => 2);
	$button_bar->Button(-text => $Message{close}, -underline => 0,
			-width => 10,
			-command => sub { $ltables->destroy; })
		->grid(-row => 1, -column => 3);

	$list = $ltables->Scrolled('Listbox', -setgrid => 1,
			-scrollbars => 'e', -selectmode => 'multiple')
	       	->pack(-fill => 'both', -expand => 'y');
	listtableswin_refresh();
}

sub import_sql {
	my $filename = '.sql';
	my $dialog = $top->DialogBox( -title => $Message{import_sql},
			-buttons => [ $Message{import}, $Message{cancel} ]);

	my $label_1 = $dialog->Label(-justify => 'left',
			-text => $Message{sqlfile} . ':');
	my $entry_1 = $dialog->Entry(-textvariable => \$filename);

	$label_1->grid(-column => 2,-row => 1,-sticky => 'w');
	$entry_1->grid(-column => 3,-row => 1,-sticky => 'ew');
	$dialog->gridRowconfigure(1, -weight  => 0, -minsize  => 30);
	$dialog->gridRowconfigure(7, -weight  => 0, -minsize  => 17);
	$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
	$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
	$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 300);
	$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

	if ($dialog->Show eq $Message{import} and $filename)
		{ import($filename); }
}

sub out {
	my $mess = shift;
	if ($command_line) { output_cl($mess); }
	           else { $output->insert('end',$mess);  $output->see('end'); }
}

sub do_strict_sql {
	my $sqlcommand = shift;
	$sqlcommand =~ s/^\s+//;  $sqlcommand =~ s/\s+$//;  $sql =~ s/\s*;$//g;
	return unless $sqlcommand;
	$sqlcommand = $dbi->ndbd_describe($1)
		if $sqlcommand =~ /^describe\s(.*)/i;
	my $sth = $dbi->prepare($sqlcommand);
	$sth->{mysql_use_result} = 1 if $dbsys eq 'mysql';
	unless (defined $sth->execute) {
		out("\n".$Message{import_error}.":".$dbi->state." ".
				$dbi->errstr."\n");
	} else { out('.'); }
}

sub import {
	my $filename = shift;
	my $buf = '';

	$filename =~ s/~/$ENV{HOME}/eg;

	if (open FILE,$filename) {
		while (<FILE>) {
			chomp;
			unless (/;$/) { $buf .= "$_\n";  next; }
			if ($strict_import) { do_strict_sql($buf.$_); }
			else { $sql = sqlwrite($buf.$_); execute_sql; }
			$buf = '';
		}
		if ($buf) {
			if ($strict_import) { do_strict_sql($buf); }
			else { $sql = sqlwrite($buf); execute_sql; }
		}
		close FILE;
		$sql = sqlwrite('');
	} else { error($Message{open_error}.": $filename: $!"); }
}

sub export {
	my ($filename,@tables) = @_;

	$filename =~ s/~/$ENV{HOME}/eg;

	if (open FILE,">>".$filename) {
		for my $table (@tables) {
			print FILE $dbi->ndbd_create_table($table) . ";\n";
			my $sth = $dbi->prepare('select * from ' . $table);
			if (defined $sth->execute) {
				my $fields_string = lc join ',',@{$sth->{NAME}};
				while (my @result = $sth->fetchrow_array) {
					print FILE "INSERT INTO $table ".
						"($fields_string) VALUES ('"
						. join("','",@result) . "');\n";
				} 
				$sth->finish;
			}
		}
		close FILE;
	} else { error($Message{append_error}.": $filename: $!"); }
}

sub export_sql {
	my $filename = '.sql';
	my $dialog = $top->DialogBox( -title => $Message{export_to_sql},
			-buttons => [ $Message{export}, $Message{cancel} ]);

	my $label_1 = $dialog->Label(-justify => 'left',
			-text => $Message{sqlfile}.':');
	my $entry_1 = $dialog->Entry(-textvariable => \$filename);

	$label_1->grid(-column => 2,-row => 1,-sticky => 'w');
	$entry_1->grid(-column => 3,-row => 1,-sticky => 'ew');
	$dialog->gridRowconfigure(1, -weight  => 0, -minsize  => 30);
	$dialog->gridRowconfigure(7, -weight  => 0, -minsize  => 17);
	$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
	$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
	$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 300);
	$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

	if ($dialog->Show eq $Message{export} and $filename)
		{ export($filename,@_); }
}

sub history_window {
	return if Exists $hwnd;

	$hwnd = $top->Toplevel(-title => $Message{hwnd});
	my $button_bar = $hwnd->Frame(-borderwidth => 2, -relief => 'raised')
		->pack(-side => 'bottom', -fill => 'x');
	$button_bar->Button(-text => $Message{do},
			-command => sub {
				for ($hbox->curselection) {
					$sql = sqlwrite($history[$_]);
					execute_sql;
				}
				$hbox->selectionClear(0,'end')
			})->grid(-row => 0, -column => 0);
	$button_bar->Button(-text => $Message{delete},
			-command => \&erase_history)
		->grid(-row => 0, -column => 1);
	$button_bar->Button(-text => $Message{close},
			-command => sub {
				$hwnd->destroy; $hwnd = undef;
			})->grid(-row => 0, -column => 2);
	$hbox = $hwnd->Scrolled('Listbox', -setgrid => 1,
			-scrollbars => 'sre', -selectmode => 'multiple', @fnt)
	       	->pack(-fill => 'both', -expand => 'y');
	my $counter = 0;
	$hbox->insert(0,
		map { $counter++;  sprintf "%4i| $_",$counter; } @history);
}

sub sql_num_line_select {
	my $nline = $number_of_sqlline_rows;

	my $dialog = $top->DialogBox( -title => $Message{sqllinenum},
		-buttons => [ $Message{set},$Message{cancel} ]);

	my $label_1 = $dialog->Label(-justify => 'left',
			-text => $Message{sqllinenumtext}.':');
	my $entry_1 = $dialog->Entry(-textvariable => \$nline);

	$label_1->grid(-column => 2,-row => 1,-sticky => 'w');
	$entry_1->grid(-column => 3,-row => 1,-sticky => 'ew');
	$dialog->gridRowconfigure(1, -weight  => 0, -minsize  => 30);
	$dialog->gridRowconfigure(7, -weight  => 0, -minsize  => 17);
	$dialog->gridColumnconfigure(1, -weight => 0, -minsize => 10);
	$dialog->gridColumnconfigure(2, -weight => 0, -minsize => 30);
	$dialog->gridColumnconfigure(3, -weight => 0, -minsize => 300);
	$dialog->gridColumnconfigure(4, -weight => 0, -minsize => 13);

	if ($dialog->Show eq $Message{set}) {
		if ($nline >= 2 and $nline <= 20) {
			$number_of_sqlline_rows = $nline;
			change_sql_line();
		} else { error($Message{sqllinenumerror}); }
	}
}

sub change_sql_line {
	if (ref $sqlline eq 'Tk::Frame') { $sql = $sqlline->get('1.0','end'); }
	$sqlline->destroy;
	if ($sql_num_line) { define_multi_line(); }
	              else { define_single_line(); }
	$sql = sqlwrite($sql);
}

sub define_multi_line {
	$sqlline = $bottom_line->Scrolled('Text', -scrollbars => 'e',
			-height => $number_of_sqlline_rows,
			-wrap => 'word', @fnt)
		->pack(-side => 'bottom', -fill => 'x', -expand => 1);
	$sqlline->bind('<Control-KeyPress-Return>',\&execute_sql);
	$sqlline->bind('<Control-KeyPress-Up>',\&history_back);
	$sqlline->bind('<Control-KeyPress-Down>',\&history_forward);
	$sqlline->bind('<KeyPress-Escape>',sub { $sql = sqlwrite(''); });
	$sqlline->bind('<Control-KeyPress-space>',\&edsql);
	$sqlline->bind('<Control-KeyPress-slash>',sub {
			$sql = sqlwrite('/'); execute_sql(); });
}

sub define_single_line {
	$sqlline = $bottom_line->Entry(-textvariable => \$sql, @fnt)
		->pack(-side => 'bottom', -fill => 'x', -expand => 1);
	$sqlline->bind('<KeyPress-Return>',\&execute_sql);
	$sqlline->bind('<KeyPress-Up>',\&history_back);
	$sqlline->bind('<KeyPress-Down>',\&history_forward);
	$sqlline->bind('<KeyPress-Escape>',sub { $sql = sqlwrite(''); });
	$sqlline->bind('<Control-KeyPress-space>',\&edsql);
	$sqlline->bind('<Control-KeyPress-slash>',
			sub { $sql = sqlwrite('/'); execute_sql(); });
}

sub edcfg {
	my $editor = 'vi';
	$editor = $ENV{EDITOR} if $ENV{EDITOR};
	if ($command_line) {
		system("$editor ~/.dbmanrc");
	} else {
		unless (fork) {
			{ exec("xterm -e $editor ~/.dbmanrc") };
			exit;
		}
	}
}

sub savecfg {
	if (open F,">>" . $ENV{HOME} . "/.dbmanrc") {
		my $header = "dbMan autosave configuration          "
			. scalar localtime;
		print F "\n\n# " . $header . "\n# "."-" x length($header). "\n";
		print F "buffer no\n" if $dont_buffer;
		print F "counting yes\n" if $counting;
		print F "empty_null yes\n" if $empty_null;
		print F "external_editor yes\n" if $external_edit;
		print F "filehistory no\n" unless $save_history;
		print F "font " . $current_font . "\n" if $current_font;
		print F "format plain\n" if $plain_format;
		print F "import quick\n" if $strict_import;
		print F "multiline yes\n" if $sql_num_line;
		print F "multiline_count " . $number_of_sqlline_rows . "\n";
		print F "output_len_warn " . $OUTPUT_LEN_WARN . "\n";
		print F "sql_transaction yes\n" if $full_sql_transaction;
		print F "summary yes\n" if $summary_info;
		print F "warning_before yes\n" if $warning_updates;
		close F;
		edcfg;
	} else { error $Message{cantsave}; }
}

sub freezeconf {
	if (open F,">>" . $ENV{HOME} . "/.dbmanrc") {
		my $header = "dbMan freeze authentification          "
			. scalar localtime;
		print F "\n\n# " . $header . "\n# "."-" x length($header). "\n";
		print F "proxy on\n" if $freeze_auth[6];
		if ($ndbi_proxy) {
			print F "ndbi_proxy on\n";
			print F "proxy_host $proxy_host\n" if $proxy_host;
			print F "proxy_port $proxy_port\n" if $proxy_port;
		}
		print F "driver " . $freeze_auth[5] . "\n" if $freeze_auth[5];
		print F "host " . $freeze_auth[0] . "\n" if $freeze_auth[0];
		print F "port " . $freeze_auth[1] . "\n" if $freeze_auth[1];
		print F "database " . $freeze_auth[2] . "\n" if $freeze_auth[2];
		print F "login " . $freeze_auth[3] . "\n" if $freeze_auth[3];
		close F;
		edcfg;
	} else { error $Message{cantsave}; }
}

sub freezeconfwp {
	if (open F,">>" . $ENV{HOME} . "/.dbmanrc") {
		my $header = "dbMan freeze authentification          "
			. scalar localtime;
		print F "\n\n# " . $header . "\n# "."-" x length($header). "\n";
		print F "proxy on\n" if $freeze_auth[6];
		if ($ndbi_proxy) {
			print F "ndbi_proxy on\n";
			print F "proxy_host $proxy_host\n" if $proxy_host;
			print F "proxy_port $proxy_port\n" if $proxy_port;
		}
		print F "driver " . $freeze_auth[5] . "\n" if $freeze_auth[5];
		print F "host " . $freeze_auth[0] . "\n" if $freeze_auth[0];
		print F "port " . $freeze_auth[1] . "\n" if $freeze_auth[1];
		print F "database " . $freeze_auth[2] . "\n" if $freeze_auth[2];
		print F "login " . $freeze_auth[3] . "\n" if $freeze_auth[3];
		print F "auth " . $freeze_auth[4] . "\n" if $freeze_auth[4];
		close F;
		edcfg;
	} else { error $Message{cantsave}; }
}

sub loginsql {
	for (split /\n/,$Config{login_sql}) {
		$sql = sqlwrite($_);  execute_sql;
	}
}

sub output_cl {
	my ($text,$tag) = @_;
	print $text;
	push @cl_buffer,$text unless $dont_buffer;
}

sub curses_loop {
	require Curses;

	$win = new Curses;
	$win->clear;
	shell_loop();
	END { endwin() if $curses; }
}

sub slang_loop {
	require Term::Slang;

	my $slang = new Term::Slang;
	$slang->init_smg;  $slang->SLang_init_tty(-1,0,1);
	$slang->smg_init_smg;  $slang->SLkp_init;
	shell_loop();
	END { if ($slang) { $slang->SLang_reset_tty;  $slang->smg_reset_smg; } }
}

sub shell_loop {
	if ($readline) {
		eval { $term->SetHistory(); };	# clear history buffer
		for (@history) {
			$term->addhistory($_);
		}
	}
	while (1) {
		my $prompt = $Message{sql} . ": ";
		$sql = '';
		while (1) {
			$sql .= input_cl('',$prompt);
			if ($sql =~ /\\$/) {
				$sql =~ s/\\$/ /;
				$prompt = $Message{sqlcont} . ": ";
			} else { last; }
		}
		execute_sql;
	}
}

sub command_line_intro {
	print sprintf($Message{dbman_cl_intro}."\n",$VERNAME);
}

sub command_line_loop  {
	if ($curses) { curses_loop; }
	elsif ($slang) { slang_loop(); }
	else { shell_loop; }
}

sub destab_quit {
	my $destab = shift;
	$destab->destroy();
}

sub table_designer {
	my $destab;
	if ($tabdesonly) {
		$destab = new MainWindow;  
		$destab->title("dbMan: ".$Message{builder_tables});
	} else {
		$destab = $top->Toplevel(-title => $Message{builder_tables});
	}
	my $menubar = $destab->Frame(-borderwidth => 2, -relief => 'raised')
		->pack(-side => 'top', -fill => 'x');

	my $menudesigner = $menubar->Menubutton(-text => $Message{designer},
			-underline => 0)->pack(-side => 'left', -padx  => 2);
	$menudesigner->command(-label => $Message{exit}, 
			-command => sub { destab_quit($destab); },
			-underline => 0);
	my $m1 = $destab->Frame(-relief => 'raised', -borderwidth => 2)
		->pack(-fill => 'both', -expand => 'y');
	my $m2 = $m1->Frame(-relief => 'sunken', -borderwidth => 2)
		->pack(-fill => 'x', -side => 'bottom');
	my $m3 = $m1->Frame(-relief => 'sunken', -width => 40, 
		-borderwidth => 2)
		->pack(-fill => 'y', -side => 'left');
	my $status = $m2->Label()->pack(-side => 'left');
#	$status->configure(-text => 'Ready.');
	
	my $desk = $m1->Scrolled('Canvas', -width => 600, -height => 400,
		-scrollbars => 'sre', -scrollregion => [qw/0 0 4000 4000/])
		->pack(-fill => 'both', -expand => 'y');

	splash_stop() unless $command_line and defined $splash;
	Tk::MainLoop if $tabdesonly;
}

sub splash_start {
	$splash = new MainWindow(-borderwidth => 0, -height => 196, 
			-width => 606, -title => $Message{dbman_splash});
	$splash->{Shown} = 0;
	$splash->Label(-image => $splash->Photo(
		-file => $ICONDIR.'/splash.gif', 
		-format => 'gif', -height => 196, -width => 606))->pack();	
	my $x = int (($splash->screenwidth - $splash->reqwidth)/2 - 
			$splash->vrootx);
	my $y = int (($splash->screenheight - $splash->reqheight)/2 - 
			$splash->vrooty);
	$splash->geometry("+$x+$y");
	$splash->configure(-cursor => 'watch');
	++$splash->{Shown};
	$splash->deiconify();
	$splash->grab();
	$splash->focus();
	$splash->update();
}

sub splash_stop {
	sleep 5;
	$splash->Unbusy();
	$splash->destroy();
}

####################### main window and program ############################

@PLUGINS = ();

print $Message{readconf}."\n" unless $quiet;  read_config;

splash_start() unless $command_line;

if ($tabdesonly) {
	table_designer();
	exit;
}

print $Message{startdbi}."\n" unless $quiet;  $dbsys = '';  drivers;

$sql_num_line = 0;  $number_of_sqlline_rows = 3;

$ndbi_proxy = 0;
if ($Config{ndbi_proxy} =~ /^on$/i) { ++$ndbi_proxy;  $Config{proxy} = 'on'; }
$proxy_host = $Config{proxy_host} || 'localhost';
$proxy_port = $Config{proxy_port} || '2401';

$Config{longreadlen} = 1000000 unless $Config{longreadlen};
$full_sql_transaction = 0;
$full_sql_transaction = 1 if defined $Config{sql_transaction}
			and $Config{sql_transaction} =~ /^yes$/i;

++$sql_num_line if $Config{multiline} =~ /yes/i;
$number_of_sqlline_rows = $Config{multiline_count}
	if $Config{multiline_count} >= 2 and $Config{multiline_count} <= 20;

$save_history = 1;
$save_history = 0 if defined $Config{filehistory}
		and $Config{filehistory} =~ /^no$/i;
$pos_cmd_search = 0;  $cmd_pattern = undef;

%Field_sep = ( Column => '|', Row => '-', Cross => '+' );
if ($Config{fields_separators} =~ /^(.)(.)(.)$/) {
	($Field_sep{Column},$Field_sep{Row},$Field_sep{Cross}) = ($1,$2,$3);
}

$counting = 0;
++$counting if defined $Config{counting} and $Config{counting} =~ /^yes$/i;

$top = undef;  $output = undef;  $sqlline = undef;  my $menuplugins = undef;
$warning_updates = 0;
++$warning_updates if defined $Config{warning_before}
			and $Config{warning_before} =~ /^yes$/i;
$plain_format = 0;
++$plain_format if defined $Config{format} and $Config{format} =~ /^plain$/i;
$summary_info = 0;
++$summary_info if defined $Config{summary} and $Config{summary} =~ /^yes$/i;
$strict_import = 0;
++$strict_import if defined $Config{import} and $Config{import} =~ /^quick$/i;
$OUTPUT_LEN_WARN = $Config{output_len_warn} if defined $Config{output_len_warn};

$empty_null = 0;
++$empty_null if defined $Config{empty_null} and $Config{empty_null} =~ /^yes$/i;

unless ($command_line) {
	print $Message{starttk}."\n" unless $quiet;
	$top = new MainWindow;  $top->title('dbMan');

#	$balloon = $top->Balloon(-initwait => 1000, -state => 'balloon',
#				-balloonposition => 'mouse');

	my $menubar = $top->Frame(-borderwidth => 2, -relief => 'raised')
		->pack(-side => 'top', -fill => 'x');

	my $menudatabase = $menubar->Menubutton(-text => $Message{database},
			-underline => 0)->pack(-side => 'left', -padx  => 2);
	$menudatabase->command(-label => $Message{open}, -command => \&opendb,
			-underline => 0);
	$menudatabase->command(-label => $Message{openproxy},
			-command => sub { opendb( { proxy => 1 } ) },
			-underline => 6);
	$menudatabase->command(-label => $Message{close}, -command => \&closedb,
			-underline => 0);
	$menudatabase->separator;

	my $menutrans = $menudatabase->cascade(-label => $Message{transaction},
			-underline => 0)->cget(-menu)->Menu;
	$menudatabase->entryconfigure($Message{transaction},
			-menu => $menutrans);
	$menutrans->command(-label => $Message{tr_begin},
			-command => \&begin_trans, -underline => 0);
	$menutrans->command(-label => $Message{tr_end},
		       	-command => \&auto_trans, -underline => 0);
	$menutrans->separator;
	$menutrans->command(-label => $Message{tr_commit},
			-command => \&commit_trans, -underline => 0);
	$menutrans->command(-label => $Message{tr_rollback},
			-command => \&rollback_trans, -underline => 0);
	$menutrans->separator;
	$menutrans->checkbutton(-label => $Message{sqltrans},
			-variable => \$full_sql_transaction, -underline => 0);

	$menudatabase->separator;
	$menudatabase->command(-label => $Message{import_sql}.'...',
			-command => \&import_sql, -underline => 0);
	$menudatabase->checkbutton(-label => $Message{import_sql_quick},
			-variable => \$strict_import, -underline => 0);
	$menudatabase->separator;
	$menudatabase->command(-label => $Message{exit}, -command => \&exit,
			-underline => 1);

	my $menutables = $menubar->Menubutton(-text => $Message{table},
			-underline => 0)
		->pack(-side => 'left', -padx  => 2);
	$menutables->command(-label => $Message{manager}.'...',
			-command => \&table_manager, -underline => 0);
	$menutables->separator;
	$menutables->command(-label => $Message{list}.'...',
			-command => \&listtableswin, -underline => 0);
	$menutables->command(-label => $Message{browse}.'...',
			-command => \&browse, -underline => 0);
	$menutables->separator;
	$menutables->command(-label => $Message{count_rows},
			-command => \&count_rows, -underline => 0);

	my $menudesigner = $menubar->Menubutton(-text => $Message{builder},
			-underline => 1)
		->pack(-side => 'left', -padx  => 2);
	$menudesigner->command(-label => $Message{builder_tables}.'...',
			-command => \&table_designer, -underline => 0);

	$menuplugins = $menubar->Menubutton(-text => $Message{plugins},
			-underline => 0)->pack(-side => 'left', -padx  => 2);
	$menuplugins->command(-label => $Message{install}.'...',
			-command => \&install_plugins, -underline => 0);
	$menuplugins->command(-label => $Message{reload_plug},
			-command => \&reload_plugins, -underline => 0);
	$menuplugins->separator;

	my $menuconfig = $menubar->Menubutton(-text => $Message{config},
			-underline => 0)->pack(-side => 'left', -padx  => 2);
	$menuconfig->command(-label => $Message{editconfig},
			-command => \&edcfg, -underline => 0);
	$menuconfig->command(-label => $Message{savesetup},
			-command => \&savecfg, -underline => 0);
	$menuconfig->command(-label => $Message{freezeconf},
			-command => \&freezeconf, -underline => 0);
	$menuconfig->command(-label => $Message{freezeconfwp},
			-command => \&freezeconfwp, -underline => 7);
	$menuconfig->separator();
	$menuconfig->checkbutton(-label => $Message{warning_updates},
			-variable => \$warning_updates, -underline => 0);

	my $menuwindow = $menubar->Menubutton(-text => $Message{window},
			-underline => 0)->pack(-side => 'left', -padx  => 2);
	$menuwindow->command(-label => $Message{find}, -command => \&find,
			-underline => 0);
	$top->bind('<Meta-F>',\&find);

	$menuwindow->separator;
	$menuwindow->command(-label => $Message{clear},
			-command => \&clear_window, -underline => 0);
	$menuwindow->checkbutton(-label => $Message{dontbuffer},
			-variable => \$dont_buffer, -underline => 0);
	$menuwindow->checkbutton(-label => $Message{plainformat},
			-variable => \$plain_format, -underline => 0);
	$menuwindow->checkbutton(-label => $Message{counting},
			-variable => \$counting, -underline => 13);
	$menuwindow->checkbutton(-label => $Message{suminfo},
			-variable => \$summary_info, -underline => 0);
	$menuwindow->checkbutton(-label => $Message{emptynull},
			-variable => \$empty_null, -underline => 5);
	$menuwindow->command(-label => $Message{font},
			-command => \&select_font, -underline => 1);
	$menuwindow->separator;

	my $menusqlline = $menuwindow->cascade(-label => $Message{sqlline},
			-underline => 4)->cget(-menu)->Menu;
	$menuwindow->entryconfigure($Message{sqlline}, -menu => $menusqlline);
	$menusqlline->radiobutton(-label => $Message{singleline},
			-variable => \$sql_num_line, -value => 0,
			-command => \&change_sql_line, -underline => 0);
	$menusqlline->radiobutton(-label => $Message{multiline},
			-variable => \$sql_num_line, -value => 1,
			-command => \&change_sql_line, -underline => 0);
	$menusqlline->command(-label => $Message{selectnumlines},
			-command => \&sql_num_line_select, -underline => 7);
	$menusqlline->separator;
	$menusqlline->checkbutton(-label => $Message{exedit},
			-variable => \$external_edit, -underline => 1);

	my $menuhistory = $menuwindow->cascade(-label => $Message{history},
			-underline => 0)->cget(-menu)->Menu;
	$menuwindow->entryconfigure($Message{history}, -menu => $menuhistory);
	$menuhistory->checkbutton(-label => $Message{filehis},
			-variable => \$save_history, -underline => 11);
	$menuhistory->command(-label => $Message{erase_his},
			-command => \&erase_history, -underline => 0);
	$menuhistory->command(-label => $Message{hiswnd},
			-command => \&history_window, -underline => 0);

	$menuwindow->separator;
	$menuwindow->command(-label => $Message{output_to_file},
			-command => \&output_to_file, -underline => 1);

	my $menuhelp = $menubar->Menubutton(-text => $Message{help},
			-underline => 0)->pack(-side => 'left', -padx  => 2);
	$menuhelp->command(-label => $Message{inhelp1},
			-command => \&inlinehelp, -underline => 0);
	$menuhelp->command(-label => $Message{sqlhelp1}, -command => \&helpsql,
			-underline => 0);
	$menuhelp->command(-label => $Message{sqlhelp2},
			-command => \&helpsqlcomm, -underline => 4);
	$menuhelp->separator;
	$menuhelp->command(-label => $Message{about}, -command => \&about,
			-underline => 0);
}

$sql = '';  @history = ();  $pos_history = 0;  $manager = undef;
$ltables = undef;  $hbox = undef;  $hwnd = undef;  $last_sql_rows = undef;  
@fnt = ();  @cl_buffer = ();  $dont_buffer = 0;
++$dont_buffer if $Config{buffer} =~ /^no$/i;

$external_edit = 0;
++$external_edit if $Config{external_editor} =~ /^yes$/i;

unless ($command_line) {
	my $main_frame = $top->Frame(-relief => 'raised', -borderwidth => 2)
		->pack(-fill=>'both', -expand=>1);

	$bottom_line = $main_frame->Frame->pack(-side => 'bottom',
			-fill => 'x');

	my $label_1 = $bottom_line->Label(-text => $Message{sql}.':',
			-justify => 'left')->pack(-side => 'left');
	my $button = $bottom_line->Button(-default => 'active',
			-text => $Message{do},
		-command => \&execute_sql)->pack(-side => 'right');
#	$balloon->attach($button, -msg => $Message{bmsg_executesql});

	if ($current_font) { @fnt = (-font => $current_font); }

	if ($sql_num_line) { define_multi_line; } else { define_single_line; }

	$output = $main_frame->Scrolled('ROText', -scrollbars => 'sre',
			-wrap => 'none', @fnt)
		->pack(-fill => 'both', -expand => 1);
	$output->tagConfigure('query',-relief => 'raised', -borderwidth => 1);
	$output->tagConfigure('trans', -background => 'yellow');

	$sqlline->focus;
}

%Macros = ();

for (split /\n/,$Config{macro}) {
	my ($tag,$val) = ('','');
	($tag,$val) = /^(.*?)\s+(.*)/;
	next unless $tag;
 	$Macros{$tag} = $val;
}
my $cmd = '';
$cmd = $ARGV[0] if defined $ARGV[0];
$cmd = '' if $cmd =~ /^-/;
my $login = $Config{login};
$login = $1 if $cmd =~ s/(.*)\@//;
$cmd = $Config{database} if not $cmd and defined $Config{database};

$list_filter = '';

$plugin_interface = new Plugin ($top,$output,$sqlline,$menuplugins,
	dbManLang::lang());

load_plugins;

END {
	print $Message{closewin}."\n" unless $quiet or $command_line;
	unload_plugins if defined @PLUGINS and scalar(@PLUGINS);
}

if (open HISTORY,$historyfile) {
	while (<HISTORY>) {
		chomp;  push @history,$_;
		$pos_history = @history;  $lastsaved = $_;
	}
	close HISTORY;
}

# $top->OnDestroy(sub { $balloon->destroy; }) unless $command_line;

splash_stop() unless $command_line;

if ($Config{proxy} =~ /^on$/i) {
	opendb({proxy => 1}, $cmd, $login);
	loginsql if defined $dbi;
} else {
	opendb($cmd,$login);
	loginsql if defined $dbi;
}

unless ($command_line) {
	print $Message{switch}."\n" unless $quiet;
	version(1);  Tk::MainLoop;
} else {
	if ($only_execute) {
		$sql = sqlwrite($only_execute);  execute_sql();
	} else {
		command_line_loop;
	}
}
