package QWizard::Generator::Gtk2;

use strict;
my $VERSION = '3.11';
use Gtk2 -init;
require Exporter;
use QWizard::Generator;
use IO::File;

@QWizard::Generator::Gtk2::ISA = qw(Exporter QWizard::Generator);

# use strict yells if we use an unquoted FALSE
use Glib qw(FALSE TRUE);

my $have_gd_graph = eval { require GD::Graph::lines; };

sub new {
    my $type = shift;
    my ($class) = ref($type) || $type;
    my $self = {'keep_working_hook' => \&QWizard::Generator::backup_params};

    # collect arguments
    for (my $i = 0; $i <= $#_; $i += 2) {
	$self->{$_[$i]} = $_[$i+1];
    }
    bless($self, $class);

    # register our various handlers
    $self->add_handler('text',\&QWizard::Generator::Gtk2::do_entry,
		       [['single','name'],
			['default'],
			['forced','0'],
			['single','size'],
			['single','maxsize'],
			['single','submit'],
			['single','refresh_on_change']]);
    $self->add_handler('checkbox',\&QWizard::Generator::Gtk2::do_checkbox,
		       [['multi','values'],
			['default'],
			['single','name'],
			['single','submit'],
			['single','refresh_on_change'],
			['single','button_label']]);
    $self->add_handler('label',
		       \&QWizard::Generator::Gtk2::do_label,
		       [['multi','values']]);
    $self->add_handler('radio',
		       \&QWizard::Generator::Gtk2::do_radio,
		       [['values,labels', "   "],
			['default'],
			['single','name'],
			['single','submit'],
			['single','refresh_on_change'],
			['single','icons'],
			['single','noiconpadding'],
		       ]);
    $self->add_handler('paragraph',
		       \&QWizard::Generator::Gtk2::do_paragraph,
		       [['multi','values'],
			['single','preformatted'],
			['single','width']]);
    $self->add_handler('hidetext',\&QWizard::Generator::Gtk2::do_entry,
		       [['single','name'],
			['default'],
			['forced','1'],
			['single','size'],
			['single','maxsize'],
			['single','submit'],
			['single','refresh_on_change']]);
    $self->add_handler('textbox',\&QWizard::Generator::Gtk2::do_textbox,
		       [['single','name'],
			['default'],
			['single','size'],
			['single','maxsize'],
			['single','submit'],
			['single','refresh_on_change']]);
    $self->add_handler('menu',
		       \&QWizard::Generator::Gtk2::do_menu,
		       [['values,labels'],
			['default'],
			['single','name'],
			['single','submit'],
			['single','refresh_on_change']]);
    $self->add_handler('unknown',
		       \&QWizard::Generator::Gtk2::do_unknown,
		       []);
    $self->add_handler('table',
		       \&QWizard::Generator::Gtk2::do_table,
		       [['norecurse','values'],
			['norecurse','headers']]);
    $self->add_handler('bar',
		       \&QWizard::Generator::Gtk2::do_bar,
		       [['norecurse','values']]);
    $self->add_handler('image',
		       \&QWizard::Generator::Gtk2::do_image,
		       [['norecurse','imgdata'],
			['norecurse','image'],
			['single','imagealt'],
			['single','addscalebuttons']]);
    $self->add_handler('graph',
		       \&QWizard::Generator::Gtk2::do_graph,
		       [['norecurse','values'],
			['norecursemulti','graph_options']]);
    $self->add_handler('multi_checkbox',
		       \&QWizard::Generator::Gtk2::do_multicheckbox,
		       [['multi','default'],
			['values,labels'],
			['single','refresh_on_change']]);
    $self->add_handler('button',
		       \&QWizard::Generator::Gtk2::do_button,
		       [['single','values'],
			['default'],
			['single','icon']]);
    $self->add_handler('fileupload',
		       \&QWizard::Generator::Gtk2::do_fileupload,
		       [['single','name'],
			['default']]);
    $self->add_handler('filedownload',
		       \&QWizard::Generator::Gtk2::do_filedownload,
		       [['single','name'],
			['default'],
			['single','data'],
			['noexpand','datafn']]);
    $self->init_default_storage();

    # force some default image search paths
    print "$self->{'imgpaths'}\n";
    $self->{'imgpaths'} = [$self->{'imgpaths'}]
      if (ref($self->{'imgpaths'}) ne 'ARRAY');
    push @{$self->{'imgpaths'}}, '/usr/share/icons/Bluecurve/16x16/stock/';
    return $self;
}

sub our_exit {
    Gtk2->main_quit;
    exit;
}

sub create_qw_label {
    my ($self, $text, $indent, $icon, $activatewidget, $noimagespacing) = @_;
    my $label = Gtk2::Label->new($text);
    $label->set_line_wrap(TRUE);
    $label->set_justify('GTK_JUSTIFY_LEFT');
    $label->set_padding(($indent ? 30 : 10), 1);
    $label->set_alignment(0, 0);
    $label->set_selectable(TRUE);

    # set with an accelerator if we have a widget to bind to
    if ($activatewidget) {
	$label->set_markup_with_mnemonic($text);
	$label->set_mnemonic_widget($activatewidget);
    }

    my $iconpath = $self->find_image_path($icon);

    # no icon?  -> We're done
    if (!$icon || ! -f $iconpath) {
	my ($padx, $pady) = $label->get_padding();
	$label->set_padding($padx + $noimagespacing, $pady);
	# if both wantarray && they requested an icon, return an array
	# (can't use just wantarray since func(create_qw_label("hi"))
	# doesn't work for simple calling usage)
	return (undef, $label, undef) if (wantarray && $icon);
	return $label;
    }

    # if an icon is requested then create an icon to display to the
    # left of the text
    my $hbox = Gtk2::HBox->new(FALSE, 6);
    my $image = Gtk2::Image->new_from_file($iconpath);
    $hbox->pack_start(Gtk2::Image->new_from_file($iconpath), FALSE, FALSE, 0)
      if ($iconpath);
    $hbox->pack_start($label, FALSE, FALSE, 0);

    return ($hbox, $label, $image) if (wantarray);
    return $hbox;
}

sub goto_top {
    my $self = shift;
    $self->{'generator'}->remove_all_table_entries();
    $self->{'qwizard'}->reset_qwizard();
    Gtk2->main_quit();
}

sub call_callbacks {
    # call callbacks
    if (exists($_[0]->{'qwsubwidgets'})) {
	foreach my $subwid (@{$_[0]->{'qwsubwidgets'}}) {
	    # XXX: pass $_[1] or something to remove or is it auto-cleaned?
	    call_callbacks($subwid);
	}
    }
    if (exists($_[0]->{'qwend'})) {
	$_[0]->{'qwend'}->($_[0]);
    }
    # remove widget
    if ($_[1]) {
	$_[1]->remove($_[0]);
    }
}

# for each widget in a table, allow a callback to be run
sub remove_table_entries {
    my $tbl = shift;
    $tbl->foreach(\&call_callbacks, $tbl);
}

# for each of the tables that populated the last screen, allow
# callbacks for each of them to to be run.
sub remove_all_table_entries {
    my $self = shift;
    foreach my $table (@{$self->{'tables'}}) {
	remove_table_entries($table);
    }

    # if we had previous multiple tables, we need to delete all but
    # the first one.
    while ($#{$self->{'removals'}} > 0) {
	my $obj = pop(@{$self->{'removals'}});
	my $from = $obj->{'removefrom'} || $self->{'vbox'};
	$from->remove($obj);
    }
    @{$self->{'tables'}} = ($self->{'tables'}[0]);
    @{$self->{'frames'}} = ($self->{'frames'}[0]);
    @{$self->{'removals'}} = ($self->{'removals'}[0]);
    $self->{'qtable'} = $self->{'tables'}[0];
    $self->{'qframe'} = $self->{'frames'}[0];
}

# Note: NOT called as a generator-> object.  First arg is expected to
# be a widget.
sub goto_refresh {
    my $self = shift;
    $self->{'generator'}->qwparam('redo_screen',1);
    goto_next($self, @_);
}

# Note: NOT called as a generator-> object.  First arg is expected to
# be a widget.
sub goto_next {
    my $self = shift;
    my $generator = $self->{'generator'};
    if ($self->{'qbuttonname'}) {
	$self->{'generator'}->qwparam($self->{'qbuttonname'},
				      $self->{'qbuttonval'});
    }
    $generator->remove_all_table_entries();
    $generator->{'window'}->window->set_cursor($generator->{'pausecursor'});
    Gtk2->main_quit();
}

# Note: NOT called as a generator-> object.  First arg is expected to
# be a widget.
sub goto_prev {
    my ($self) = @_;
    $self->{'generator'}->revert_params();
    $self->{'generator'}->remove_all_table_entries();
    Gtk2->main_quit();
}

sub our_mainloop {
    my ($self, $wiz, $p) = @_;
    $self->{'progwindow'}->hide if ($self->{'progwindow'});
    $self->{'window'}->show_all;
    if ($self->{'nointro'}) {
	$self->{'introframe'}->hide;
	$self->{'gtk2intro'}->hide;
    }
    if ($self->qwparam('allow_refresh') ||
	$wiz->{'allow_refresh'} ||
	$p->{'allow_refresh'}) {
	$self->{'refreshbut'}->show();
    } else {
	$self->{'refreshbut'}->hide();
    }
    $self->{'window'}->window->set_cursor($self->{'normalcursor'});
    $self->{'mainscrolledwindow'}->get_vscrollbar()->set_value(0);
    Gtk2->main;
}

sub add_qframe {
    my ($self, $title, $where) = @_;
    $where ||= $self->{'vbox'};
    $self->{'qtable'} = Gtk2::Table->new(3, $self->{'qtable_height'}, FALSE);
    push @{$self->{'tables'}}, $self->{'qtable'};
    if (defined($title)) {
	$self->{'qframe'} = Gtk2::Frame->new($title);
	$self->{'qframe'}->set_border_width(5);
	$self->{'qtable_height'} = 10;
	$self->{'qframe'}->add($self->{'qtable'});
	$self->{'qframe'}{'removefrom'} = $where;
	$where->pack_start($self->{'qframe'}, FALSE, FALSE, 0);
	push @{$self->{'frames'}}, $self->{'qframe'};
	push @{$self->{'removals'}}, $self->{'qframe'};
    } else {
	$where->pack_start($self->{'qtable'}, FALSE, FALSE, 0);
	$self->{'qtable'}{'removefrom'} = $where;
	push @{$self->{'removals'}}, $self->{'qtable'};
    }
}

sub get_scrolled_vbox {
    my ($self) = shift;

    my $sw = Gtk2::ScrolledWindow->new();
    $sw->set_policy('automatic','automatic');

    my $vb = Gtk2::VBox->new(FALSE, 6);
    $sw->add_with_viewport($vb);

    return ($vb, $sw);
}

sub init_screen {
    my ($self, $wiz, $title) = @_;
    if (!$self->{'window'}) {
	
	# create the window object itself
	$self->{'window'} = Gtk2::Window->new('toplevel');
	$self->{'window'}->set_title($title);
	$self->{'window'}->set_border_width(5);
	$self->{'window'}->set_default_size(900,650);
	$self->{'window'}->signal_connect(delete_event => \&our_exit);
#	$self->{'window'}->signal_connect(destroy => \&our_exit);
	
	# create cursors
	$self->{'pausecursor'} = Gtk2::Gdk::Cursor->new('watch');
	$self->{'normalcursor'} = Gtk2::Gdk::Cursor->new('arrow');

	## parentvbox: the master vbox
	# contains the topbar, the main widgets (mainhbox), and the buttons
	$self->{'parentvbox'} = Gtk2::VBox->new(FALSE, 6);
	$self->{'window'}->add($self->{'parentvbox'});

	## the top bar
	# is a table
	$self->{'topbar'} = Gtk2::Table->new(4,4,FALSE);
	$self->{'parentvbox'}->pack_start($self->{'topbar'}, FALSE, FALSE, 0);

	# mainhbox: The main widget section, after the title
	$self->{'mainhbox'} = Gtk2::HBox->new(FALSE,3);
	$self->{'parentvbox'}->pack_start($self->{'mainhbox'}, TRUE, TRUE, 0);
	
	## packing the left/center/right frames
	# children of the mainhbox
	#
	# the left frame of the hbox
	$self->{'outerpane'} = Gtk2::HPaned->new();
	$self->{'mainhbox'}->pack_start($self->{'outerpane'},
					TRUE, TRUE, 0);
	$self->{'mainleftpane'} = Gtk2::HPaned->new();
	$self->{'outerpane'}->pack1($self->{'mainleftpane'}, TRUE, TRUE);

	($self->{'leftvbox'}, 
	 $self->{'leftscrolledwindow'}) = $self->get_scrolled_vbox();
	
	  #Gtk2::VBox->new(FALSE,3);
	$self->{'mainleftpane'}->pack1($self->{'leftscrolledwindow'},
				       FALSE, TRUE);
	$self->{'lefttable'} = Gtk2::Table->new(4,4,FALSE);
	$self->{'leftvbox'}->pack_start($self->{'lefttable'}, FALSE, FALSE, 0);

	# the center frame box
	$self->{'centervbox'} = Gtk2::VBox->new(FALSE,3);
	$self->{'mainleftpane'}->pack2($self->{'centervbox'}, TRUE, TRUE);

	# the right frame of the hbox
	($self->{'rightvbox'},
	 $self->{'rightscrolledwindow'}) = $self->get_scrolled_vbox();
	
	$self->{'outerpane'}->pack2($self->{'rightscrolledwindow'},
				    FALSE, TRUE);
	$self->{'righttable'} = Gtk2::Table->new(4,4,FALSE);
	$self->{'rightvbox'}->pack_start($self->{'righttable'},
					 FALSE, FALSE, 0);

	## packing the center widget box

	# the title name
	$self->{'gtk2title'} = Gtk2::Label->new($title);
	$self->{'centervbox'}->pack_start($self->{'gtk2title'},
					  FALSE, FALSE, 0);

	# the scrolled window that makes up the questions
	($self->{'vbox'}, $self->{'mainscrolledwindow'}) =
	  $self->get_scrolled_vbox('main', $self->{'centervbox'});

	$self->{'centervbox'}->pack_start($self->{'mainscrolledwindow'},
					  TRUE, TRUE, 0);

	$self->{'introframe'} = Gtk2::Frame->new();
	$self->{'gtk2intro'} = $self->create_qw_label('');
	$self->{'gtk2intro'}->set_max_width_chars(80);
	$self->{'gtk2intro'}->set_line_wrap(TRUE);
	# the introduction
	$self->{'introframe'}->add($self->{'gtk2intro'});
	$self->{'vbox'}->pack_start($self->{'introframe'}, FALSE, FALSE, 0);

	# bottom row buts done in do_ok_cancel
	# get added as children of the parentvbox to be full width on bottom

	$self->add_qframe('');
    }
    $self->initialize_auto_accelerator();
}

sub process_gtk2_events {
    Gtk2->main_iteration_do(FALSE); # once at least to be sure
    while (Gtk2->events_pending) {
	Gtk2->main_iteration_do(FALSE);
    }
    Gtk2::Gdk->flush;
}

sub finished {
    my $self = shift;
    if ($self->{'window'}) {
	$self->{'window'}->hide();
	
	$self->process_gtk2_events;
    }
}

sub set_progress {
    my ($self, $progress, $text) = @_;
    if (!$self->{'progress'}) {
	$self->{'progwindow'} = Gtk2::Window->new('toplevel');
	$self->{'progress'} = Gtk2::ProgressBar->new;
	return if (!$self->{'progress'} || !$self->{'progress'});
	$self->{'progwindow'}->add($self->{'progress'});
	$self->{'progwindow'}->set_title("Progress");
	my ($x, $y) = $self->{'window'}->get_position();
	my ($w, $h) = $self->{'window'}->get_size();
	$self->{'progwindow'}->move($x + int($w/2), $y + int($h/2));
	$self->{'progwindow'}->show_all;
    }
    $self->{'progress'}->set_text($text || (int(100*$progress) . "%"));
    $self->{'progress'}->show();
    $self->{'progress'}->set_fraction($progress);
    $self->process_gtk2_events;
}

#
# note: new widgets added here and memorized for if() statements need to be
# discarded from the self hash in finished();
#
sub do_ok_cancel {
  my ($self, $nexttext, $wiz, $p) = @_;
  if (!$self->{'bot'}) {
      my $hb;
      $self->{'bot'} = Gtk2::HBox->new (FALSE, 6);
      $self->{'bot'}->set_border_width(3);
      $self->{'parentvbox'}->pack_start($self->{'bot'}, FALSE, FALSE, 0);

      if (!$self->{'prevbut'}) {
	  $self->{'prevbut'} = Gtk2::Button->new();
	  ($hb, $self->{'prevbutlab'}, $self->{'prevbuticon'}) =
	    $self->create_qw_label($wiz->{'back_text'} || '_Back',
			    FALSE, 'gtk-go-back.png', $self->{'prevbut'});
	  $self->{'prevbut'}->add($hb || $self->{'prevbutlab'});

	  $self->{'bot'}->pack_start($self->{'prevbut'}, FALSE, FALSE, 0);
	  $self->{'prevbut'}->signal_connect(clicked => \&goto_prev);
	  $self->{'prevbut'}->{'generator'} = $self;
      }
      if (!$self->{'nextbut'}) {
	  $self->{'nextbut'} = Gtk2::Button->new();
	  ($hb, $self->{'nextbutlab'}, $self->{'nextbuticon'}) =
	    $self->create_qw_label($nexttext || $wiz->{'next_text'} || '_Next',
			    FALSE, 'gtk-go-forward.png', $self->{'nextbut'});
	  $self->{'nextbut'}->add($hb || $self->{'nextbutlab'});
	  $self->{'bot'}->pack_start($self->{'nextbut'}, FALSE, FALSE, 0);
	  $self->{'nextbut'}->signal_connect(clicked => \&goto_next);
	  $self->{'nextbut'}->{'generator'} = $self;
      }
      if (!$self->{'refreshbut'}) {
	  $self->{'refreshbut'} = Gtk2::Button->new();
	  ($hb, $self->{'refreshbutlab'}, $self->{'refreshbuticon'}) =
	    $self->create_qw_label('_Refresh',
			    FALSE, 'gtk-refresh.png', $self->{'refreshbut'});
	  $self->{'refreshbut'}->add($hb || $self->{'refreshbutlab'});

	  $self->{'refreshbut'} = Gtk2::Button->new('_Refresh');
	  $self->{'bot'}->pack_start($self->{'refreshbut'}, FALSE, FALSE, 0);
	  $self->{'refreshbut'}->signal_connect(clicked => \&goto_refresh);
	  $self->{'refreshbut'}->{'generator'} = $self;
      }
      if (!$self->{'canbut'} && !$self->{'no_cancel'}) {
	  $self->{'canbut'} = Gtk2::Button->new();
	  ($hb, $self->{'cancelbutlab'}, $self->{'cancelbuticon'}) =
	    $self->create_qw_label($wiz->qwparam('QWizard_Cancel') ||
			    $wiz->{'cancel_text'} || 'Cancel',
			    FALSE, 'gtk-cancel.png', $self->{'cancelbut'});
	  $self->{'canbut'}->add($hb || $self->{'cancelbutlab'});

	  $self->{'bot'}->pack_end($self->{'canbut'}, FALSE, FALSE, 0);
	  $self->{'canbut'}->signal_connect(clicked => \&goto_top);
	  $self->{'canbut'}->{'generator'} = $self;
	  $self->{'canbut'}->{'qwizard'} = $wiz;
      }
  } else {
      $self->{'nextbutlab'}->set_markup_with_mnemonic($nexttext || 'Ok');
  }
}


# put stuff at a particular spot in the current table
sub put_it {
    my ($self, $w, $row, $col) = @_;

    # try and determine the correct spot to put something in the
    # resulting widget table.  Callers could have passed a spot
    # (rare), or we might be slowly building and incromenteing
    # counters to get to the right spot.
    if (!$row) {
	if (exists($self->{'currentrow'}) && defined($self->{'currentrow'})) {
	    $row = $self->{'currentrow'};
	} else {
	    $row = $self->{'currentq'};
	}
    }
    if (!$col) {
	if (exists($self->{'currentcol'}) && defined($self->{'currentcol'})) {
	    $col = $self->{'currentcol'};
	} else {
	    $col = 2;
	}
    }

    # If the widget is a simple string, then we create a label out of it.
    if (ref($w) eq '') {
	$w = $self->create_qw_label($w);
	$w->set_alignment(0, 0) if ($col == 1);
    }

    # Finally, place the widget in the table at the calculated spot
    # column 1 is filled, and all others are fill and expanded both

    if (ref($self->{'qtable'}) eq 'Gtk2::Table') {
	# Parent widget is a table.
	# Pack it in our calculated spot
	$self->{'qtable'}->attach($w, $col, $col+1, $row, $row+1,
				  ($col == 1 ? [qw(fill)] : [qw(fill)]),
				  ($col == 1 ? [qw(fill)] : [qw(fill)]),
#				  ($col == 1 ? [qw(fill)] : [qw(fill expand)]),
#				  ($col == 1 ? [qw(fill)] : [qw(fill expand)]),
				  ($col == 1 ? 1 : 0),
				  0);
    } elsif (ref($self->{'qtable'}) =~ /Gtk2::.Box/) {
	# container is a box.  Packet it at the start.
	# XXX: allow filling/expanding/etc.
	$self->{'qtable'}->pack_start($w, FALSE, FALSE, 0);
	print "box:    " . ref($self->{'qtable'}) . "\n";
    } else {
	# assume a generic single container.  Probably shouldn't be
	# getting here.
	$self->{'qtable'}->add($w);
    }

    # Remember the last widget.
    # 1) used for tables that need to remember a list of sub-widgets
    # 2) XXX: future key bindings
    $self->{'lastwidget'} = $w;

#     # bind the tab and alt-tab key presses to forward and backward widgets
#     if (ref($w) =~ /Entry|Menu|Text|Button|Checkbutton|Radio|Optionmenu/) {
# 	if ($self->{'lastw'}) {
# 	    $self->{'lastw'}->bind('<Tab>',[\&tab_next, $w, $self]);
# 	    $w->bind('<Alt-Key-Tab>',[\&tab_next, $self->{'lastw'}, $self]);
# 	}
# 	$self->{'lastw'} = $w;
#     }
}

sub set_default {
    my ($self, $q, $def) = @_;
    $self->qwparam($q->{'name'}, $def) if ($def && $self->qwparam($q->{'name'}) ne $def);
}

######################################################################
# QWizard functions for doing stuff.

sub wait_for {
  my ($self, $wiz, $next, $p) = @_;
  $self->do_ok_cancel($next, $wiz, $p);
  $self->our_mainloop($wiz, $p);

  return 1;
}

sub do_error {
    my ($self, $q, $wiz, $p, $err) = @_;
    $self->{'currentq'}++;
    $self->{'qadd'}++;
    # XXX: make font red
    my $lb = $self->create_qw_label($err);
    $lb->set_markup("<span weight=\"bold\" foreground=\"red\">$err</span>");
    $self->put_it($lb, undef, 2);
}

sub do_question {
    my ($self, $q, $wiz, $p, $text, $qcount) = @_;
    my $top = $self->{'qtable'};
    my $l;
    $self->{'currentq'} = $qcount + $self->{'qadd'};

    #
    # Get the actual help text, in case this is a subroutine.
    #
    my $helpdesc = $q->{'helpdesc'};
    if (ref($helpdesc) eq "CODE") {
	$helpdesc = $helpdesc->();
    }

    # add the question
    my $hf = Gtk2::HBox->new();
    $l = $self->create_qw_label($text, $q->{'indent'});
    # pulled from something else...  use or not?
    # $l->set_alignment(0, 0);
    $self->{'lastquestion'} = $l;
    $self->{'lasttext'} = $text;
    $hf->pack_start($l, FALSE,FALSE,0);

    # add a help bubble if needed
    if ($q->{'helptext'}) {
	# XXX Ideally make this button smaller.
	my $but = Gtk2::Button->new('[?]');
	$but->set_relief('none');
	$but->signal_connect(clicked => \&do_helptext_button);
	$but->{'helptext'} = $q->{'helptext'};
	$but->{'generator'} = $self;
	$but->set_border_width(0);
	$hf->pack_start($but, FALSE, FALSE, 0);
    }

    if ($helpdesc) {
#	if (!$self->qwpref('usehelpballons')) {
	my $f = Gtk2::VBox->new();

	$f->set_spacing(0);
	$l->set_padding(($q->{'indent'} ? 30 : 10), 0);
	$f->pack_start($hf, FALSE,FALSE,0);
	my $helpl = $self->create_qw_label($helpdesc, $q->{'indent'});
	$helpl->set_padding(($q->{'indent'} ? 40 : 20), 0);
	$f->pack_start($helpl, FALSE, FALSE, 0);
	$self->put_it($f, undef, 1);
	#	} else {
	#	    # XXX: help bubble?
	#	    $self->put_it($hf, undef, 1);
	#	}
    } else {
	$self->put_it($hf, undef, 1);
    }
}

sub do_helptext_button {
    my $but = shift;
    my $self = $but->{'generator'};

    # get the text
    my $helptext = $but->{'helptext'};
    if (ref($helptext) eq "CODE") {
	$helptext = $helptext->();
    }

    my $win;
    $win = Gtk2::Window->new('toplevel');
    $win->set_title("Help");
    $win->set_border_width(5);
    $win->set_default_size(300,200);

    my $p = $self->create_qw_label($helptext);
    $p->set_line_wrap(FALSE);
    $win->add($p);
    $win->show_all;
}

sub start_primaries {
    my $self = shift;

    # forget about past side counts
    delete $self->{'sides'};
}

sub end_primaries {
    my $self = shift;
    if (!$self->{'sides'}{'righttable'}) {
	$self->{'oldrightpos'} = $self->{'outerpane'}->get_position();
	$self->{'outerpane'}->set_position(4000);
    } else {
	my ($w, $h) = $self->{'window'}->get_size();
	$self->{'outerpane'}->set_position($self->{'oldrightpos'} || $w-200);
    }

    if (!$self->{'sides'}{'lefttable'}) {
	$self->{'oldleftpos'} = $self->{'mainleftpane'}->get_position();
	$self->{'mainleftpane'}->set_position(0);
    } else {
	$self->{'mainleftpane'}->set_position($self->{'oldleftpos'} || 200);
    }
}



sub start_questions {
    my ($self, $wiz, $p, $title, $intro) = @_;
    $self->{'gtk2title'}->set_markup("<span size=\"x-large\" underline=\"single\">$title</span>");
#    $self->{'gtk2title'}->set_pattern("_" x length($title));
    if ($intro) {
	$self->{'gtk2intro'}->set_text($intro);
	$self->{'nointro'} = 0;
    } else {
	# GRR...  can't hide here since a show_all comes later.
	$self->{'gtk2intro'}->set_label('');
	$self->{'nointro'} = 1;
    }

    $self->{radiogroups} = {};
	
    return;
    # XXX: intro
    $self->{'qintro'}->delete('1.0','end');
    if ($intro) {
	$self->{'qintro'}->configure(-height => length($intro)/80 + 1);
	$self->{'qintro'}->insert('end',$intro);
    } else {
	$self->{'qintro'}->configure(-height => 0);
    }
}

sub end_questions {
    my $self = shift;
    # this makes us keep adding new table rows during a merge
    $self->{'qadd'} = $self->{'currentq'} + 1;
    $self->{'lastw'} = undef;
}


##################################################
# widgets
##################################################

sub start_bar {
    my ($self, $wiz, $name) = @_;

    $self->add_qframe($name);
    $self->{'inbar'} = 1;
}

sub end_bar {
    my ($self, $wiz, $name) = @_;

    $self->add_qframe($name);
    delete($self->{'inbar'});
}

sub do_bar {
    my ($self, $q, $wiz, $p, $widgets) = @_;

    $self->start_bar($wiz, undef);
    $self->do_a_table([$widgets], $self->{'qtable'}, -1, $wiz, $q, $p);
    $self->end_bar($wiz, '');
}

sub do_top_bar {
    my ($self, $q, $wiz, $p, $widgets) = @_;

    my $oldtable = $self->{'qtable'};
    $self->{'qtable'} = $self->{'topbar'};
    push @{$self->{'tables'}}, $self->{'qtable'};
    $self->do_a_table([$widgets], $self->{'qtable'}, -1, $wiz, $q, $p);
    $self->{'qtable'} = $oldtable;
}

sub do_side {
    my ($self, $spot, $q, $wiz, $p, $widgets) = @_;

    my $oldtable = $self->{'qtable'};
    $self->{'qtable'} = $self->{$spot};

    push @{$self->{'tables'}}, $self->{'qtable'};

    my @tableinfo;
    foreach my $w (@$widgets) {
	next if (!$w);
	if (ref($w) eq 'ARRAY') {

	    # special side grouping
	    # XXX: make border stand out.

	    my $title = "";

	    my $sidetable = $self->{'qtable'};
	    $title = shift(@$w) if (ref($w->[0]) eq '');

	    $self->add_qframe($title, $self->{'leftvbox'});

	    if (0) {
		# old OTHER way to create a box
		my $vb = Gtk2::VBox->new(FALSE, 10);
		my $sidetable = $self->{'qtable'};
		$self->{'qtable'} = $vb;

		# An encapsulated set maybe with a title
		if (ref($w->[0]) eq '') {
		    my $title = $self->create_qw_label("");
		    $title->set_markup("<span size=\"large\" underline=\"single\">" . shift(@$w) . "</span>");
		    $vb->pack_start($title, FALSE, FALSE, 0);
		}
	    }

	    foreach my $widget (@$w) {
		#print "widget: $widget\n";
		$wiz->ask_question($p, $widget);
	    }
	    $self->{'qtable'} = $sidetable;

	    ## OLD $self->{'leftvbox'}->pack_start($vb, FALSE, FALSE, 0);
	} else {
	    push @tableinfo, [$w];
	}
	$self->{'sides'}{$spot}++;
    }

    $self->do_a_table(\@tableinfo, $self->{'qtable'}, -1, $wiz, $q, $p);
    $self->{'qtable'} = $oldtable;
}

sub do_left_side {
    my $self = shift;
    $self->do_side('lefttable', @_);
}

sub do_right_side {
    my $self = shift;
    $self->do_side('righttable', @_);
}

sub do_button {
    my ($self, $q, $wiz, $p, $vals, $def, $icon) = @_;
    $vals = $self->add_accelerator($vals);

    my $but = Gtk2::Button->new();
    my $butlab = 
      $self->create_qw_label($vals, FALSE, $icon, $but);
    $butlab->set_padding(3, 0);
    $but->add($butlab);

    $but->signal_connect(clicked => \&goto_next);
    $but->{'qbuttonname'} = $q->{'name'};
    $but->{'qbuttonval'} = $def || $q->{'default'}; # XXX: hack for refresh; doesn't deal with code refs properly though.  ugh.
    $but->{'generator'} = $self;
    $self->put_it($but);
}

sub do_filedownload {
    my ($self, $q, $wiz, $p, $name, $def, $data, $datafn) = @_;

    # A file upload box is created via a button to request a file.

    my $but = Gtk2::Button->new($def || 'Output To File...');
    $but->signal_connect(clicked => \&create_filedownload_screen);
    $but->{'qbuttonname'} = $q->{'name'};
    $but->{'qbuttonval'} = $def;
    $but->{'generator'} = $self;
    $but->{'parent_button'} = $but;
    $but->{'data'} = $data;
    $but->{'datafn'} = $datafn;
    $but->{'q'} = $q;
    $but->{'wiz'} = $wiz;
    $but->{'p'} = $p;
    $self->put_it($but);
}

sub do_fileupload {
    my ($self, $q, $wiz, $p, $name, $def) = @_;

    # A file upload box is created via a button to request a file.

    my $but = Gtk2::Button->new($def || 'Select File...');
    $but->signal_connect(clicked => \&create_fileupload_screen);
    $but->{'qbuttonname'} = $q->{'name'};
    $but->{'qbuttonval'} = $def;
    $but->{'generator'} = $self;
    $but->{'parent_button'} = $but;
    $self->put_it($but);
}

sub create_fileupload_screen {
    my $parent_button = shift;

    # create the widget screen
    my $fs = Gtk2::FileSelection->new("Select a file");

    # define the action for the Ok button.
    my $ok = $fs->ok_button;
    $ok->{'pwidget'} = $fs;
    $ok->{'generator'} = $parent_button->{'generator'};
    $ok->{'qwname'} = $parent_button->{'qbuttonname'};
    $ok->{'parent_button'} = $parent_button;
    $ok->signal_connect('clicked' =>
			   sub {
			       my $val = $_[0]{'pwidget'}->get_filename();
			       # save the value
			       $_[0]->{'generator'}->qwparam($_[0]->{'qwname'},
							     $val);
			       # close the widget
			       $_[0]->{'pwidget'}->hide_all;

			       # change the button text
			       # (truncate just to file name first).
			       $val =~ s/.*\///;
			       $_[0]->{'parent_button'}->set_label($val);
			   });

    # define the action for the Cancel button.
    my $can = $fs->cancel_button;
    $can->{'pwidget'} = $fs;
    $can->{'generator'} = $parent_button->{'generator'};
    $can->{'qwname'} = $parent_button->{'qwname'};
    $can->signal_connect('clicked' =>
			   sub {
			       # close the widget
			       $_[0]->{'pwidget'}->hide_all;
			   });
    $fs->show_all;
}

sub create_filedownload_screen {
    my ($parent_button) = @_;

    # create the widget screen
    my $fs = Gtk2::FileSelection->new("Select a file");

    # define the action for the Ok button.
    my $ok = $fs->ok_button;
    $ok->{'pwidget'} = $fs;
    $ok->{'generator'} = $parent_button->{'generator'};
    $ok->{'qwname'} = $parent_button->{'qbuttonname'};
    $ok->{'data'} = $parent_button->{'data'};
    $ok->{'datafn'} = $parent_button->{'datafn'};
    $ok->{'q'} = $parent_button->{'q'};
    $ok->{'p'} = $parent_button->{'p'};
    $ok->{'wiz'} = $parent_button->{'wiz'};
    $ok->{'parent_button'} = $parent_button;
    $ok->signal_connect('clicked' =>
			   sub {
			       my $but = $_[0];

			       # open the file to save the data in
			       my $filename = $but->{'pwidget'}->get_filename();
			       my $fileh = new IO::File;
			       $fileh->open(">" . $filename);

			       # save the question data field
			       if ($but->{'data'}) {
				   print $fileh $but->{'data'};
			       }

			       # call the datafn routine as well
			       if ($but->{'datafn'} &&
				   ref($but->{'datafn'}) eq 'CODE') {
				   $but->{'datafn'}($fileh, $filename,
						    $but->{'wiz'},
						    $but->{'p'},
						    $but->{'q'});
			       }

			       # close the output file
			       $fileh->close();

			       # close the widget
			       $_[0]->{'pwidget'}->hide_all;
			   });

    # define the action for the Cancel button.
    my $can = $fs->cancel_button;
    $can->{'pwidget'} = $fs;
    $can->{'generator'} = $parent_button->{'generator'};
    $can->{'qwname'} = $parent_button->{'qwname'};
    $can->signal_connect('clicked' =>
			   sub {
			       # close the widget
			       $_[0]->{'pwidget'}->hide_all;
			   });
    $fs->show_all;
}

sub check_callback {
    if ($_[0]->get_active) {
	$_[0]->{'generator'}->qwparam($_[0]->{'qwname'},
				      $_[0]->{'qwvals'}[0]);
    } else {
	$_[0]->{'generator'}->qwparam($_[0]->{'qwname'},
				      $_[0]->{'qwvals'}[1]);
    }
}

sub do_checkbox {
    my ($self, $q, $wiz, $p, $vals, $def, $name,
	$submit, $refresh_on_change, $checkbutton_label) = @_;
    $vals = [1, 0] if ($#$vals == -1);
    my $cb = Gtk2::CheckButton->new();
    if ($checkbutton_label) {
	$cb->set_label($checkbutton_label);
    }
    my $text = $self->add_accelerator($self->{'lasttext'});
    if ($text ne $self->{'lasttext'} || $self->{'lasttext'} =~ /_/) {
	$self->{'lastquestion'}->set_markup_with_mnemonic($text);
	$self->{'lastquestion'}->set_mnemonic_widget($cb);
    }
    if ($def eq $vals->[0]) {
	$cb->set_active(TRUE);
    }
    @{$cb->{'qwvals'}} = @$vals;
    $cb->{'generator'} = $self;
    $cb->{'qwname'} = $name;
    $cb->{'qwend'} = \&check_callback;
    if ($submit) {
	$cb->signal_connect(clicked => \&goto_next);
    }
    if ($refresh_on_change) {
	$cb->signal_connect(clicked => \&goto_refresh);
    }
    $self->put_it($cb);
    $self->set_default($q, $def);
}

# set all buttons to on
sub set_all_boxes {
    my $wid = shift;
    my $checkboxes = $wid->{'boxes'};
    foreach my $checkbox (@$checkboxes) {
	$checkbox->set_active(TRUE);
    }
}

# set all buttons to on
sub unset_all_boxes {
    my $wid = shift;
    my $checkboxes = $wid->{'boxes'};
    foreach my $checkbox (@$checkboxes) {
	$checkbox->set_active(FALSE);
    }
}

# set all buttons to on
sub toggle_all_boxes {
    my $wid = shift;
    my $checkboxes = $wid->{'boxes'};
    foreach my $checkbox (@$checkboxes) {
	if ($checkbox->get_active) {
	    $checkbox->set_active(FALSE);
	} else {
	    $checkbox->set_active(TRUE);
	}
    }
}

sub do_multicheckbox {
    my ($self, $q, $wiz, $p, $defs, $vals, $labels, $refresh_on_change) = @_;
    my $tf = Gtk2::VBox->new(FALSE, 3);
    my $count = -1;
    my @buts;
    foreach my $v (@$vals) {
	$count++;
	my $l = (($labels->{$v}) ? $labels->{$v} : "$v");
	my $lb = $self->create_qw_label($l);
	my $c = Gtk2::CheckButton->new();
	my $text = $self->add_accelerator($l);
	if ($text ne $l || $l =~ /_/) {
	    $lb->set_markup_with_mnemonic($text);
	    $lb->set_mnemonic_widget($c);
	}
	my $hb = Gtk2::HBox->new(FALSE, 0);

	$hb->pack_start($c, FALSE, FALSE, 0);
	$hb->pack_start($lb, FALSE, FALSE, 0);
	$tf->pack_start($hb, FALSE, FALSE, 0);
	
	# special handling when a screen refrsh is in progress
	# normally this is handled by QWizard itself with the default
	# flag, but for multi-checkboxes we have more than one name.

	if ($wiz->qwparam('redoing_now')) {
	    $c->set_active(TRUE) if ($wiz->qwparam($q->{'name'} . $v) eq $v);
	}  else {
	    # set the value now
	    $self->{'datastore'}->set($q->{'name'} . $v, $defs->[$count]);

	    # test to see if the current default value matches and
	    # then turn the checkbox to on.
	    if ($defs->[$count] eq $v) {
		$c->set_active(TRUE);
	    }
	}
	$c->{'qwvals'} = [$v,undef];
	$c->{'generator'} = $self;
	$c->{'qwname'} = $q->{'name'} . $v;
	$c->{'qwend'} = \&check_callback;
	if ($refresh_on_change) {
	    $c->signal_connect(clicked => \&goto_refresh);
	}
	push @buts, $c;
	
	push @{$wiz->{'passvars'}},$q->{'name'} . $v;
	push @{$tf->{'qwsubwidgets'}}, $c;
    }

    if (!$q->{'notoggles'}) {
	my $hb = Gtk2::HBox->new(FALSE, 0);

	my $but = Gtk2::Button->new('Set All');
	$but->signal_connect(clicked => \&set_all_boxes);
	$but->{'boxes'} = \@buts;
	$hb->pack_start($but, FALSE, FALSE, 0);

	$but = Gtk2::Button->new('Unset All');
	$but->signal_connect(clicked => \&unset_all_boxes);
	$but->{'boxes'} = \@buts;
	$hb->pack_start($but, FALSE, FALSE, 0);

	$but = Gtk2::Button->new('Toggle All');
	$but->signal_connect(clicked => \&toggle_all_boxes);
	$but->{'boxes'} = \@buts;
	$hb->pack_start($but, FALSE, FALSE, 0);

	$tf->pack_start($hb, FALSE, FALSE, 0);
    }

    $self->put_it($tf);
}

sub do_radio {
    my ($self, $q, $wiz, $p, $vals, $labels, $def, $name,
	$submit, $refresh_on_change, $icons, $noiconpadding) = @_;
    my $vb = Gtk2::VBox->new();
    my (@ws);

    foreach my $v (@$vals) {
	my $text = (($labels->{$v}) ? $labels->{$v} : "$v");
	$text = $self->add_accelerator($text);
	my $rb = Gtk2::RadioButton->new($self->{'radiogroups'}{$name});
	my $label = $self->create_qw_label($text, FALSE, $icons->{$v}, $rb, 
				    $noiconpadding);
	$rb->add($label);
	if ($v eq $def) {
	    $rb->set_active(TRUE);
	}
	$rb->{'qwname'} = $name;
	$rb->{'generator'} = $self;
	$rb->{'qwvalue'} = $v;
	push @ws, $rb;
	if ($submit) {
	    $rb->signal_connect(clicked => \&goto_next);
	}
	if ($refresh_on_change) {
	    $rb->signal_connect(clicked => \&goto_refresh);
	}
	$vb->pack_start($rb, FALSE, FALSE, 0);
	$self->{'radiogroups'}{$name} = $rb->get_group();
    }
    $vb->{'rwidgets'} = \@ws;
    $vb->{'qwend'} = sub {
	foreach my $w (@{$_[0]->{'rwidgets'}}) {
	    if ($w->get_active()) {
		$w->{'generator'}->qwparam($w->{'qwname'}, $w->{'qwvalue'});
		last;
	    }
	}
    };
    $self->put_it($vb);
    $self->set_default($q, $def);
}

sub do_label {
    my ($self, $q, $wiz, $p, $vals, $def) = @_;
    if (defined ($vals)) {
	foreach my $i (@$vals) {
	    $self->put_it($i);
	}
    }
}

sub do_paragraph {
    my ($self, $q, $wiz, $p, $vals, $preformatted, $width) = @_;
    my $w = $width || 40;
    foreach my $i (@$vals) {
	my $t;
	$t = $self->create_qw_label($i);
	if ($preformatted) {
	    $t->set_line_wrap(FALSE)
	} else {
	    # XXX use width argument to define where to wrap
	    $t->set_line_wrap(TRUE)
	}
	$self->put_it($t);
    }
}

sub do_menu {
    my ($self, $q, $wiz, $p, $vals, $labels, $def, $name,
	$submit, $refresh_on_change) = @_;

    my $optionmenu = Gtk2::OptionMenu->new();
    $optionmenu->{'generator'} = $self;
    $optionmenu->{'qwname'} = $name;
    my $menu = Gtk2::Menu->new();

    my $h = 0;
    my $activem;
    my $activenum = 0;
    foreach my $v (@$vals) {
	my $mitem;
	if ($labels->{$v}) {
	    $mitem = Gtk2::MenuItem->new($labels->{$v});
	} else {
	    $mitem = Gtk2::MenuItem->new($v);
	}
	if ((defined($def) && $def eq $v) ||
	    (!$def && !exists($optionmenu->{'finalval'}))) {
	    $activem = $mitem;
	    $activenum = $h;
	    $optionmenu->{'finalval'} = $v;
	    $mitem->{'nosubmityet'} = 1;
	}
	$mitem->{'qwvalue'} = $v;
	$mitem->{'finalvalref'} = \$optionmenu->{'finalval'};
	$mitem->{'submit'} = $submit;
	$mitem->{'refresh_on_change'} = $refresh_on_change;
	$mitem->{'generator'} = $self;
 	$mitem->signal_connect('activate' =>
 			       sub {
				   # set the final value upon selection
 				   ${$_[0]->{'finalvalref'}} =
 				     $_[0]->{'qwvalue'};

				   # auto-submit requested (unless
				   # still setting up).
				   goto_next($_[0])
				     if ($_[0]->{'submit'} &&
					 !$_[0]->{'nosubmityet'});
				   # refresh on change requested (unless
				   # still setting up).
				   goto_refresh($_[0])
				     if ($_[0]->{'refresh_on_change'} &&
					 !$_[0]->{'nosubmityet'});
 			       });
	
	$menu->attach($mitem, 0, 1, $h, $h+1);
	$h++;
    }

    $optionmenu->{'qwend'} = sub {
	$_[0]->{'generator'}->qwparam($_[0]->{'qwname'}, $_[0]->{'finalval'});
    };
    $optionmenu->set_menu($menu);
    $self->put_it($optionmenu);
    if ($activem) {
	$menu->set_active($activenum);
	$menu->activate_item($activem, 1);
	delete $activem->{'nosubmityet'};
    }
    $self->set_default($q, $def);
}

sub do_entry {
    my ($self, $q, $wiz, $p, $name, $def, $hide,
	$size, $maxsize, $submit, $refresh_on_change) = @_;
    # XXX: refresh_on_change
    # XXX: submit

    my $e = Gtk2::Entry->new();
    $e->set_text($def);
    $e->{'qwend'} = sub { 
	$_[0]->{'generator'}->qwparam($name, $_[0]->get_text());
    };
    $e->{'generator'} = $self;

    if ($maxsize) {
	$e->set_max_length($maxsize);
    }
    if ($size) {
	$e->set_width_chars($size)
    }

    #
    # Set up a value to use if the text shouldn't be echoed to the screen.
    #
    if ($hide) {
	$e->set_invisible_char("*");
	$e->set_visibility(FALSE);
    }

    $self->put_it($e);
    $self->set_default($q, $def);
}

sub do_textbox {
    my ($self, $q, $wiz, $p, $name, $def) = @_;

    my $tb = Gtk2::TextBuffer->new();
    $tb->set_text($def);
    my $tv = Gtk2::TextView->new_with_buffer($tb);
    $tv->set_size_request(300,150);
    $tv->{'qwend'} = sub { 
	my @bounds = $_[0]->get_buffer()->get_bounds();
	$_[0]->{'generator'}->qwparam($_[0]->{'qwname'},
				      $_[0]->get_buffer()->get_text(@bounds, TRUE));
    };
    $tv->{'generator'} = $self;
    $tv->{'qwname'} = $name;

    $self->put_it($tv);
    $self->set_default($q, $def);
}

sub do_separator {
    my ($self, $q, $wiz, $p, $text) = @_;
    my $where = $self->{'qf'};
    $self->{'currentq'}++;
    $self->{'qadd'}++;
    my $lab = Gtk2::Label->new();
    $self->put_it($lab);
}

##################################################
# Display
##################################################

sub do_a_table_widget {
    my ($self, $wiz, $p, $containedin, $column, $colnum, $rowc) = @_;

    my $oldqt = $self->{'qtable'};
    $self->{'qtable'} = $containedin;

    my $oldq = $self->{'currentq'};

    my $oldrow = $self->{'currentrow'};
    $self->{'currentrow'} = $rowc;

    my $oldc = $self->{'currentcol'};
    $self->{'currentcol'} = $colnum;
		
    my $subname = $wiz->ask_question($p, $column);
    push @{$wiz->{'passvars'}}, $subname if ($subname);
    push @{$containedin->{'qwsubwidgets'}}, $self->{'lastwidget'};

    $self->{'qtable'} = $oldqt;
    $self->{'currentq'} = $oldq;
    if ($oldc) {
	$self->{'currentcol'} = $oldc;
    } else {
	delete $self->{'currentcol'};
    }
    if ($oldrow) {
	$self->{'currentrow'} = $oldrow;
    } else {
	delete $self->{'currentrow'};
    }
}


sub do_a_table {
    my ($self, $table, $parentt, $rowc, $wiz, $q, $p) = @_;

    foreach my $row (@$table) {
	my $col = 0;
	$rowc++;
	foreach my $column (@$row) {
	    if (ref($column) =~ /Gtk2/) {
		# a native Gtk2 widget.  Just add it.
		$parentt->attach_defaults($column, $col, $col + 1,
					  $rowc, $rowc+1);
		$col++;
	    } elsif (ref($column) eq "ARRAY") {
		# sub table
		my $newt = Gtk2::Table->new(4,4,FALSE);
		$self->do_a_table($column, $newt, -1, $wiz, $q, $p);
		$parentt->attach_defaults($newt, $col, $col+1, $rowc, $rowc+1);
		$col++;
		push @{$parentt->{'qwsubwidgets'}}, $newt;
	    } elsif (ref($column) eq "HASH") {
		$self->do_a_table_widget($wiz, $p, $parentt, $column,
					 $col, $rowc);
		$col++;
	    } else {
		$parentt->attach_defaults($self->create_qw_label($column),
					  $col, $col + 1, $rowc, $rowc+1);
		$col++;
	    }
	}
    }
}

sub do_table {
    my ($self, $q, $wiz, $p, $table, $headers) = @_;

    my $fixed = ($headers) ? 1 : 0;

    my $tab = Gtk2::Table->new(4,4,FALSE);
    $tab->set_border_width(4);

    if ($headers) {
	my $col = 0;
	foreach my $column (@$headers) {
	    # XXX: mark up bold?
	    $tab->attach_defaults($self->create_qw_label($column),
				  $col, $col+1, 0, 1);
	    $col++;
	}
    }

    $self->do_a_table($table, $tab, $fixed-1, $wiz, $q, $p);
    $self->put_it($tab);
}

sub do_graph {
    my $self = shift;
    my ($q, $wiz, $p, $data, $gopts) = @_;

    # a graph is really a file with a special generator.
    $self->do_image($q, $wiz, $p, $self->do_graph_data(@_), undef, "[graph]");
}

##############################################
#
sub scale_img {
    my $but = shift;
    my $img = $but->{'img'};
    my $gdimg = $img->{'origpixbuf'};

    # get curernt image size
    my $wid = $gdimg->get_width();
    my $hei = $gdimg->get_height();

    # set the total image size to the sum of all button presses
    $img->{'currentsize'} += $but->{'scale'};
    $img->{'currentsize'} = 0 if ($img->{'currentsize'} < 0);

    $gdimg =
      $gdimg->scale_simple($wid * $img->{'currentsize'},
			   $hei * $img->{'currentsize'}, 'GDK_INTERP_NEAREST');
#     $wid = $gdimg->get_width();
#     $hei = $gdimg->get_height();
    $img->set_from_pixbuf($gdimg);
#     print "img: $wid x $hei","\n";
}

sub orig_size {
    my $but = shift;
    my $img = $but->{'img'};
    my $gdimg = $img->{'origpixbuf'};
    $img->{'scale'} = 1;
    $img->set_from_pixbuf($img->{'origpixbuf'});
}

sub create_scale_but {
    my ($hb, $lab, $dist, $img) = @_;

    my $but = Gtk2::Button->new($lab);
    $but->signal_connect(clicked => \&scale_img);
    $but->{'img'} = $img;
    $but->{'scale'} = $dist;
    $hb->pack_end($but, FALSE, FALSE, 0);
}

sub do_image {
	my $self = shift;
	my ($q, $wiz, $p, $datastr, $filestr, $imgalt, $scaleopts) = @_;

	my $img;
	if ($datastr) {
	    $filestr = $self->create_temp_file('.png',$datastr);
	} else {
	    $filestr = $self->find_image_path($filestr);
	}
	# image file
	$img = Gtk2::Image->new_from_file($filestr);

	if ($img) {
	    my $vb = Gtk2::VBox->new();
	    my $but;

	    if ($scaleopts) {
		my $hb = Gtk2::HBox->new();
		
		create_scale_but($hb, "25% >", .25, $img);
		create_scale_but($hb, "10% >", .1, $img);
		
		my $but = Gtk2::Button->new("Original Size");
		$but->signal_connect(clicked => \&orig_size);
		$hb->pack_end($but, FALSE, FALSE, 0);

		create_scale_but($hb, "< 10%", -.1, $img);
		create_scale_but($hb, "< 25%", -.25, $img);

		$vb->pack_end($hb, FALSE, FALSE, 0);
	    }

	    $vb->pack_end($img, FALSE, FALSE, 0);

	    $img->{'currentsize'} = 1;
	    my ($arg1, $arg2) = $img->get_pixbuf();

	    $img->{'origpixbuf'} = $arg1;
	    $but->{'img'} = $img if ($but);

	    $self->put_it($vb);
	} else {
	    $self->put_it(Gtk2::Label->new($imgalt || "Broken Image"));
	}
}

##################################################
# Trees
##################################################

sub do_tree {
    my ($self, $q, $wiz, $p, $labels, $expand_all, $def) = @_;

    my $tv = new Gtk2::TreeView();
    my $mod = new Gtk2::TreeStore('Glib::String');
    $tv->set_model($mod);

    my @expand;
    if ($def) {
	#ensure that the default is initially visible
	my $cur = $def;
	unshift @expand, $cur;
	until ($cur eq $q->{'root'}) {
	    $cur = get_name($q->{'parent'}->($wiz, $cur));
	    if (!$cur) {
		# uh oh; can't find path to the default.
		print STDERR "QWizard/Gtk2: can't find path to root=$q->{root} for default tree node for: $q->{name}\n";
		@expand = ();
		last;
	    }
	    unshift @expand, $cur;
	}
	$self->{'datastore'}->set($q->{'name'}, $def) if $q->{'name'};
    }

    #
    # remember a bunch of important stuff in the view
    #
    $mod->{'qwdata'}{'qw'} = $wiz;
    $mod->{'qwdata'}{'q'} = $q;
    $mod->{'qwdata'}{'labels'} = $labels;
    $mod->{'qwdata'}{'first'} = 1;
    $mod->{'qwdata'}{'expand_all'} = $expand_all;
    $mod->{'qwdata'}{'refresh_on_change'} =
      $q->{'refresh_on_change'};
    $mod->{'qwdata'}{'generator'} = $wiz->{'generator'};
    $mod->{'qwdata'}{'datamap'}{'0'} = $q->{'root'};

    #
    # add the root node
    #
    my $iter = $mod->append(undef);
    $mod->set($iter, 0, $q->{'root'});
    $mod->append($iter); # add bogus child

    #
    # When a row expands, add all the children
    #
    $tv->signal_connect("row-expanded",
			sub {
			    my ($treeview,$piter,$path) = @_;
			    $self->add_children($treeview, $piter,
						$path->to_string());
			});

    #
    # When a row collapses, remove the sub data (probably not needed
    # but cleaner and removes memory)
    #
    $tv->signal_connect("row-collapsed",
			sub {
			    # remove all children from the tree
			    my ($treeview,$iter,$path) = @_;

			    my $model = $treeview->get_model();

			    # remove children
			    while (my $i = $model->iter_children($iter)) {
				$model->remove($i);
			    }

			    # attach a bogus one to enuser the
			    # parent has the hierarchial widget
			    # still.
			    $model->append($iter);

			    return 1;
			});

    #
    # when the cursor changes, we pick this as our select value
    #
    $tv->signal_connect("cursor-changed",
			sub {
			    my ($path, $col) = $_[0]->get_cursor();
			    my $mod = $_[0]->get_model();
			    my $qname = $mod->{'qwdata'}{'q'}{'name'};
			    my $pname = $path->to_string();
			    my $val = $mod->{'qwdata'}{'datamap'}{$pname};
			    $mod->{'qwdata'}{'qw'}->qwparam($qname, $val);
			    if ($mod->{'qwdata'}{'first'}) {
				delete($mod->{'qwdata'}{'first'});
			    } else {
				goto_refresh($mod->{'qwdata'})
				  if ($mod->{'qwdata'}{'refresh_on_change'});
			    }
			});


    #
    # Add the rendering type
    #
    my $col = Gtk2::TreeViewColumn->new;
    $col->set_title("Name");

    my $cell = Gtk2::CellRendererText->new;
    $col->pack_start ($cell, 1);
    $col->add_attribute ($cell, text => 0);
    $tv->append_column($col);

    #
    # Do initial expasion to find the default
    #
    if ($expand_all ne '0' && $expand_all ne '') {
	for (my $i = 0; $i <= (int($expand_all) || 10); $i++) {
	    $tv->expand_all();
	}
    } elsif ($#expand > -1) {
	shift @expand;  # drop the root node
	$tv->{'expand'} = \@expand;
 	$tv->expand_row(new Gtk2::TreePath("0"), FALSE);
	$tv->set_cursor(new Gtk2::TreePath($tv->{'qwdata'}{'cursor'}))
	  if ($tv->{'qwdata'}{'cursor'});
    }

    $self->put_it($tv);
}

sub get_name {
    my $node = shift;

    if (ref($node) eq 'HASH') {
	return $node->{'name'};
    } else {
	return $node;
    }
}

sub add_children {
    my ($self, $tv, $piter, $path) = @_;
    my $model = $tv->get_model();
    my $count = 0;
    my $q = $model->{'qwdata'}{'q'};
    my $wiz = $model->{'qwdata'}{'qw'};
    my $labels = $model->{'qwdata'}{'labels'};
    my $expand = $tv->{'expand'};

    # get a list of children
    my $children = 
      $q->{'children'}->($wiz, $model->{'qwdata'}{'datamap'}{$path});

    if (!$children || $#$children == -1) {
	# probably will never get here if the rest of the code was good...
	$model->remove($model->iter_nth_child($piter, 0));
	return;
    }

    # append the path with new suffixes :N:M:...
    $path .= ":" if ($path ne '');

    # add each child node.
    foreach my $child (@$children) {
	add_node($wiz, $tv, $model, $child, $q, $piter, $labels,
		 $path . "$count");
	$count++;
    }

    # remove bogus node
    $model->remove($model->iter_nth_child($piter, 0));

    # expading further if needed
    $tv->expand_row(new Gtk2::TreePath($tv->{'qwdata'}{'expand_row'}), FALSE)
      if (exists($tv->{'qwdata'}{'expand_row'}));

}

sub add_node {
    my ($wiz, $tv, $model, $node, $q, $piter, $labels, $path) = @_;

    my $label;
    my $name = get_name($node);
    if (ref($node) eq 'HASH') {
	$label = $node->{'label'};
    }

    $label = $label || $labels->{$name} || $name;

    #text of the node is the label. name is the identifier.

    my $child = $model->insert($piter, -1);
    $model->set($child, 0, $label);
    $model->{'qwdata'}{'datamap'}{$path} = $name;

    # test to see if the new node has children
    my $ans = $q->{'children'}->($wiz, $name);

    # add a bogus child to make the drop down widget appear
    $model->insert($child, -1) if ($ans && $#$ans > -1);

    # look to see if we're a child that needs opening in the default pass
    if (defined($tv->{'expand'}) &&
	$#{$tv->{'expand'}} > -1 &&
	$tv->{'expand'}[0] eq $name) {
	shift @{$tv->{'expand'}}; # shift of the current one;

	if ($#{$tv->{'expand'}} == -1) {
	    # this is the last node, selecte it
	    $tv->{'qwdata'}{'cursor'} = $path;
	    delete($tv->{'qwdata'}{'expand_row'});
	} else {
	    # tell the sub row to expand
	    $tv->{'qwdata'}{'expand_row'} = $path;
	}
    }
}

##################################################
#
# Automatic updating for monitors.
#

sub do_autoupd
{
	#
	# Dummy routine for now!
	#
	warn "Gtk2.do_autoupd:  currently no automatic updating is defined for Gtk2.  This should be fixed RSN.\n"
}

##################################################
# unknown type errors
#
sub do_unknown {
    my ($self, $q, $wiz, $p) = @_;
    $self->put_it("Unknown question type $q->{type} not handled in primary '$p->{module_name}'.\nIt is highly likely this application will no longer function properly beyond this point.");
}


##################################################
# action confirm
##################################################

sub start_confirm {
    my ($self, $wiz) = @_;

    $self->remove_all_table_entries();
    $self->put_it('Wrapping up.',1,1);
    $self->put_it('Do you want to commit the following changes:',2,1);
    $self->{'resultf'} = $self->create_qw_label('');
    $self->put_it($self->{'resultf'},3,1);
}

sub end_confirm {
    my ($self, $wiz) = @_;
    # this will be deleted by the cancel button if they press it.
    $self->do_hidden($wiz, 'wiz_confirmed', 'Commit');
    $self->do_ok_cancel($wiz->qwparam('QWizard_commit') || "_Commit", $wiz);
    $self->our_mainloop();
    return 1;
}

sub do_confirm_message {
    my ($self, $wiz, $msg) = @_;
    $self->{'resultf'}->set_text($self->{'resultf'}->get_text() . $msg . "\n");
}

sub canceled_confirm {
    my ($self, $wiz) = @_;
    goto_top();
}

##################################################
# actions
##################################################

sub start_actions {
    my ($self, $wiz) = @_;
    $self->remove_all_table_entries();
    $self->put_it('Processing your request...',1,1);
    $self->{'resultf'} = $self->create_qw_label('');
    $self->put_it($self->{'resultf'},2,1);
}

sub end_actions {
    my ($self, $wiz) = @_;
    $self->put_it('Done',3,1);
    $self->do_ok_cancel($wiz->qwparam('QWizard_finish') || "_Finish", $wiz);
    $self->clear_params();
    $self->our_mainloop();
    return 1;
}

sub do_action_output {
    my ($self, $wiz, $action) = @_;
    $self->{'resultf'}->set_text($self->{'resultf'}->get_text() . $action . "\n");
}

sub do_action_error {
    my ($self, $wiz, $errstr) = @_;
    # XXX: make red
    $self->{'resultf'}->set_text($self->{'resultf'}->get_text() . $errstr . "\n");
}

1;
