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="loading..."; 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