/usr/local/CPAN/GBrowse/Bio/Graphics/Browser2/Render/TrackConfig.pm


package Bio::Graphics::Browser2::Render::TrackConfig;
use strict;
use warnings;
use CGI qw(:standard);
use Bio::Graphics::Browser2::Shellwords 'shellwords';
use constant MIN=>0;
use constant MAX=>999999999;

sub new {
    my $class  = shift;
    my $render = shift;
    return bless {render=>$render},ref $class || $class;
}

sub render { shift->{render} }

sub config_dialog {
    my $self               = shift;
    my ($label,$revert_to_defaults) = @_;
    my $render = $self->render;

    my $state       = $render->state();
    my $data_source = $render->data_source();
    my $seg      = $label =~ /:overview$/ ? $render->thin_whole_segment
	          :$label =~ /:region$/   ? $render->thin_region_segment
                  :$render->thin_segment;
    my $length = eval{$seg->length}||0;
    $length   /= $data_source->global_setting('details multiplier');

    eval 'require Bio::Graphics::Browser2::OptionPick; 1'
        unless Bio::Graphics::Browser2::OptionPick->can('new');

    my $picker = Bio::Graphics::Browser2::OptionPick->new($render);

    # summary options
    my $can_summarize = $data_source->can_summarize($label);
    my $summary_mode  = $data_source->show_summary($label,$length);

    my $slabel           = $summary_mode ? $label : $data_source->semantic_label($label,$length);

    if ($revert_to_defaults) {
	$state->{features}{$label}{summary_override}  = {} if $summary_mode;
	$state->{features}{$label}{semantic_override} = {} unless $summary_mode;
	delete $state->{features}{$label}{summary_mode_len};
    }

    my $semantic_override = $render->find_override_region($state->{features}{$label}{semantic_override},$length);
    $semantic_override   ||= 0;

    my ($semantic_level)   = $slabel =~ /(\d+)$/;
    $semantic_level      ||= 0;
    my @level              = map {
	scalar $render->data_source->unit_label($_)
    } split ':',($semantic_override || $semantic_level);
    my $level              = join '..',@level;

    my $key = $render->label2key($label);
    $key .= $summary_mode      ? " (".$render->translate('FEATURE_SUMMARY').')'
           :$level             ? " ($level)"
	   :'';

    my $override = $summary_mode 
	? $state->{features}{$label}{summary_override}                      ||= {}
        : $state->{features}{$label}{semantic_override}{$semantic_override} ||= {};

    my $return_html = start_html();

    my $title   = div({-class=>'config-title'},$key);
    my $dynamic = $render->translate('DYNAMIC_VALUE');

    my $height   = $data_source->semantic_fallback_setting( $label => 'height' ,        $length)    || 10;
    my $width    = $data_source->semantic_fallback_setting( $label => 'linewidth',      $length )   || 1;
    my $glyph    = $data_source->semantic_fallback_setting( $label => 'glyph',          $length )   || 'box';
    my $stranded = $data_source->semantic_fallback_setting( $label => 'stranded',       $length);
    my $variance_band = $data_source->semantic_fallback_setting( $label => 'variance_band',$length);
    my $limit    = $data_source->semantic_fallback_setting( $label => 'feature_limit' , $length)    || 0;
    my $summary_length  = $data_source->semantic_fallback_setting( $label => 'show summary' , $length) || 0;

    # options for wiggle & xy plots
    my $min_score= $data_source->semantic_fallback_setting( $label => 'min_score' ,     $length);
    my $max_score= $data_source->semantic_fallback_setting( $label => 'max_score' ,     $length);
    $min_score = -1 unless defined $min_score;
    $max_score = +1 unless defined $max_score;
    my $autoscale = $data_source->semantic_fallback_setting( $label => 'autoscale' ,     $length);
    $autoscale    = 'local' if $summary_mode;
    $autoscale  ||= 'local';

    my $sd_fold   = $data_source->semantic_fallback_setting( $label => 'z_score_bound' ,     $length);
    $sd_fold    ||= 4;

    my $bicolor_pivot = $data_source->semantic_fallback_setting( $label => 'bicolor_pivot' ,  $length);
    my $graph_type    = $data_source->semantic_fallback_setting( $label => 'graph_type' ,     $length);
    my $glyph_subtype = $data_source->semantic_fallback_setting( $label => 'glyph_subtype' ,  $length);

    # options for wiggle_whiskers
    my $max_color   = $data_source->semantic_fallback_setting( $label => 'max_color' ,   $length);
    my $mean_color  = $data_source->semantic_fallback_setting( $label => 'mean_color' ,  $length);
    my $stdev_color = $data_source->semantic_fallback_setting( $label => 'stdev_color' , $length);

    my @glyph_select;

    if ($summary_mode) {
	$glyph        = $override->{glyph} || 'wiggle_density';
	@glyph_select = qw(wiggle_xyplot wiggle_density);
    } else {
	@glyph_select = shellwords(
	    $data_source->semantic_fallback_setting( $label => 'glyph select', $length )
	    );
	unshift @glyph_select,$dynamic if ref $data_source->fallback_setting($label=>'glyph') eq 'CODE';
    }

    my $db           = $data_source->open_database($label,$length);
    my $quantitative = $glyph =~ /wiggle|vista|xy|density/ || ref($db) =~ /bigwig/i;
    my $can_whisker  = $quantitative && ref($db) =~ /bigwig/i;
    my $vista        = $glyph =~ /vista/;

    unless (@glyph_select) { # reasonable defaults
	@glyph_select = $can_whisker  ? qw(wiggle_xyplot wiggle_density wiggle_whiskers)
		       :$vista        ? 'vista_plot'
                       :$quantitative ? qw(wiggle_xyplot wiggle_density)
	                              : qw(arrow anchored_arrow box crossbox dashed_line diamond 
                                         dna dot dumbbell ellipse gene line primers saw_teeth segments 
                                         span site transcript triangle
                                         two_bolts wave);
    }

    my %glyphs       = map { $_ => 1 } ( $glyph, @glyph_select );
    my @all_glyphs   = sort keys %glyphs;
    my $g = $override->{'glyph'} || $glyph;

    my $url = url( -absolute => 1, -path => 1 );
    my $mode = $summary_mode ? 'summary' : 'normal';
    my $reset_js = <<END;
new Ajax.Request('$url',
                  { method: 'get',
                    asynchronous: false,
                    parameters: 'action=configure_track&track=$label&track_defaults=1;mode=$mode',
                    onSuccess: function(t) { document.getElementById('contents').innerHTML=t.responseText;
					     t.responseText.evalScripts();
		                           },
                    onFailure: function(t) { alert('AJAX Failure! '+t.statusText)}
                  }
                );
END

    my $self_url  = url( -absolute => 1, -path => 1 );
    my $form_name = 'track_config_form';
    my $form      = start_form(
        -name => $form_name,
        -id   => $form_name,
    );

    my @rows;
    push @rows, TR({-class=>'general'},
		   td( {-colspan => 2}, $title));

    push @rows, TR({-class=>'general'},
		   th( { -align => 'right' }, $render->translate('Show') ),
		   td( checkbox(
			   -name     => 'show_track',
			   -value    => $label,
			   -override => 1,
			   -checked  => $state->{features}{$label}{visible},
			   -label    => ''
		       )
		   ),
        );
    
    push @rows,TR( {-class=>'general'},
		   th( { -align => 'right' }, $render->translate('GLYPH') ),
		   td($picker->popup_menu(
			  -name    => 'conf_glyph',
			  -values  => \@all_glyphs,
			  -default => ref $glyph eq 'CODE' ? $dynamic : $glyph,
			  -current => $override->{'glyph'},
			  -scripts => {-id=>'glyph_picker_id',-onChange => 'track_configure.glyph_select($(\'config_table\'),this)'}
		      )
		   )
	);

    for my $glyph (@all_glyphs) {
	my $class = "Bio\:\:Graphics\:\:Glyph\:\:$glyph";
	eval "require $class" unless $class->can('new');
	if (my $subtypes = eval{$class->options->{glyph_subtype}}) {
	    my $options  = $subtypes->[0];
	    next unless ref $options eq 'ARRAY';
	    push @rows,(TR {-class => $glyph,
			    -id    => "conf_${glyph}_subtype"},
			th({-align => 'right'}, $glyph,$render->tr('Subtype')),
			td($picker->popup_menu(
			       -values   => $options,
			       -name     => "conf_${glyph}_subtype",
			       -override => 1,
			       -default => ref $glyph_subtype eq 'CODE' ? $dynamic : $glyph_subtype,
			       -scripts => { -id => "conf_${glyph}_subtype_id",
					     -onChange => 'track_configure.glyph_select($(\'config_table\'),$(\'glyph_picker_id\'))'},
			       -current  => $override->{'glyph_subtype'})));
	}
	if (my $subgraphs = eval{$class->options->{graph_type}}) {
	    my $options  = $subgraphs->[0];
	    $graph_type ||= $subgraphs->[1];
	    next unless ref $options eq 'ARRAY';
	    push @rows,(TR {-class => "$glyph graphtype",
			    -id    => "conf_${glyph}_graphtype"},
			th({-align => 'right'}, $render->tr('XYplot_type')),
			td($picker->popup_menu(
			       -name     => "conf_${glyph}_graphtype",
			       -values   => $options,
			       -override => 1,
			       -default => ref $graph_type eq 'CODE' ? $dynamic : $graph_type,
			       -scripts => { -id => "conf_${glyph}_graphtype_id",
					     -onChange => 'track_configure.glyph_select($(\'config_table\'),$(\'glyph_picker_id\'))'},
			       -current  => $override->{'graph_type'})));
	}
    }

    push @rows,TR( {-class => 'features',
		    -id    => 'packing'},
		   th( { -align => 'right' }, $render->translate('Packing') ),
		   td( popup_menu(
			   -name     => 'format_option',
			   -values   => [ 0 .. 3 ],
			   -override => 1,
			   -default  => $state->{features}{$label}{options},
			   -labels   => {
			       0 => $render->translate('Auto'),
			       1 => $render->translate('Compact'),
			       2 => $render->translate('Expand'),
			       3 => $render->translate('Expand_Label'),
			   }
		       )
		   )
        );

    #######################
    # bicolor pivot stuff
    #######################
    my $p = $override->{bicolor_pivot} || $bicolor_pivot || 'none';
    my $has_pivot = $g =~ /wiggle_xyplot|wiggle_density|xyplot/;

    push @rows,TR( {-class=>'xyplot density',
		     -id   =>'bicolor_pivot_id'},
                   th( { -align => 'right'}, $render->translate('BICOLOR_PIVOT')),
		   td( $picker->popup_menu(
			   -name    => 'conf_bicolor_pivot',
			   -values  => [qw(none zero mean 1SD 2SD 3SD value)],
			   -labels  => {value => 'value entered below',
					'1SD' => 'mean + 1 standard deviation',
					'2SD' => 'mean + 2 standard deviations',
					'3SD' => 'mean + 3 standard deviations',
			   },
			   -default => $bicolor_pivot,
			   -current => $p =~ /^-?[\d.eE]+(?:SD)?$/i ? 'value' : $p,
			   -scripts => {-onChange => 'track_configure.pivot_select(this)',
					-id       => 'conf_bicolor_pivot'}
		       )
		   )
        );

    my $pv    = $p =~ /^[\d.-eE]+$/ ? $p : 0.0;
    push @rows,TR({-class =>'xyplot density',
		   -id=>'switch_point_other'},
		  th( {-align => 'right' },$render->translate('BICOLOR_PIVOT_VALUE')),
                  td( textfield(-name  => 'bicolor_pivot_value',
				-value => $pv)));
    

    push @rows,TR({-class=>'switch_point_color xyplot density'}, 
		  th( { -align => 'right' }, $render->translate('BICOLOR_PIVOT_POS_COLOR')),
		   td( $picker->color_pick(
			   'conf_pos_color',
			   $data_source->semantic_fallback_setting( $label => 'pos_color', $length ),
			   $override->{'pos_color'}
		       )
		   )
        );

    push @rows,TR( {-class=>'switch_point_color xyplot density'}, 
		   th( { -align => 'right' }, $render->translate('BICOLOR_PIVOT_NEG_COLOR') ),
		   td( $picker->color_pick(
			   'conf_neg_color',
			   $data_source->semantic_fallback_setting( $label => 'neg_color', $length ),
			   $override->{'neg_color'}
		       )
		   )
        );

    push @rows,TR( { -id    => 'bgcolor_picker',
		     -class => 'xyplot density features peaks',
		   },
		   th( { -align => 'right' }, $render->translate('BACKGROUND_COLOR') ),
		   td( $picker->color_pick(
			   'conf_bgcolor',
			   $data_source->semantic_fallback_setting( $label => 'bgcolor', $length ),
			   $override->{'bgcolor'}
		       )
		   )
        );
    push @rows,TR( { -id    => 'startcolor_picker',
		     -class => 'peaks',
		   },
		   th( { -align => 'right' }, 'Peak gradient start'),
		   td( $picker->color_pick(
			   'conf_start_color',
			    $data_source->semantic_fallback_setting( $label => 'start_color', $length ),
			   $override->{'start_color'}
		       )
		   )
        );
    push @rows,TR( { -id    => 'endcolor_picker',
		     -class => 'peaks',
		   },
		   th( { -align => 'right' }, 'Peak gradient end'),
		   td( $picker->color_pick(
			   'conf_end_color',
			    $data_source->semantic_fallback_setting( $label => 'end_color', $length ),
			   $override->{'end_color'}
		       )
		   )
        );
    push @rows,TR( {-class=>'xyplot features peaks'},
		   th( { -align => 'right' }, $render->translate('FG_COLOR') ),
		   td( $picker->color_pick(
			   'conf_fgcolor',
			   $data_source->semantic_fallback_setting( $label => 'fgcolor', $length ),
			   $override->{'fgcolor'}
		       )
		   )
        );


    #######################
    # wiggle colors
    #######################
    push @rows,TR( {-class=>'whiskers'}, 
		   th( { -align => 'right' }, $render->translate('WHISKER_MEAN_COLOR')),
		   td( $picker->color_pick(
			   'conf_mean_color',
			   $mean_color || 'black',
			   $override->{'mean_color'}
		       )
		   )
        );

    push @rows,TR( {-class=>'whiskers'}, 
		   th( { -align => 'right' }, $render->translate('WHISKER_STDEV_COLOR') ),
		   td( $picker->color_pick(
			   'conf_stdev_color',
			   $stdev_color || 'grey',
			   $override->{'stdev_color'}
		       )
		   )
        );

    push @rows,TR( {-class=>'whiskers'}, 
		   th( { -align => 'right' }, $render->translate('WHISKER_MAX_COLOR') ),
		   td( $picker->color_pick(
			   'conf_max_color',
			   $max_color || 'lightgrey',
			   $override->{'max_color'}
		       )
		   )
        );

    push @rows,TR({-class=>'xyplot autoscale',
                   -id  => "xyplot_autoscale"
		  },
		    th( { -align => 'right' },$render->tr('AUTOSCALING')),
		    td( $picker->popup_menu(
			    -name    => "conf_xyplot_autoscale",
			    -values  => [qw(none local)],
			    -labels  => {none=>'fixed',local=>'scale to view'},
			    -default => $autoscale,
			    -current => $override->{autoscale},
			    -scripts => {-onChange => 'track_configure.autoscale_select(this,$(\'glyph_picker_id\'))',
					 -id  => "conf_xyplot_autoscale"
			    }
			)));

    push @rows,TR({-class=>'wiggle vista_plot autoscale',
		   -id   => 'wiggle_autoscale'},
		    th( { -align => 'right' },$render->translate('AUTOSCALING')),
		    td( $picker->popup_menu(
			    -name    => "conf_wiggle_autoscale",
			    -values  => $summary_mode ? [qw(none local)] : [qw(none z_score local chromosome global clipped_global)],
			    -labels  => {none      =>'fixed',
					 z_score   =>'scale to SD multiples',
					 local     =>'scale to local min/max',
					 chromosome=>'scale to chromosome min/max',
					 global    =>'scale to genome min/max',
					 clipped_global   =>'clip to +/- SDs shown below'
			    },
			    -default => $autoscale,
			    -current => $override->{autoscale},
			    -scripts => {-onChange => 'track_configure.autoscale_select(this,$(\'glyph_picker_id\'))',
					 -id       => "conf_wiggle_autoscale"
			    }
		       )));

    push @rows,TR({-class=>'wiggle vista_plot autoscale',
		   -id   => 'wiggle_z_fold'},
		    th( { -align => 'right' },$render->translate('SD_MULTIPLES')),
		    td( $picker->popup_menu(
			    -name    => "conf_z_score_bound",
			    -values  => [qw(1 2 3 4 5 6)],
			    -labels  => {1   =>'1 SD',
					 2   =>'2 SD',
					 3   =>'3 SD',
					 4   =>'4 SD',
					 5   =>'5 SD',
					 6   =>'6 SD',
					 },
			    -default => $sd_fold,
			    -current => $override->{z_score_bound}
			)));

    push @rows,TR( {-class=> 'xyplot density whiskers vista_plot autoscale',
		    -id   => 'fixed_minmax'
		   },
		   th( { -align => 'right' },$render->translate('SCALING')),
		   td( textfield(-name  => 'conf_min_score',
				 -class => 'score_bounds',
				 -size  => 5,
				 -value => defined $override->{min_score} ? $override->{min_score}
				                                          : $summary_mode ? 0 : $min_score),
		   '-',
		   textfield(-name  => 'conf_max_score',
			     -class => 'score_bounds',
			     -size  => 5,
			     -value => defined $override->{max_score} ? $override->{max_score}
			                                              : $summary_mode ? 10 : $max_score)))
	if $quantitative;

    push @rows,TR({-class=>'xyplot'},
		  th( { -align => 'right' }, $render->translate('SHOW_VARIANCE')),
		  td(
		      hidden(-name=>'conf_variance_band',-value=>0),
		      checkbox(
			  -name    => 'conf_variance_band',
			  -override=> 1,
			  -value   => 1,
			  -checked => defined $override->{'variance_band'} 
			  ? $override->{'variance_band'} 
			  : $variance_band,
			  -label   => '',
		      )
		  )
        );

    push @rows,TR( {-class=>'features'},
		   th( { -align => 'right' }, $render->translate('LINEWIDTH') ),
		   td( $picker->popup_menu(
			   -name    => 'conf_linewidth',
			   -current => $override->{'linewidth'},
			   -default => $width || 1,
			   -values  => [ sort { $a <=> $b } ( $width, 1 .. 5 ) ]
		       )
		   )
	);

    push @rows,TR( {-class=>'general'},
		   th(
		       { -align => 'right' }, $render->translate('HEIGHT') ),
		   td( $picker->popup_menu(
			   -name    => 'conf_height',
			   -current => $override->{'height'},
			   -default => $summary_mode ? 15 : $height,
			   -values  => [
				sort { $a <=> $b }
				( $height, map { $_ * 5 } ( 1 .. 20 ) )
			   ],
		       )
		   )
        );
    
    push @rows,TR({-class=>'features'},
		  th( { -align => 'right' }, $render->translate('Limit') ),
		  td( $picker->popup_menu(
			  -name     => 'conf_feature_limit',
			  -values   => [ 0, 5, 10, 25, 50, 100, 200, 500, 1000 ],
			  -labels   => { 0 => $render->translate('NO_LIMIT') },
			   -current  => $override->{feature_limit},
			  -override => 1,
			  -default  => $limit,
		       )
		  )
        );
    
    push @rows,TR({-class=>'features'},
		  th( { -align => 'right' }, $render->translate('STRANDED') ),
		  td( hidden(-name=>'conf_stranded',-value=>0),
		      checkbox(
			  -name    => 'conf_stranded',
			  -override=> 1,
			 -value   => 1,
			  -checked => defined $override->{'stranded'} 
			  ? $override->{'stranded'} 
			  : $stranded,
			  -label   => '',
		      )
		  )
        );

    my ($low,$hi)   = $render->find_override_bounds($state->{features}{$label}{semantic_override},$length);
    $low ||= $semantic_level;
    $hi  ||= MAX;
    push @rows,TR({-class=>'general'},
		  th( {-align => 'right' }, 
		      $render->translate('APPLY_CONFIG')
		      ),
		  td(
		      $self->region_size_menu('apply_semantic_low',$length,MIN),
		      '-',
		      $self->region_size_menu('apply_semantic_hi',$hi,MAX),
		  )
	) unless $summary_mode;

    my $summ = defined $state->{features}{$label}{summary_mode_len}
                         ?$state->{features}{$label}{summary_mode_len}
                         :$summary_length;
    push @rows,TR({-class=>'general'},
		  th( { -align => 'right' }, $render->translate('SHOW_SUMMARY')),
		  td(
		      $self->region_size_menu('summary_mode',$summ)
		  )
	) if $can_summarize && $summary_length;

    my $submit_script = <<END;
Element.extend(this);
var ancestors    = this.ancestors();
var form_element = ancestors.find(function(el) {return el.nodeName=='FORM'; });
Controller.reconfigure_track('$label',form_element,'$mode')
END

    push @rows,TR({-class=>'general'},
		  td({-colspan=>2},
		     button(
			 -style   => 'background:pink',
			 -name    => $render->translate('Revert'),
			 -onClick => $reset_js
		     ), br, 
		     button(
			 -name    => $render->translate('Cancel'),
			 -onClick => 'Balloon.prototype.hideTooltip(1)'
		     ),
		     button(
			 -name    => $render->translate('Change'),
			 -onClick => $submit_script
		     ),
		     hidden(-name=>'segment_length',-value=>$length),
		  )
    );

    $form .= table({-id=>'config_table',-border => 0 },@rows);
    $form .= end_form();
	$return_html
        .= table( TR( td( { -valign => 'top' }, [ $form ] ) ) );
    $return_html .= script({-type=>'text/javascript'},"track_configure.glyph_select(\$('config_table'),\$('glyph_picker_id'))");
    $return_html .= end_html();
    return $return_html;
}

sub region_size_menu {
  my $self = shift;
  my ($name,$length,$extra_val) = @_;

  my $source =  $self->render->data_source;
  my %seen;
  my @r         = sort {$a<=>$b} $source->get_ranges(),$length,$extra_val;
  my @ranges	= grep {!$seen{$source->unit_label($_)}++} @r;
  my %labels    = map  {$_=> scalar $source->unit_label($_)} @ranges;

  $labels{MIN()}   = $self->render->translate('MIN');
  $labels{MAX()}   = $self->render->translate('MAX');
  @ranges = sort {$b||0<=>$a||0} @ranges;

  return popup_menu(-name    => $name,
		    -values  => \@ranges,
		    -labels  => \%labels,
		    -default => $length,
		    -force   => 1,
		   );
}

1;