#!/usr/bin/perl -w ################################################################################ # TMB - tiny mysql browser by # # this little perl script allows you to view and edit data in a mysql database # using a nice gtk+ interface # # required perl modules: # Gtk, DBI, DBD::mysql # # it shouldn't be too hard to port it to other database drivers # ################################################################################ use Gtk; set_locale Gtk; init Gtk; use DBI; my ($false, $true)=(0,1); sub do_exit { exit }; sub do_main_quit { Gtk->main_quit; return 1 }; $TMBHOME=(getpwuid $<)[7]."/.tmb"; ### activity progress-bar ###################################################### sub new_progress { my $pb=new Gtk::ProgressBar(); $pb->configure(0, 0, 100); $pb->set_activity_mode($true); return $pb; } sub make_progress { my ($pb)=@_; $pb->show; $pb->set_value(($pb->get_value()+1)%100); while(Gtk->events_pending){ Gtk->main_iteration; } } ### modal message box ########################################################## sub msg { my $dlg=new Gtk::Dialog(); $dlg->set_title("[tmb]"); $dlg->set_modal($true); $dlg->set_policy($false, $false, $true); $dlg->set_position('center'); $dlg->signal_connect("delete-event", \&do_main_quit); $dlg->border_width(8); my $lbl = new Gtk::Label(shift); $lbl->show(); $dlg->vbox()->pack_start($lbl, $true, $true, 8); my @buttons=@_; scalar @buttons or @buttons=("!Ok"); my $flag=-1; my $n=0; for(@buttons){ my $tmp=s/^!//; my $btn = new Gtk::Button($_); $btn->can_default(1); $btn->signal_connect("clicked", sub { ${$_[1]}=$_[2]; Gtk->main_quit }, \$flag, $n++); $btn->set_usize(80, -1); $btn->show(); $dlg->action_area()->pack_start($btn, $false, $false, 4); $dlg->set_default($btn) if $tmp; } $dlg->show(); Gtk->main; $dlg->destroy; return $flag; } sub info { my $win = new Gtk::Window("toplevel"); $win->set_title("[tmb]"); $win->set_modal($true); $win->set_policy($false, $false, $true); $win->set_position('center'); $win->signal_connect("delete-event", sub { 1 }); $win->border_width(8); my $lbl = new Gtk::Label($_[0]); $win->add($lbl); $lbl->signal_connect_after("expose-event", \&do_main_quit); $lbl->show(); $win->show(); { no warnings; Gtk->main() } return $win; } ### login window - ask for hostname, username and password ##################### sub login_window { my %config; if(open FD, "<$TMBHOME/config"){ while(){ chomp; /^([^=]*)=(.*)/ and $config{$1}=$2; } close FD; } my $win = new Gtk::Window("toplevel"); $win->set_title("tiny mysql browser - login"); $win->signal_connect("delete_event", \&do_exit); $win->border_width(8); $win->set_default_size(300, 100); my $t = new Gtk::Table(4, 2, $false); $t->set_row_spacings(4); $t->set_col_spacings(4); $t->show(); $win->add($t); my $n=0; my $label = new Gtk::Label("user\@server:"); $label->set_justify('right'); $label->set_alignment(1, 0.5); $label->show(); $t->attach_defaults($label, 0, 1, $n, $n+1); my $combo = new Gtk::Combo(); $combo->entry->signal_connect("activate", sub {$_[1]->activate_default}, $win); $combo->disable_activate(); $combo->show(); $t->attach_defaults($combo, 1, 2, $n, $n+1); ++$n; $label = new Gtk::Label("password:"); $label->set_justify('right'); $label->set_alignment(1, 0.5); $label->show(); $t->attach_defaults($label, 0, 1, $n, $n+1); my $entry = new Gtk::Entry(); $entry->signal_connect("activate", sub {$_[1]->activate_default}, $win); $entry->set_visibility($false); $entry->show(); $t->attach_defaults($entry, 1, 2, $n, $n+1); ++$n; $config{save_pass} = 0 unless defined $config{save_pass}; if($config{save_pass}){ $combo->entry->signal_connect("changed", sub { my $u=$_[0]->get_text; my $s=$_[1]->{"password-$u"}; $s="" unless defined $s; $_[2]->set_text($s); }, \%config, $entry); } my $cb_remember = new_with_label Gtk::CheckButton("remember (plaintext!) passwords"); $cb_remember->show(); $t->attach_defaults($cb_remember, 0, 2, $n, $n+1); $cb_remember->set_active($config{save_pass}); ++$n; my $cb_cache = new_with_label Gtk::CheckButton("remember database/table tree"); $cb_cache->show(); $t->attach_defaults($cb_cache, 0, 2, $n, $n+1); $cb_cache->set_active((defined $config{cache})?$config{cache}:1); ++$n; $win->set_position('center'); $win->show(); my @recent=(); @recent=(split ':', $config{recent}) if defined $config{recent}; $combo->set_popdown_strings(@recent); my $hb = new Gtk::HBox($true, 4); $hb->show(); $t->attach_defaults($hb, 0, 2, $n, $n+1); my $btn = new Gtk::Button("Ok"); $btn->signal_connect("clicked", \&do_main_quit); $btn->show(); $hb->pack_start($btn, $true, $true, 0); $btn->can_default(1); $win->set_default($btn); $btn = new Gtk::Button("Exit"); $btn->signal_connect("clicked", \&do_exit); $btn->show(); $btn->can_default(1); $hb->pack_start($btn, $true, $true, 0); while(){ Gtk->main; ($host, $user, $password) = ( (split "@", $combo->entry->get_text)[1,0], $entry->get_text()); my $w=info("Connecting to $host..."); $dbh = DBI->connect("DBI:mysql:host=$host", $user, $password, { PrintError => 0 }); $w->destroy; last if defined $dbh; msg "Can't connect: ".$DBI::errstr; } if($config{save_pass}=$cb_remember->get_active()){ $config{"password-$user\@$host"}=$password; }else{ while(my ($k, $v) = each %config){ $k =~ /^password-/ and delete $config{$k}; } } @recent = grep { $_ ne "$user\@$host" } @recent; unshift @recent, "$user\@$host"; splice @recent, 15; $config{recent}=join(":", @recent); $cache_tree = $config{cache} = $cb_cache->get_active(); $win->destroy; mkdir $TMBHOME, 0700; open FD, ">$TMBHOME/config"; while(my ($a,$b) = each %config){ print FD "$a=$b\n" } close FD; } ### db/table selection window ################################################## sub refresh_tree; sub sql_window; sub tables_window { my $win = new Gtk::Window("toplevel"); $win->set_title("[tmb] - $user\@$host"); $win->signal_connect("delete_event", \&do_exit); my $vb = new Gtk::VBox($false, 4); my $sw = new Gtk::ScrolledWindow(undef, undef); $sw->set_policy( 'automatic', 'automatic' ); $sw->set_usize(150, 200); $sw->show(); $vb->pack_start($sw, $true, $true, 0); $db_tree = new Gtk::Tree(); $db_tree->show(); $sw->add_with_viewport($db_tree); my $hb = new Gtk::HBox($true, 4); $ok_btn = new Gtk::Button("Open table"); $ok_btn->signal_connect("clicked", \&open_table); $ok_btn->show(); $ok_btn->set_sensitive($false); $hb->pack_start($ok_btn, $true, $true, 0); my $pb=new_progress(); my $btn = new Gtk::Button("SQL"); $btn->signal_connect("clicked", \&sql_window); $btn->show(); $hb->pack_start($btn, $true, $true, 0); $btn = new Gtk::Button("Refresh"); $btn->signal_connect("clicked", sub { refresh_tree $_[1] } , $pb); $btn->show(); $hb->pack_start($btn, $true, $true, 0); $btn = new Gtk::Button("Exit"); $btn->signal_connect("clicked", \&do_exit); $btn->show(); $hb->pack_start($btn, $true, $true, 0); $hb->show(); $vb->pack_start($hb, $false, $false, 0); $vb->pack_start($pb, $false, $false, 0); $vb->show(); $win->border_width(8); $win->add($vb); $win->set_position('center'); $win->show(); $cache_file="$TMBHOME/dbcache-$user\@$host"; if($cache_tree and open FD, "<$cache_file"){ $tree_root_item = new_with_label Gtk::TreeItem("Databases"); $tree_root_item->signal_connect("select", \&select_table); $tree_root_item->show(); $db_tree->append($tree_root_item); my $tree = new Gtk::Tree(); $tree_root_item->set_subtree($tree); while(){ chomp; my ($db, @tabs)=split /\./; my $item=new_with_label Gtk::TreeItem("$db"); $item->signal_connect("select", \&select_table); $item->show(); $tree->append($item); scalar @tabs or next; $subtree = new Gtk::Tree(); $item->set_subtree($subtree); for my $tab(@tabs){ my $titem=new_with_label Gtk::TreeItem($tab); $titem->signal_connect("select", \&select_table, "$db.$tab"); $titem->signal_connect("button-press-event", sub { my ($w, $ev) = @_; $ev->{type} eq "2button_press" or return 0; open_table(); return 1; }); $titem->show(); $subtree->append($titem); } } close FD; $tree_root_item->expand(); }else{ refresh_tree($pb); } Gtk->main; $win->destroy; } sub refresh_tree { $db_tree->remove($tree_root_item) if defined $tree_root_item; $tree_root_item = new_with_label Gtk::TreeItem("Databases"); $tree_root_item->signal_connect("select", \&select_table); $tree_root_item->show(); $db_tree->append($tree_root_item); my $tree = new Gtk::Tree(); $tree_root_item->set_subtree($tree); &make_progress; my $req=$dbh->prepare("show databases"); $req->execute(); if($cache_tree){ open FD, ">$cache_file" or $cache_tree=0; } while(my ($db) = $req->fetchrow_array()){ my $item=new_with_label Gtk::TreeItem("$db"); $item->signal_connect("select", \&select_table); $item->show(); $tree->append($item); &make_progress; my $treq=$dbh->prepare("show tables from $db"); my @tabs=(); if($treq->execute()){ my $subtree = undef; while(my ($tab) = $treq->fetchrow_array()){ push @tabs, $tab; &make_progress; if(!defined $subtree){ $subtree = new Gtk::Tree(); $item->set_subtree($subtree); } my $titem=new_with_label Gtk::TreeItem($tab); $titem->signal_connect("select", \&select_table, "$db.$tab"); $titem->show(); $subtree->append($titem); } } print FD join(".", $db, @tabs), "\n" if $cache_tree; } close FD if $cache_tree; $_[0]->hide; $tree_root_item->expand(); } sub select_table { $ok_btn->set_sensitive(defined ($selected_table=$_[1])); } ################################################################################ sub toggle { my ($tb, $hr, $name)=@_; if($tb->active){ $hr->{"e_$name"}->show; $hr->{"h_$name"}=1; }else{ $hr->{"e_$name"}->hide; $hr->{"h_$name"}=0; } } sub requery { my ($go, $pb, $clist, $h_where, $e_where, $h_order, $e_order, $h_limit, $e_limit, $table)= @{$_[0]}{qw(go progress clist h_where e_where h_order e_order h_limit e_limit table)}; if(exists $_[0]->{editor}){ $_[0]->{editor}{win}->destroy; delete $_[0]->{editor}; } $clist->clear(); $go->set_sensitive($false); make_progress $pb; my $r="select * from $table"; if($h_where){ $r.=" where ".$e_where->get_text(); } if($h_order){ $r.=" order by ".$e_order->get_text(); } if($h_limit){ $r.=" limit ".$e_limit->get_text(); } my $req = $dbh->prepare($r); if($req->execute()){ my $i=0; while(my $ar = $req->fetchrow_arrayref()){ $clist->append(map { (defined)?$_:"" } @{$ar}); my $tmp=join("", map { (defined)?"0":"1" } @{$ar}); $clist->set_row_data($i, \$tmp); ++$i & 31 or make_progress $pb; } }else{ msg $dbh->errstr; } $pb->hide; $go->set_sensitive($true); } sub edit_window; sub remove_records { my ($wid, $wdata) = @_; my @selection = $wdata->{clist}->selection(); my $n=scalar @selection or return; msg("Delete $n selected records?", "Yes", "!No") == 0 or return; my $req = $dbh->prepare("delete from ".$wdata->{table}." where ". join(" or ",($wdata->{primary_key}."=?") x $n)); $req->execute( map { $wdata->{clist}->get_text($_, $wdata->{primary_key_n}) } @selection); requery $wdata; } sub open_table { my $w=$tblwin{$selected_table}; if(defined $w){ $w->{win}->window->raise(); return; } my %win_data; $tblwin{$selected_table} = \%win_data; $win_data{table}=$selected_table; my $win = new Gtk::Window("toplevel"); $win_data{win}=$win; $win->set_title("[tmb] - $user\@$host - $selected_table"); $win->signal_connect("destroy", sub { $_[1]->{editor}{win}->destroy if exists $_[1]->{editor}; delete $tblwin{$_[1]->{table}}; 0 }, \%win_data); $win->set_default_size(400, 200); my $vb0 = new Gtk::VBox($false, 0); $vb0->show; $win->add($vb0); my $menubar = new Gtk::MenuBar(); $menubar->show; $vb0->pack_start($menubar, $false, $false, 0); my $menuitem; $menuitem = new Gtk::MenuItem("Insert"); $menuitem->signal_connect("activate", sub { edit_window undef, $_[1] }, \%win_data); $menuitem->show; $menubar->add($menuitem); $menuitem = new Gtk::MenuItem("Delete"); $menuitem->signal_connect("activate", \&remove_records, \%win_data); $menuitem->show; $menubar->add($menuitem); my $delete_menu=$menuitem; $menuitem = new Gtk::MenuItem("Close"); $menuitem->signal_connect("activate", sub { $_[1]->{win}->destroy }, \%win_data); $menuitem->right_justify; $menuitem->show; $menubar->add($menuitem); my $vb = new Gtk::VBox($false, 4); $vb->show; $vb->border_width(8); $vb0->pack_start($vb, $true, $true, 0); my $hb = new Gtk::HBox($false, 4); $hb->show; $vb->pack_start($hb, $false, $false, 0); my $lbl=new Gtk::Label("select * from $selected_table"); $lbl->show; my $cb_where = new_with_label Gtk::ToggleButton("where"); my $e_where = new Gtk::Entry(); $win_data{e_where}=$e_where; $win_data{h_where}=0; $cb_where->signal_connect("toggled", \&toggle, \%win_data, "where"); $e_where->signal_connect("activate", sub {$_[1]->activate_default}, $win); $cb_where->show; my $cb_order = new_with_label Gtk::ToggleButton("order by"); my $e_order = new Gtk::Entry(); $win_data{e_order}=$e_order; $win_data{h_order}=0; $win_data{cb_order}=$cb_order; $cb_order->signal_connect("toggled", \&toggle, \%win_data, "order"); $e_order->signal_connect("activate", sub {$_[1]->activate_default}, $win); $cb_order->show; my $cb_limit = new_with_label Gtk::ToggleButton("limit"); my $e_limit = new Gtk::Entry(); $win_data{e_limit}=$e_limit; $win_data{h_limit}=0; $cb_limit->signal_connect("toggled", \&toggle, \%win_data, "limit"); $e_limit->signal_connect("activate", sub {$_[1]->activate_default}, $win); $cb_limit->show; for($lbl, $cb_where, $e_where, $cb_order, $e_order, $cb_limit, $e_limit){ $hb->pack_start($_, $false, $false, 0); } my $btn = new_with_label Gtk::Button("Go!"); $btn->show; $btn->can_default(1); $hb->pack_end($btn, $false, $false, 0); $win->set_default($btn); $btn->signal_connect("clicked", sub { requery $_[1] }, \%win_data); $win_data{go}=$btn; my $sw = new Gtk::ScrolledWindow(undef, undef); $sw->set_policy( 'automatic', 'automatic' ); $sw->show(); $vb->pack_start($sw, $true, $true, 0); my $pb=new_progress; $win_data{progress}=$pb; $vb->pack_start($pb, $false, $false, 0); $win->show(); make_progress $pb; my $req = $dbh->prepare("describe $selected_table"); $req->execute(); my @fields=(); my $primary_key; my $n=0; my $key_n; while(my (@a) = $req->fetchrow_array()){ push @fields, $a[0]; if($a[3] eq "PRI"){ $primary_key = $a[0]; $key_n=$n; } $n++; make_progress $pb; } if(!@fields){ msg "Can't get field list!\n"; $win->destroy; return; } my $clist = new_with_titles Gtk::CList(@fields); for(0..$#fields){ $clist->set_column_auto_resize($_, $true); } $clist->set_selection_mode('extended'); $clist->signal_connect("button-press-event", sub { my ($w, $wdata, $ev) = @_; my $clist=$wdata->{clist}; # while(my ($a,$b) = each %{$ev}){ # print "$a -> $b\n"; # } $ev->{type} eq "2button_press" or return 0; my ($r, $c)=$clist->get_selection_info(@{$ev}{"x", "y"}); defined $r or return 0; my @undefs=split("", ${$clist->get_row_data($r)}); edit_window $r, $wdata, map { (shift(@undefs) ne "0")?undef: $clist->get_text($r, $_) } (0..$clist->columns()-1); return 1; }, \%win_data) if defined $primary_key; $clist->column_titles_active(); $clist->signal_connect("click_column", sub { my ($w, $wdata, $num) = @_; my $t=$wdata->{fields}->[$num]; if($wdata->{h_order} and $wdata->{e_order}->get_text eq $t){ $t="$t desc"; } $wdata->{e_order}->set_text($t); $wdata->{cb_order}->set_active(1); }, \%win_data); $clist->show(); $sw->add($clist); $win_data{primary_key}=$primary_key; $win_data{primary_key_n}=$key_n; $delete_menu->set_sensitive(defined $primary_key); if(!defined $primary_key){ my $lbl=new Gtk::Label("Existing rows can't be modified in TMB because this table has no primary key."); $lbl->show; $vb->pack_start($lbl, $false, $false, 0); } $win_data{clist}=$clist; $win_data{fields}=\@fields; requery \%win_data; } ################################################################################ sub editor_update; sub editor_refresh; sub edit_window { my $row=shift; my $wdata=shift; my $table=$wdata->{table}; my %win_data; if(exists $wdata->{editor}){ $wdata->{editor}{win}->destroy; } my $mode=(defined $row)?"edit":"add"; $win_data{mode}=$mode; my $win = new Gtk::Window("dialog"); $win->set_title("[tmb] - $user\@$host - editing $table"); $win->signal_connect("delete_event", sub { delete $_[0]->{editor}; 0}, $wdata); $win->border_width(8); $win->set_default_size(300, 100); $win->set_transient_for($wdata->{win}); $win_data{row}=$row; $win_data{win}=$win; $win_data{parent}=$wdata; $win_data{key}=$_[$wdata->{primary_key_n}]; $wdata->{editor}=\%win_data; my $t = new Gtk::Table(scalar @_ + 1, 3, $false); $t->set_row_spacings(4); $t->set_col_spacings(4); $t->show(); $win->add($t); my $n=0; my $prev_entry; for(@{$wdata->{fields}}){ my $label=new Gtk::Label("$_:"); $label->set_justify('right'); $label->set_alignment(1, 0.5); $label->show(); $t->attach_defaults($label, 0, 1, $n, $n+1); my $entry=new Gtk::Entry(); $win_data{"entry_$_"}=$entry; $entry->set_sensitive($_ ne $wdata->{primary_key}); $prev_entry->signal_connect("activate", sub {$_[1]->set_focus($_[2])}, $win, $entry) if defined $prev_entry; $prev_entry=$entry; my $null=new_with_label Gtk::ToggleButton("null"); $win_data{"null_$_"}=$null; my $v=undef; $v = shift if $mode eq "edit"; $win_data{"field_$_"}=$v; if(defined $v){ $entry->set_text($v); }else{ $null->set_active(1); } my $cid=$entry->signal_connect("changed", sub { $_[1]->{"changed_".$_[2]}=1; $_[1]->{"field_".$_[2]}=$_[0]->get_text; $_[1]->{"changed"}=1; $_[3]->set_active(0); }, \%win_data, $_, $null); $entry->show(); $t->attach_defaults($entry, 1, 2, $n, $n+1); $null->signal_connect("toggled", sub { my ($null, $entry, $wdata, $name, $cid)=@_; $wdata->{changed}=1; $wdata->{"changed_$name"}=1; if($null->active){ $entry->signal_handler_block($cid); $entry->set_text(""); $entry->signal_handler_unblock($cid); $wdata->{"field_$name"}=undef; }else{ $wdata->{"field_$name"}=$entry->get_text; } }, $entry, \%win_data, $_, $cid); $null->show(); $t->attach_defaults($null, 2, 3, $n, $n+1); ++$n; } defined $prev_entry and $prev_entry->signal_connect("activate", sub { $_[1]->window->raise }, $win); my $sep=new Gtk::HSeparator(); $sep->show; $t->attach_defaults($sep, 0, 3, $n, $n+1); ++$n; my $hb = new Gtk::HBox($true, 4); $hb->show(); $t->attach_defaults($hb, 0, 3, $n, $n+1); if($mode eq "edit"){ my $btn = new Gtk::Button("Ok"); $btn->signal_connect("clicked", sub { editor_update $_[1] or return; delete $_[1]->{parent}->{editor}; $_[1]->{win}->destroy }, \%win_data); $btn->show(); $hb->pack_start($btn, $true, $true, 0); $btn->can_default(1); $win->set_default($btn); $btn = new Gtk::Button("Apply"); $btn->signal_connect("clicked", sub{ editor_update $_[1] }, \%win_data); $btn->show(); $hb->pack_start($btn, $true, $true, 0); $btn->can_default(1); $btn = new Gtk::Button("Refresh"); $btn->signal_connect("clicked", sub { editor_refresh $_[1]},\%win_data); $btn->show(); $hb->pack_start($btn, $true, $true, 0); $btn->can_default(1); }else{ my $btn = new Gtk::Button("Ok"); $btn->signal_connect("clicked", sub { editor_update $_[1] or return; requery $_[1]->{parent} }, \%win_data); $btn->show(); $hb->pack_start($btn, $true, $true, 0); $btn = new Gtk::Button("Next"); $btn->signal_connect("clicked", sub{ editor_update $_[1] or return; for(@{$_[1]->{parent}->{fields}}){ $_[1]->{"null_$_"}->set_active(1); } }, \%win_data); $btn->show(); $hb->pack_start($btn, $true, $true, 0); $btn->can_default(1); $win->set_default($btn); $btn = new Gtk::Button("Cancel"); $btn->signal_connect("clicked", sub { requery $_[1]->{parent} }, \%win_data); $btn->show(); $hb->pack_start($btn, $true, $true, 0); } $win->show(); } sub editor_update { my $table=$_[0]->{parent}->{table}; my $mode=$_[0]->{mode}; if($mode eq "add"){ my $req=$dbh->prepare("insert into $table values(".join(",", ("?") x scalar(@{$_[0]->{parent}->{fields}})).")"); if(!$req->execute( map { $_[0]->{"field_$_"} } @{$_[0]->{parent}->{fields}} )){ msg $dbh->errstr; return 0; } return 1; } $_[0]->{changed} or return 1; delete $_[0]->{changed}; my $key_field=$_[0]->{parent}->{primary_key}; my @changes=(); my @args=(); my $i=0; for(@{$_[0]->{parent}->{fields}}){ if(exists $_[0]->{"changed_$_"}){ delete $_[0]->{"changed_$_"}; my $v=$_[0]->{"field_$_"}; if(defined $v){ push @changes, "$_ = ?"; push @args, $v; }else{ push @changes, "$_ = NULL"; $v=""; } $_[0]->{parent}->{clist}->set_text($_[0]->{row}, $i, $v); } ++$i; } my $tmp = join("", map { (defined $_[0]->{"field_$_"})?"0":"1" } @{$_[0]->{parent}->{fields}}); $_[0]->{parent}{clist}->set_row_data($_[0]->{row}, \$tmp); my $req = $dbh->prepare("update $table set ".join(", ", @changes)." where $key_field=?"); if($req->execute(@args, $_[0]->{key})){ return 1; }else{ msg $dbh->errstr; return 0; } } sub editor_refresh { my $table=$_[0]->{parent}->{table}; my $key_field=$_[0]->{parent}->{primary_key}; my $req = $dbh->prepare("select * from $table where $key_field=?"); $req->execute($_[0]->{key}); my @values = $req->fetchrow_array(); for(@{$_[0]->{parent}->{fields}}){ my $v=shift @values; $_[0]->{"field_$_"}=$v; if(defined $v){ $_[0]->{"entry_$_"}->set_text($v); }else{ $_[0]->{"null_$_"}->set_active(1); } delete $_[0]->{"changed_$_"}; } delete $_[0]->{changed}; } ################################################################################ sub sql_window { my %win_data; my $win = new Gtk::Window("toplevel"); $win_data{win}=$win; $win->set_title("[tmb] - $user\@$host - sql window"); $win->border_width(8); $win->set_default_size(400, 200); my $vb = new Gtk::VBox($false, 4); $vb->show; $win->add($vb); my $hb = new Gtk::HBox($false, 4); $hb->show; $vb->pack_start($hb, $false, $false, 0); my $lbl = new Gtk::Label("SQL query:"); $lbl->show; $hb->pack_start($lbl, $false, $false, 0); my $e_sql = new Gtk::Entry(); $e_sql->signal_connect("activate", sub { $_[1]->activate_default }, $win); $e_sql->show; $hb->pack_start($e_sql, $true, $true, 0); my $btn = new_with_label Gtk::Button("Go!"); $btn->show; $btn->can_default(1); $hb->pack_end($btn, $false, $false, 0); $win->set_default($btn); $btn->signal_connect("clicked", sub { sql_requery(@_[1,2]) }, \%win_data, $e_sql); $win_data{go}=$btn; my $sw = new Gtk::ScrolledWindow(undef, undef); $sw->set_policy( 'automatic', 'automatic' ); $sw->show(); $vb->pack_start($sw, $true, $true, 0); $lbl = new Gtk::Label("type a query and click 'Go!' or press enter"); $lbl->show; $sw->add_with_viewport($lbl); my $pb=new_progress; $win_data{progress}=$pb; $vb->pack_start($pb, $false, $false, 0); $win_data{sw}=$sw; $win->show(); } sub sql_requery { my ($wdata, $e_qry) = @_; defined $wdata->{sw}->child() and $wdata->{sw}->child()->destroy; make_progress $wdata->{progress}; my $req = $dbh->prepare($e_qry->get_text()); if(!$req->execute()){ my $lbl = new Gtk::Label($dbh->errstr); $lbl->show; $wdata->{sw}->add_with_viewport($lbl); $wdata->{progress}->hide; return; } if(!$req->{NUM_OF_FIELDS}){ my $lbl = new Gtk::Label("Empty output set"); $lbl->show; $wdata->{sw}->add_with_viewport($lbl); $wdata->{progress}->hide; return; } make_progress $wdata->{progress}; my $clist = new_with_titles Gtk::CList(@{$req->{NAME}}); for(0..$clist->columns()-1){ $clist->set_column_auto_resize($_, $true); } $clist->show; $wdata->{sw}->add($clist); my $i=0; while(my $ar = $req->fetchrow_arrayref){ $clist->append(map { (defined)?$_:"" } @{$ar}); ++$i & 31 or make_progress $wdata->{progress}; } $wdata->{progress}->hide; } ################################################################################ login_window(); tables_window();