diff --git a/conf/languages/POSIX.pm b/conf/languages/POSIX.pm
index c695994b7..45a17ee6c 100644
--- a/conf/languages/POSIX.pm
+++ b/conf/languages/POSIX.pm
@@ -141,6 +141,10 @@ END
TRACKS => 'Tracks',
+ TRACK_SELECT => 'Search for Specific Tracks',
+
+ TRACK_NAME => 'Track name',
+
EXTERNAL_TRACKS => 'External tracks italicized',
OVERVIEW_TRACKS => '*Overview track',
@@ -193,6 +197,8 @@ END
CLEAR_HIGHLIGHTING => 'Clear highlighting',
+ CLEAR => 'Clear',
+
UPDATE => 'Update',
UPDATE_TRACKS => 'Update Tracks',
diff --git a/conf/plugins/SimpleTrackFinder.pm b/conf/plugins/SimpleTrackFinder.pm
new file mode 100644
index 000000000..a649623b2
--- /dev/null
+++ b/conf/plugins/SimpleTrackFinder.pm
@@ -0,0 +1,99 @@
+package Bio::Graphics::Browser::Plugin::SimpleTrackFinder;
+# $Id: SimpleTrackFinder.pm,v 1.1 2009-05-20 20:36:20 lstein Exp $
+use strict;
+use CGI qw(:standard *table);
+use base 'Bio::Graphics::Browser::Plugin';
+our $VERSION = '0.25';
+
+sub name { "Simple Track Finder" }
+
+sub description {
+ return p("The simple track finder filters the track table by the name of the track.");
+}
+
+sub type { 'trackfilter' }
+
+sub init { }
+
+sub config_defaults {
+ my $self = shift;
+ return {
+ track_name => undef,
+ };
+}
+
+sub reconfigure {
+ my $self = shift;
+ my $current_config = $self->configuration;
+ $current_config->{track_name} = $self->config_param('track_name');
+}
+
+sub configure_form {
+ my $self = shift;
+ my $current_config = $self->configuration;
+ my $name = $current_config->{track_name};
+ return
+ b('Partial or full track name:').
+ textfield(-id => 'track_name_filter',
+ -name => $self->config_name('track_name'),
+ -default => $name,
+ -override => 1,
+ -onKeyDown=>'if (event.keyCode==13) doPluginUpdate()',
+ ).
+ button(-value => 'Clear',
+ -onClick => "\$('track_name_filter').clear(); doPluginUpdate()");
+}
+
+sub filter_tracks {
+ my $self = shift;
+ my $tracks = shift;
+ my $source = shift;
+
+ my $config = $self->configuration;
+ my $name = $config->{track_name} or return @$tracks;
+
+ my @filtered = grep {
+ my $key = $source->setting($_=>'key') || $_;
+ $key =~ m/$name/i
+ } @$tracks;
+ warn "name = $name, filtered = @filtered";
+ return @filtered;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bio::Graphics::Browser::Plugin::SimpleTrackFinder - Limit list of tracks to those that match a name pattern
+
+=head1 SYNOPSIS
+
+In the appropriate gbrowse configuration file:
+
+ track filter = SimpleTrackFinder
+
+=head1 DESCRIPTION
+
+=head1 OPTIONS
+
+
+=head1 BUGS
+
+None known yet.
+
+=head1 SEE ALSO
+
+L
+
+=head1 AUTHOR
+
+Lincoln Stein Elincoln.stein@gmail.comE.
+
+Copyright (c) 2009 Ontario Institute for Cancer Research
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/conf/plugins/SourceTrackFinder.pm b/conf/plugins/SourceTrackFinder.pm
new file mode 100644
index 000000000..6f586b74e
--- /dev/null
+++ b/conf/plugins/SourceTrackFinder.pm
@@ -0,0 +1,136 @@
+package Bio::Graphics::Browser::Plugin::SourceTrackFinder;
+# $Id: SourceTrackFinder.pm,v 1.1 2009-05-20 20:36:20 lstein Exp $
+use strict;
+use CGI qw(:standard *table);
+use base 'Bio::Graphics::Browser::Plugin';
+use Bio::Graphics::Browser::Util 'shellwords';
+our $VERSION = '0.25';
+
+sub name { "Source Track Finder" }
+
+sub description {
+ return p("The source track finder filters the track table by the data and config source the track.");
+}
+
+sub type { 'trackfilter' }
+
+sub init { }
+
+sub config_defaults {
+ my $self = shift;
+
+ # this line gets all the options defined in the "[SourceTrackFinder:plugin]" stanza
+ my %fields = map {$_=>undef} $self->get_fields;
+
+ return \%fields;
+}
+
+
+# this method gets all the options defined in the "[SourceTrackFinder:plugin]" stanza
+sub get_fields {
+ my $self = shift;
+ return $self->browser_config->plugin_setting;
+}
+
+sub reconfigure {
+ my $self = shift;
+ my $current_config = $self->configuration;
+
+ for my $f ($self->get_fields) {
+ $current_config->{lc $f} = $self->config_param($f);
+ }
+}
+
+sub configure_form {
+ my $self = shift;
+ my $current_config = $self->configuration;
+ my $source = $self->browser_config;
+
+ my @fields = $self->get_fields;
+ my @elements;
+
+ for my $f (@fields) {
+ my @options = ('',shellwords($source->plugin_setting($f)));
+ push @elements,b(ucfirst $f.':');
+ push @elements,
+ popup_menu(
+ -id => "plugin_$f",
+ -class => "SourceTrackFinderPopup",
+ -name => $self->config_name($f),
+ -values => \@options,
+ -default => $current_config->{$f},
+ -override => 1,
+ -onChange => 'doPluginUpdate()',
+ )
+ }
+ push @elements,
+ button(-value => 'Clear',
+ -onClick => q($$('.SourceTrackFinderPopup').each(function(m) {m.selectedIndex=0}); doPluginUpdate();)
+ );
+ return join ' ',@elements;
+
+
+}
+
+sub filter_tracks {
+ my $self = shift;
+ my $track_labels = shift;
+ my $source = shift;
+
+ my $config = $self->configuration;
+ my @fields = $self->get_fields;
+ my %filters = map {lc $_ => $config->{lc $_}} @fields;
+
+ my @result;
+
+ LABEL:
+ for my $l (@$track_labels) {
+ do {push @result,$l; next LABEL} if $l =~ /^(plugin|file|http|das)/;
+
+ for my $f (keys %filters) {
+ my %values = map {lc $_=>1} shellwords $source->fallback_setting($l=>$f);
+ next LABEL if length $filters{$f} && !$values{lc $filters{$f}};
+ }
+
+ push @result,$l;
+ }
+ return @result;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bio::Graphics::Browser::Plugin::SimpleTrackFinder - Limit list of tracks to those that match a name pattern
+
+=head1 SYNOPSIS
+
+In the appropriate gbrowse configuration file:
+
+ track filter = SimpleTrackFinder
+
+=head1 DESCRIPTION
+
+=head1 OPTIONS
+
+
+=head1 BUGS
+
+None known yet.
+
+=head1 SEE ALSO
+
+L
+
+=head1 AUTHOR
+
+Lincoln Stein Elincoln.stein@gmail.comE.
+
+Copyright (c) 2009 Ontario Institute for Cancer Research
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/htdocs/js/controller.js b/htdocs/js/controller.js
index 0087f4599..85b087153 100644
--- a/htdocs/js/controller.js
+++ b/htdocs/js/controller.js
@@ -3,7 +3,7 @@
Lincoln Stein
Ben Faga
- $Id: controller.js,v 1.91 2009-05-15 03:23:19 lstein Exp $
+ $Id: controller.js,v 1.92 2009-05-20 20:36:20 lstein Exp $
Indentation courtesy of Emacs javascript-mode
(http://mihai.bazon.net/projects/emacs-javascript-mode/javascript.el)
@@ -236,6 +236,7 @@ var GBrowseController = Class.create({
var request_str = "update_sections=1" + param_str;
for (var i = 0; i < section_names.length; i++) {
+ $(section_names[i]).innerHTML="";
request_str += "§ion_names="+section_names[i];
}
@@ -636,14 +637,24 @@ var GBrowseController = Class.create({
filter_track: track_id,
}).toQueryString(),
onSuccess: function(transport) {
- var track_div_id = Controller.gbtracks.get(track_id).track_div_id;
Balloon.prototype.hideTooltip(1);
Controller.rerender_track(track_id,true);
} // end onSuccess
});
-
},
+ restrict_tracks:
+ function(restriction_data) {
+ new Ajax.Request(document.URL,{
+ method: 'post',
+ parameters:
+ restriction_data + '&' +
+ $H({restrict_tracks: 1}).toQueryString(),
+ onSuccess: function(transport) {
+ Controller.update_sections(new Array(track_listing_id),'',1);
+ }
+ });
+ },
// Plugin Methods *************************************************
@@ -669,7 +680,7 @@ var GBrowseController = Class.create({
}).toQueryString(),
onSuccess: function(transport) {
- Controller.wipe_div(pc_div_id);
+ if (pc_div_id != null) Controller.wipe_div(pc_div_id);
if (plugin_type == 'annotator'){
Controller.each_track(plugin_track_id,function(gbtrack) {
@@ -679,6 +690,9 @@ var GBrowseController = Class.create({
else if (plugin_type == 'filter'){
Controller.update_coordinates("reload segment");
}
+ else if (plugin_type == 'trackfilter') {
+ Controller.update_sections(new Array(track_listing_id),'',1);
+ }
} // end onSuccess
});
},
diff --git a/lib/Bio/Graphics/Browser/DataSource.pm b/lib/Bio/Graphics/Browser/DataSource.pm
index 8046bf734..d215c9dd2 100644
--- a/lib/Bio/Graphics/Browser/DataSource.pm
+++ b/lib/Bio/Graphics/Browser/DataSource.pm
@@ -342,7 +342,8 @@ sub plugin_setting {
my $caller_package = caller();
my ($last_name) = $caller_package =~ /(\w+)$/;
my $option_name = "${last_name}:plugin";
- $self->setting($option_name => @_);
+ return $self->label_options($option_name) unless @_;
+ return $self->setting($option_name => @_);
}
sub karyotype_setting {
@@ -792,5 +793,62 @@ sub add_dbid_to_feature {
}
+=head2 @labels = $source->data_source_to_label(@data_sources)
+
+Search through all stanzas for those with a matching "data source"
+option. Data sources look like this:
+
+ [stanzaLabel1]
+ data source = FlyBase
+
+ [stanzaLabel2]
+ data source = FlyBase
+
+Now searching for $source->data_source_to_label('FlyBase') will return
+"stanzaLabel1" and "stanzaLabel2" along with others that match. A
+track may have several data sources, separated by spaces.
+
+=cut
+
+
+sub data_source_to_label {
+ my $self = shift;
+ return $self->_secondary_key_to_label('data source',@_);
+}
+
+=head2 @labels = $source->track_source_to_label(@track_sources)
+
+Search through all stanzas for those with a matching "track source"
+option. Track sources look like this:
+
+ [stanzaLabel]
+ track source = UCSC EBI NCBI
+
+Now searching for $source->track_source_to_label('UCSC','EBI') will
+return "stanzaLabel" along with others that match. A track may have
+several space-delimited track sources.
+
+=cut
+sub track_source_to_label {
+ my $self = shift;
+ return $self->_secondary_key_to_label('track source',@_);
+}
+
+sub _secondary_key_to_label {
+ my $self = shift;
+ my $field = shift;
+ my $index = $self->{'.secondary_key'};
+ if (!exists $index->{$field}) {
+ for my $label ($self->labels) {
+ my @sources = shellwords $self->setting($label=>$field) or next;
+ push @{$index->{$field}{lc $_}},$label foreach @sources;
+ }
+ }
+
+ my %seenit;
+ return grep {!$seenit{$_}++}
+ map {exists $index->{$field}{lc $_} ? @{$index->{$field}{lc $_}} : () } @_;
+}
+
1;
diff --git a/lib/Bio/Graphics/Browser/Plugin.pm b/lib/Bio/Graphics/Browser/Plugin.pm
index 1da0d3621..04cd8108f 100644
--- a/lib/Bio/Graphics/Browser/Plugin.pm
+++ b/lib/Bio/Graphics/Browser/Plugin.pm
@@ -1,5 +1,5 @@
package Bio::Graphics::Browser::Plugin;
-# $Id: Plugin.pm,v 1.19 2009-01-30 22:06:19 lstein Exp $
+# $Id: Plugin.pm,v 1.20 2009-05-20 20:36:20 lstein Exp $
# base class for plugins for the Generic Genome Browser
=head1 NAME
@@ -269,9 +269,10 @@ values.
=item $browser_config = $self->browser_config
-This method returns a copy of the Bio::Graphics::Browser object that
-drives gbrowse. This object allows you to interrogate (and change!)
-the values set in the current gbrowse configuration file.
+This method returns a copy of the Bio::Graphics::Browser::DataSource
+object that drives gbrowse. This object allows you to interrogate
+(and change!) the values set in the current gbrowse configuration
+file.
The recommended use for this object is to recover plugin-specific
settings from the gbrowse configuration file. These can be defined by
@@ -290,8 +291,8 @@ You can now access these settings from within the plugin by using the
following idiom:
my $browser_config = $self->browser_config;
- my $traverse_isa = $browser_config->plugin_setting('traverse_isa');
- my $server = $browser_config->plugin_setting('use_server');
+ my $traverse_isa = $browser_config->plugin_setting('traverse_isa');
+ my $server = $browser_config->plugin_setting('use_server');
This facility is intended to be used for any settings that should not
be changed by the end user. Persistent user preferences should be
diff --git a/lib/Bio/Graphics/Browser/PluginSet.pm b/lib/Bio/Graphics/Browser/PluginSet.pm
index 0fe245dac..e3d3829d3 100644
--- a/lib/Bio/Graphics/Browser/PluginSet.pm
+++ b/lib/Bio/Graphics/Browser/PluginSet.pm
@@ -1,7 +1,7 @@
package Bio::Graphics::Browser::PluginSet;
# API for using plugins
-# $Id: PluginSet.pm,v 1.11 2009-03-27 02:10:09 lstein Exp $
+# $Id: PluginSet.pm,v 1.12 2009-05-20 20:36:20 lstein Exp $
use strict;
use Bio::Graphics::Browser;
@@ -20,7 +20,6 @@ sub new {
my @plugins = shellwords($config->plugins);
warn "PLUGINS = @plugins" if DEBUG;
-
PLUGIN:
for my $plugin (@plugins) {
my $class = "Bio\:\:Graphics\:\:Browser\:\:Plugin\:\:$plugin";
diff --git a/lib/Bio/Graphics/Browser/Render.pm b/lib/Bio/Graphics/Browser/Render.pm
index 97394889e..6265d1a05 100644
--- a/lib/Bio/Graphics/Browser/Render.pm
+++ b/lib/Bio/Graphics/Browser/Render.pm
@@ -583,6 +583,14 @@ sub asynchronous_event {
return (302,undef,$self->image_link($self->state,$format));
}
+# obsolete
+# # update the track restriction policy
+# if (param('restrict_tracks')) {
+# $settings->{restrict_tracks} = param('track_name_filter');
+# warn "restricting tracks to $settings->{restrict_tracks}";
+# return (204,'text/plain',undef);
+# }
+
# autocomplete support
if (my $match = param('autocomplete')) {
my $search = $self->get_search_object;
@@ -762,9 +770,10 @@ sub render {
# NOTE: these handle_* methods will return true
# if they want us to exit before printing the header
- $self->handle_gff_dump() && return;
- $self->handle_plugins() && return;
- $self->handle_downloads() && return;
+ $self->handle_track_dump() && return;
+ $self->handle_gff_dump() && return;
+ $self->handle_plugins() && return;
+ $self->handle_downloads() && return;
$self->render_header();
$self->render_body();
@@ -1237,8 +1246,8 @@ sub init_plugins {
$self->language,
$self->session);
$self->plugins($plugins);
-
$self->load_plugin_annotators();
+
$plugins;
}
@@ -1314,6 +1323,49 @@ sub handle_gff_dump {
return 1;
}
+sub track_filter_plugin {
+ my $self = shift;
+ my $plugins = $self->plugins;
+ my ($filter) = grep {$_->type eq 'trackfilter'} $plugins->plugins;
+ return $filter;
+}
+
+# track dumper
+sub handle_track_dump {
+ my $self = shift;
+ my $source = $self->data_source;
+
+ param('show_tracks') or return;
+ print header('text/plain');
+
+ my (%ts,%ds,@labels_to_dump);
+ if (my @labels = $source->track_source_to_label(shellwords param('ts'))) {
+ %ts = map {$_=>1} @labels;
+ }
+ if (my @labels = $source->data_source_to_label(shellwords param('ds'))) {
+ %ds = map {$_=>1} @labels;
+ }
+ if (param('ts') && param('ds')) { # intersect
+ @labels_to_dump = grep {$ts{$_}} keys %ds;
+ } elsif (param('ts') or param('ds')) { #union
+ @labels_to_dump = (keys %ts,keys %ds);
+ } else {
+ @labels_to_dump = $source->labels;
+ }
+
+ print '#',join("\t",qw(TrackLabel DataSource TrackSource Description)),"\n";
+ for my $l (@labels_to_dump) {
+ next if $l =~ /_scale/;
+ next if $l =~ /(plugin|file):/;
+ print join("\t",
+ $l,
+ $source->setting($l=>'data source'),
+ $source->setting($l=>'track source'),
+ $source->setting($l=>'key')),"\n";
+ }
+ return 1;
+}
+
# Handle plug-ins that aren't taken care of asynchronously
sub handle_plugins {
my $self = shift;
@@ -1997,8 +2049,19 @@ sub update_tracks {
my $self = shift;
my $state = shift;
- $self->set_tracks($self->split_labels(param('label'))) if param('label');
- $self->set_tracks($self->split_labels(param('t'))) if param('t');
+ # selected tracks can be set by the 'label' parameter
+ if (my @l = param('label')) {
+ $self->set_tracks($self->split_labels(@l));
+ } #... the 't' parameter
+ elsif (my @t = param('t')) {
+ $self->set_tracks($self->split_labels(@t));
+ } #... the 'ds' (data source) parameter
+ elsif (my @ds = shellwords param('ds')) {
+ $self->set_tracks($self->data_source->data_source_to_label(@ds));
+ } #... or the 'ts' (track source) parameter
+ elsif (my @ts = shellwords param('ts')) {
+ $self->set_tracks($self->data_source->track_source_to_label(@ts));
+ }
if (my @selected = $self->split_labels(param('enable'))) {
$state->{features}{$_}{visible} = 1 foreach @selected;
diff --git a/lib/Bio/Graphics/Browser/Render/HTML.pm b/lib/Bio/Graphics/Browser/Render/HTML.pm
index 86d14e776..83bf35383 100644
--- a/lib/Bio/Graphics/Browser/Render/HTML.pm
+++ b/lib/Bio/Graphics/Browser/Render/HTML.pm
@@ -565,20 +565,47 @@ sub galaxy_form {
$html .= hidden(-name=>'m',-value=>'application/x-gff3');
$html .= endform();
-# Copied from gbrowse 1.69 -- not sure if still appropriate
-# my $plugin_action = param('plugin_action');
-# if ($plugin_action eq $CONFIG->tr('Go') && param('plugin') eq 'invoke_galaxy') {
-# $html .= script('document.galaxyform.submit()');
-# }
-
return $html;
}
+sub render_track_filter {
+ my $self = shift;
+ my $plugin = shift;
+
+ my $form = $plugin->configure_form();
+ my $plugin_type = $plugin->type;
+ my $action = $self->tr('Configure_plugin');
+ my $name = 'plugin:'.$plugin->name;
+
+ return
+ p({-id=>'track select'},
+ start_form({-id => 'track_filterform',
+ -name => 'configure_plugin',
+ -onSubmit=> 'return false'}),
+ $form,
+ button(
+ -name => 'plugin_button',
+ -value => $self->tr('Configure_plugin'),
+ -onClick => 'doPluginUpdate()',
+ ),
+ end_form(),
+ script({-type=>'text/javascript'},
+ "function doPluginUpdate() { Controller.reconfigure_plugin('$action',null,null,'$plugin_type',\$('track_filterform')) }")
+ );
+}
# This surrounds the track table with a toggle
sub render_toggle_track_table {
my $self = shift;
- return $self->toggle('Tracks', $self->render_track_table());
+ my $html;
+
+ if (my $filter = $self->track_filter_plugin) {
+ $html .= $self->toggle({tight=>1},'track_select',div({class=>'searchtitle',
+ style=>"text-indent:2em"},$self->render_track_filter($filter)));
+ }
+ $html .= $self->toggle('Tracks',$self->render_track_table);
+
+ return $html;
}
# this draws the various config options
@@ -597,6 +624,11 @@ sub render_track_table {
my %labels = map {$_ => $self->label2key($_)} @labels;
my @defaults = grep {$settings->{features}{$_}{visible} } @labels;
+ if (my $filter = $self->track_filter_plugin) {
+ eval {@labels = $filter->filter_tracks(\@labels,$source)};
+ warn $@ if $@;
+ }
+
# Sort the tracks into categories:
# Overview tracks
# Region tracks
@@ -1230,9 +1262,10 @@ sub plugin_menu {
my $settings = $self->state;
my $plugins = $self->plugins;
- my $labels = $plugins->menu_labels;
+ my $labels = $plugins->menu_labels;
- my @plugins = sort {$labels->{$a} cmp $labels->{$b}} keys %$labels;
+ my @plugins = grep {$plugins->plugin($_)->type ne 'trackfilter'} # track filter gets its own special position
+ sort {$labels->{$a} cmp $labels->{$b}} keys %$labels;
# Add plugin types as attribute so the javascript controller knows what to do
# with each plug-in
diff --git a/t/02.rearchitecture.t b/t/02.rearchitecture.t
index c714fec7b..e84d0da44 100644
--- a/t/02.rearchitecture.t
+++ b/t/02.rearchitecture.t
@@ -15,7 +15,7 @@ use FindBin '$Bin';
use lib "$Bin/testdata";
use TemplateCopy; # for the template_copy() function
-use constant TEST_COUNT => 78;
+use constant TEST_COUNT => 83;
use constant CONF_FILE => "$Bin/testdata/conf/GBrowse.conf";
BEGIN {
@@ -155,7 +155,7 @@ ok($source->html1,'This is inherited');
ok($source->html2,'This is overridden');
# does the timeout calculation work?
-ok($source->global_time('cache time'),3600);
+ok($source->global_time('expire cache'),7200);
# Do semantic settings work?
ok($source->safe,1,'source should be safe');
@@ -240,6 +240,18 @@ $source->clear_cache;
(undef,$adapter,@args) = $source->db_settings;
ok($args[3]=~m!^/buzz/buzz!); # old value cached
+# Test the data_source_to_label() and track_source_to_label() functions
+my @labels = sort $source->track_source_to_label('foobar');
+ok(scalar @labels, 0);
+@labels = sort $source->track_source_to_label('modENCODE');
+ok("@labels","CDS Genes ORFs");
+@labels = sort $source->track_source_to_label('marc perry','nicole washington');
+ok("@labels","CDS ORFs");
+@labels = sort $source->data_source_to_label('SGD');
+ok("@labels","CDS Genes ORFs");
+@labels = sort $source->data_source_to_label('flybase');
+ok("@labels","CDS");
+
exit 0;
END {
diff --git a/t/testdata/conf/templates/yeast_chr1.conf b/t/testdata/conf/templates/yeast_chr1.conf
index 7e9f09d9d..5d18c309d 100644
--- a/t/testdata/conf/templates/yeast_chr1.conf
+++ b/t/testdata/conf/templates/yeast_chr1.conf
@@ -79,6 +79,8 @@ key = Centromeres
[Genes]
feature = gene:sgd
+data source = sgd
+track source = modENCODE
glyph = generic
bgcolor = yellow
forwardcolor = yellow
@@ -92,6 +94,8 @@ key = Named gene
feature = ORF:sgd
glyph = arrow
fgcolor = red
+data source = sgd
+track source = "nicole washington" modENCODE
linewidth = 2
height = 6
description = 1
@@ -102,6 +106,8 @@ feature = ORF:sgd
glyph = cds
description = 0
height = 26
+data source = sgd flybase
+track source = "Marc Perry" modENCODE
# we need this because the yeast GFF file does not define the phase
allow_empty_phase = 1
sixframe = 1