add a useful comment
[mussa.git] / alg / glseqbrowser.cpp
index 70637a0dae60a12df2af47015545ec81ed79d927..04cc792ec441e3eeb375fb2ac47bf842d509b3c0 100644 (file)
@@ -1,17 +1,18 @@
 #include "alg/glseqbrowser.hpp"
 #include "mussa_exceptions.hpp"
 
+#include <math.h>
 #include <iostream>
+#include <sstream>
 #include <stdexcept>
 
 using namespace std;
 
 GlSeqBrowser::GlSeqBrowser()
-  : border(25),
-    max_ortho(400.0, 0.0, 600.0, 0.0),
-    cur_ortho(max_ortho),
+  : border_width(25),
+    cur_ortho(400.0, 0.0, 600.0, 0.0),
     viewport_size(600, 400),
-    viewport_center(0),
+    viewport_center((cur_ortho.right-cur_ortho.left)/2+cur_ortho.left),
     zoom_level(2),
     color_mapper(),
     track_container()
@@ -19,14 +20,14 @@ GlSeqBrowser::GlSeqBrowser()
 }
 
 GlSeqBrowser::GlSeqBrowser(const GlSeqBrowser& gt)
-  : border(gt.border),
-    max_ortho(gt.max_ortho),
+  : border_width(gt.border_width),
     cur_ortho(gt.cur_ortho),
     viewport_size(gt.viewport_size),
     viewport_center(gt.viewport_center),
     zoom_level(gt.zoom_level),
     color_mapper(gt.color_mapper),
-    track_container(gt.track_container)
+    track_container(gt.track_container),
+    path_segments(gt.path_segments)
 {
 }
 
@@ -43,7 +44,7 @@ void GlSeqBrowser::resizeGL(int width, int height)
   viewport_size.x = width;
   viewport_size.y = height;
   glViewport(0, 0, (GLsizei)width, (GLsizei)height);
-  //update_layout();
+  update_viewport(viewport_center, zoom_level);
 }
 
 void GlSeqBrowser::paintGL() const
@@ -63,7 +64,7 @@ void GlSeqBrowser::paintGL() const
   glFlush();
 }
 
-void GlSeqBrowser::processSelection(GLuint hits, GLuint buffer[], GLuint bufsize)
+void GlSeqBrowser::processSelection(GLuint hits, GLuint buffer[], GLuint bufsize, const rect<float>& r)
 {
   GLuint *ptr;
   GLuint names;
@@ -75,11 +76,11 @@ void GlSeqBrowser::processSelection(GLuint hits, GLuint buffer[], GLuint bufsize
   GLuint path_index = 0;
   GLuint pair_key_0 = 0;
   GLuint pair_key_1 = 0;
+  TrackRegion track;
 
   selected_paths.clear();
   selected_tracks.clear();
 
-  std::cout << "hits = " << hits << std::endl;
   ptr = (GLuint *) buffer;
   if (hits > 0)
     selectedMode = true;
@@ -115,8 +116,19 @@ void GlSeqBrowser::processSelection(GLuint hits, GLuint buffer[], GLuint bufsize
           }
           break;
         case MussaTrack:
+        {
           objid = *ptr++; ++consumed_names;
-          selected_tracks.insert(objid);
+
+          int left = track_container[objid]->leftbase(r.left);
+          int right = track_container[objid]->rightbase(r.right);
+          // the static_cast should be ok, since basepairs line up on 
+          // integral values
+          //TrackRegion track(objid, left, right); 
+          track.set(objid, left, right);
+          selected_tracks.push_back(track);
+          //clog << "selected track " << objid
+          //     << "(" << left << ", " << right << ")" << endl;
+        }
         break;
         default:
           cout << "unknown type " << objtype << " ";
@@ -135,7 +147,7 @@ void GlSeqBrowser::selectRegion(int top, int left, int bottom, int right)
   GLfloat x_scale = cur_ortho.width()/((float)viewport_size.x);
   GLfloat y_scale = cur_ortho.height()/((float)viewport_size.y);
   GLfloat x_left = cur_ortho.left + (left*x_scale);
-  GLfloat x_right = right * x_scale;
+  GLfloat x_right = cur_ortho.left + (right * x_scale);
 
   if (top > bottom) {
     // woah, someone gave us a rectangle with the origin in the lower left
@@ -170,19 +182,58 @@ void GlSeqBrowser::selectRegion(int top, int left, int bottom, int right)
   glFlush();
   glPopMatrix();
   hits = glRenderMode(GL_RENDER);
-  processSelection(hits, selectBuf, select_buf_size);
+  processSelection(hits, selectBuf, select_buf_size, selectedRegion);
+}
+
+float GlSeqBrowser::border() const
+{
+  return border_width;
 }
 
 float GlSeqBrowser::left() const
 { 
-  return max_ortho.left; 
+  float left;
+  if (track_container.size() == 0)
+  {
+    return cur_ortho.left;
+  } else {
+    vector<boost::shared_ptr<GlSequence> >::const_iterator track_i = track_container.begin();    
+    left = (*track_i)->x();
+    for( ; track_i != track_container.end(); ++track_i)
+    {
+      if ((*track_i)->x() < left) {
+        left = (*track_i)->x();
+      }
+    }
+    return left-border_width;
+  }
 }
 
 float GlSeqBrowser::right() const
 { 
-  return max_ortho.right; 
+  float right;
+  if (track_container.size() == 0) {
+    return cur_ortho.right;
+  } else {
+    vector<boost::shared_ptr<GlSequence> >::const_iterator track_i = track_container.begin();
+    right = (*track_i)->right();
+    for( ; track_i != track_container.end(); ++track_i) {
+      if ((*track_i)->right() > right)
+        right = (*track_i)->right();
+    }
+    return right+border_width;
+  }
 }
 
+float GlSeqBrowser::get_pixel_width() const
+{
+  GLint viewport[4];
+  glGetIntegerv(GL_VIEWPORT, viewport);
+  GLint vp_width = viewport[3]; // grab the viewport width
+  
+  return round((cur_ortho.right-cur_ortho.left)/vp_width);
+}  
+
 void GlSeqBrowser::setViewportCenter(float x)
 {
   update_viewport(x, zoom_level);
@@ -214,64 +265,117 @@ float GlSeqBrowser::viewportWidth() const
   return cur_ortho.right - cur_ortho.left;
 }
 
-void GlSeqBrowser::setZoom(int new_zoom)
+int GlSeqBrowser::viewportPixelHeight() const
+{
+  return viewport_size.y;
+}
+
+int GlSeqBrowser::viewportPixelWidth() const
+{
+  return viewport_size.x;
+}
+
+double GlSeqBrowser::zoomOut()
+{
+
+  if (right() - left() > 0) {
+    cur_ortho.left = left();
+    cur_ortho.right = right();
+    zoom_level =  (right() - left()) / (double)viewport_size.x;
+    return zoom_level;
+  } else {
+    // made up number representing 50 bp / pixel
+    return 50.0;
+  }
+}
+
+double GlSeqBrowser::zoomToSequence()
+{
+  // (experimentally determined zoom level)
+  const double friendly_zoom = 0.10;
+  setZoom(friendly_zoom);
+  return friendly_zoom;
+}
+
+void GlSeqBrowser::setZoom(double new_zoom)
 {
   update_viewport(viewport_center, new_zoom);
   zoom_level = new_zoom;
 }
 
-int GlSeqBrowser::zoom() const
+double GlSeqBrowser::zoom() const
 {
   return zoom_level;
 }
 
-void GlSeqBrowser::setColorMapper(AnnotationColors& cm)
+void GlSeqBrowser::setColorMapper(boost::shared_ptr<AnnotationColors> cm)
 {
   color_mapper = cm;
 }
 
-AnnotationColors& GlSeqBrowser::colorMapper()
+const AnnotationColors& GlSeqBrowser::colorMapper()
 {
-  return color_mapper;
+  return *color_mapper;
 }
 
 void GlSeqBrowser::clear()
 {
+  clear_selection();
   clear_links();
   path_segments.clear();
   track_container.clear();
 }
 
-void GlSeqBrowser::push_sequence(const Sequence &s)
+void GlSeqBrowser::clear_selection()
+{
+  selectedMode = false;
+  selectedRegion.clear();
+  selected_paths.clear();
+  selected_tracks.clear();
+}
+
+void GlSeqBrowser::push_sequence(const Sequence& s)
 {
-  GlSequence gs(s, color_mapper);
+  boost::shared_ptr<Sequence> seq_copy(new Sequence(s));
+  GlSequence gs(seq_copy, color_mapper);
   push_sequence(gs);
 }
 
-void GlSeqBrowser::push_sequence(GlSequence &gs)
+
+void GlSeqBrowser::push_sequence(boost::shared_ptr<Sequence> s)
+{
+  boost::shared_ptr<GlSequence> gs(new GlSequence(s, color_mapper));
+  push_sequence(gs);
+}
+
+void GlSeqBrowser::push_sequence(GlSequence gs)
+{
+  boost::shared_ptr<GlSequence> new_gs(new GlSequence(gs));
+  push_sequence(new_gs);
+}
+
+void GlSeqBrowser::push_sequence(boost::shared_ptr<GlSequence> gs)
 {
   clear_links();
-  pathid = 0;
   track_container.push_back(gs);
   update_layout();
   if (track_container.size() > 1)
     path_segments.push_back(pair_segment_map());
 }
 
-const std::vector<GlSequence>& GlSeqBrowser::sequences() const
+const std::vector<boost::shared_ptr<GlSequence> >& GlSeqBrowser::sequences() const
 {
   return track_container;
 }
 
 void GlSeqBrowser::clear_links()
 {
-  path_segment_map_vector::iterator psmv_i;
-  for(psmv_i = path_segments.begin();
-      psmv_i != path_segments.end();
-      ++psmv_i)
+  path_segments.clear();
+  for (int i = track_container.size()-1; i > 0; --i)
   {
-    psmv_i->clear();
+    path_segments.push_back(pair_segment_map());
   }
+  pathid = 0;
 }
 
 void 
@@ -281,6 +385,12 @@ GlSeqBrowser::link(const vector<int>& path, const vector<bool>& rc, int length)
     // should i throw an error instead?
     return;
   }
+  if (path.size() != track_container.size() ) {
+    stringstream msg;
+    msg << "Path size [" << path.size() << "] and track size [" 
+        << track_container.size() << "] don't match" << endl;
+    throw mussa_error(msg.str());
+  }
   if (path.size() != rc.size()) {
     throw runtime_error("path and reverse compliment must be the same length");
   }
@@ -295,17 +405,23 @@ GlSeqBrowser::link(const vector<int>& path, const vector<bool>& rc, int length)
     pair_segment_map::iterator found_segment = path_segments[track_i].find(p);
     if (found_segment == path_segments[track_i].end()) {
       // not already found
-      float y1 = track_container[track_i].y();
-            y1 -= track_container[track_i].height()/2;
-      float y2 = track_container[track_i+1].y();
-            y2 += track_container[track_i+1].height()/2;
-            
-      Segment s(prev_x, y1, *path_i, y2, prev_rc);
+      float y1 = track_container[track_i]->y();
+            y1 -= track_container[track_i]->height()/2;
+      float y2 = track_container[track_i+1]->y();
+            y2 += track_container[track_i+1]->height()/2;
+      
+      bool rcFlag = (prev_rc or *rc_i) and !(prev_rc and *rc_i);
+      Segment s(prev_x, y1, *path_i, y2, rcFlag, length);
       s.path_ids.insert(pathid);
       path_segments[track_i][p] = s;
     } else {
       //found
       found_segment->second.path_ids.insert(pathid);
+      // make each segment the size of the largest of any link between these 
+      // two bases
+      if (found_segment->second.length < length) {
+        found_segment->second.length = length;
+      }
     }
     prev_x = *path_i;
     prev_rc = *rc_i;
@@ -317,59 +433,203 @@ GlSeqBrowser::link(const vector<int>& path, const vector<bool>& rc, int length)
   ++pathid;
 }
 
+void GlSeqBrowser::setSelectedPaths(std::vector<int> paths)
+{
+  selected_paths.clear();
+  for(std::vector<int>::iterator itor = paths.begin();
+      itor != paths.end();
+      ++itor)
+  {
+    selected_paths.insert(*itor);
+  }
+}
+
 const set<int>& GlSeqBrowser::selectedPaths() const
 {
   return selected_paths;
 }
 
-void GlSeqBrowser::update_viewport(float center, int new_zoom)
+void GlSeqBrowser::appendSelectedTrack(GLuint track, int start, int stop)
+{
+  selected_tracks.push_back(TrackRegion(track, start, stop));
+}
+
+list<TrackRegion> GlSeqBrowser::selectedTracks() const 
+{
+  return selected_tracks;
+}
+
+//! copy sequence from selected track using formating function
+template<class Item>
+size_t GlSeqBrowser::copySelectedTracks(std::list<Item>& result, 
+             Item (*formatter)(boost::shared_ptr<Sequence> s, 
+                               int left, 
+                               int right))
+{
+  size_t base_pairs_copied = 0;
+  result.clear();
+
+  for(selected_track_iterator track_i = selected_tracks.begin();
+      track_i != selected_tracks.end();
+      ++track_i)
+  {
+    int track_index = track_i->track_id;
+    if (track_index >= track_container.size()) {
+      // should this be an exception instead?
+      clog << "track " << track_index << " > " << track_container.size() 
+           << endl;
+    } else {
+      // we should be safe
+      boost::shared_ptr<Sequence> seq = track_container[track_index]->sequence();
+      result.push_back(formatter(seq, track_i->left, track_i->right));
+      base_pairs_copied += max(track_i->right-track_i->left, 0);
+    }
+  }
+  return base_pairs_copied;
+}
+
+//! copy sequence from selected tracks as FASTA sequences
+size_t GlSeqBrowser::copySelectedTracksAsFasta(std::string& copy_buffer)
 {
-  float max_width = max_ortho.width();
-  // division by zero is a major bummer
-  if (new_zoom < 1) {
-    new_zoom = 1;
+  std::list<std::string> result;
+  struct AsFasta {
+    static string formatter(boost::shared_ptr<Sequence> seq, 
+                            int left, 
+                            int right)
+    {
+      stringstream s;
+      s << ">" << seq->get_fasta_header() 
+        << "|" << "subregion=" << left << "-" << right+1
+        << std::endl
+        << seq->subseq(left, right-left+1) << std::endl;
+      return s.str();
+    }
+  };
+  size_t base_pairs_copied = copySelectedTracks(result, AsFasta::formatter);
+  // I wish there was some way to use for_each and bind here
+  for (list<string>::iterator result_i = result.begin();
+       result_i != result.end();
+       ++result_i)
+  {
+    copy_buffer.append(*result_i);
   }
-  float new_max_width = max_width / new_zoom;
-  cur_ortho.left = center-new_max_width;
-  cur_ortho.right = center+new_max_width;
+  return base_pairs_copied;
+}
+
+//! copy sequence from selected tracks as new sequences
+size_t GlSeqBrowser::copySelectedTracksAsSequences(std::list<Sequence>& result)
+{
+  struct AsSequence {
+    static Sequence formatter(boost::shared_ptr<Sequence> seq, 
+                              int left, 
+                              int right)
+    {
+      return seq->subseq(left, right-left+1);
+    }
+  };
+  return copySelectedTracks(result, AsSequence::formatter);
+}
+
+size_t GlSeqBrowser::copySelectedTracksAsSeqLocation(
+    std::list<SequenceLocation>& result)
+{
+  struct AsSeqLocation {
+    static SequenceLocation formatter(boost::shared_ptr<Sequence> seq, 
+                                      int left, 
+                                      int right)
+    {
+      return SequenceLocation(seq, left, right);
+    }
+  };
+  return copySelectedTracks(result, AsSeqLocation::formatter);
+}
+
+//! copy sequence from selected tracks as plain sequences
+size_t GlSeqBrowser::copySelectedTracksAsString(std::string& copy_buffer)
+{
+  std::list<string> result;
+  struct AsString {
+    static string formatter(boost::shared_ptr<Sequence> seq, 
+                            int left, 
+                            int right)
+    {
+      stringstream s;
+      s << seq->subseq(left, right-left+1);
+      return s.str();
+    }
+  };
+
+  size_t base_pairs_copied = copySelectedTracks(result, AsString::formatter);
+  // I wish there was some way to use for_each and bind here
+  for (list<string>::iterator result_i = result.begin();
+       result_i != result.end();
+       ++result_i)
+  {
+    copy_buffer.append(*result_i);
+  }
+  return base_pairs_copied;
+}
+
+void GlSeqBrowser::centerOnPath(const vector<int>& paths)
+{
+  if (paths.size() != track_container.size()) {
+    throw mussa_error("Path length didn't match the number of sequences");
+  }
+
+  for(size_t track_i = 0; track_i != track_container.size(); ++track_i)
+  {
+    // -15 = shift more to the left
+    track_container[track_i]->setX((viewport_center-15) - paths[track_i]);
+  }
+}
+
+void GlSeqBrowser::update_viewport(float center, double new_zoom)
+{
+  // limit how close we can get
+  if (new_zoom < 0.01) {
+    new_zoom = 0.01;
+  }
+  double new_width = (new_zoom * (float)viewport_size.x);
+  cur_ortho.left = center-new_width/2.0;
+  cur_ortho.right = center+new_width/2.0;
 }
 
 void GlSeqBrowser::update_layout()
 {
-  typedef std::vector<GlSequence>::iterator glseq_itor_type;
-  float available_height = (float)cur_ortho.top - 2 * (float)border;
+  typedef std::vector<boost::shared_ptr<GlSequence> >::iterator glseq_itor_type;
+  float available_height = (float)cur_ortho.top - 2 * (float)border_width;
   float max_base_pairs = 0;
   size_t track_count = track_container.size();
 
   if (track_count > 1) {
     // we have several sequences
     float track_spacing = available_height / (track_count-1);
-    float y = available_height + (float)border;
+    float y = available_height + (float)border_width;
     for(glseq_itor_type seq_i = track_container.begin();
         seq_i != track_container.end();
         ++seq_i, y-=track_spacing)
     {
-      seq_i->setX(0);
-      seq_i->setY(y);
-      if (seq_i->length() > max_base_pairs)
-        max_base_pairs = seq_i->length();
+      (*seq_i)->setX(0);
+      (*seq_i)->setY(y);
+      if ((*seq_i)->size() > max_base_pairs)
+        max_base_pairs = (*seq_i)->size();
     }
   } else if (track_count == 1) {
     // center the single track
     glseq_itor_type seq_i = track_container.begin();
-    seq_i->setX(0);
-    seq_i->setY(viewport_size.x /2);
-    max_base_pairs = seq_i->length();
+    (*seq_i)->setX(0);
+    (*seq_i)->setY(viewport_size.x /2);
+    max_base_pairs = (*seq_i)->size();
   } else {
     // nothing to do as we're empty
     return;
   }
-  max_ortho.right = max_base_pairs + border;
-  max_ortho.left = -border;
-  max_ortho.top = viewport_size.x;
-  max_ortho.bottom = 0;
-  cur_ortho = max_ortho;
+  cur_ortho.right = max_base_pairs + border_width;
+  cur_ortho.left = -border_width;
+  cur_ortho.top = viewport_size.x;
+  cur_ortho.bottom = 0;
   viewport_center = (cur_ortho.width()/2) + cur_ortho.left;
+  zoomOut();
 }
 
 void GlSeqBrowser::draw() const
@@ -404,14 +664,19 @@ void GlSeqBrowser::draw_tracks() const
   for(size_t track_i = 0; track_i != track_container.size(); ++track_i)
   {
     glPushName(track_i);
-    track_container[track_i].draw(cur_ortho.left, cur_ortho.right);
+    track_container[track_i]->draw(cur_ortho.left, cur_ortho.right);
     glPopName();
   }
 }
 
 void GlSeqBrowser::draw_segments() const
 {
-  glLineWidth(0.1);
+  glLineWidth(1);
+  glEnable(GL_BLEND);
+  glDepthMask(GL_FALSE);
+  const float zdepth = -1.0;
+  const float min_segment_width = max((float)(1.0), get_pixel_width());
+  
   // each vector contains path_segment_maps of all the connections
   // between this track and the next
   path_segment_map_vector::const_iterator psmv_i;
@@ -439,26 +704,54 @@ void GlSeqBrowser::draw_segments() const
                        back_inserter(selected));
 
       if (not s.reversed) {
+        // forward
         if (selected_paths.size() == 0 or selected.size() > 0) {
-          glColor3f(1.0, 0.0, 0.0);
+          glColor4f(1.0, 0.0, 0.0, 1.0);
         } else {
-          glColor3f(1.0, 0.8, 0.8);
+          glColor4f(1.0, 0.7, 0.7, 0.4);
         }
       } else { 
+        // reverse
         if (selected_paths.size() == 0 or selected.size() > 0) {
-          glColor3f(0.0, 0.0, 1.0);
+          glColor4f(0.0, 0.0, 1.0, 1.0);
         } else {
-          glColor3f(0.8, 0.8, 1.0);
+          glColor4f(0.7, 0.7, 1.0, 0.4);
         }
       }
       // save the multipart name for our segment
       glPushName(path_index); glPushName(key.first); glPushName(key.second);
-      glBegin(GL_LINES);
-      glVertex3f(s.start.x, s.start.y, -1);
-      glVertex3f(s.end.x, s.end.y, -1);
-      glEnd();
+      float seq_start_x = s.start.x 
+                        + track_container[path_index]->x();
+      float seq_end_x = s.end.x
+                      + track_container[path_index+1]->x();
+      if (s.length <= min_segment_width) {
+        // use lines for elements of length <=1 or < 1 pixel.
+        // and try to center the line
+        const float offset = s.length * 0.5;
+        glBegin(GL_LINES);
+          glVertex3f(seq_start_x+offset, s.start.y, -1);
+          glVertex3f(seq_end_x  +offset, s.end.y, -1);
+        glEnd();
+      } else {
+        // otherwise use quads
+        // compute length
+        float seq_start_x_length = s.start.x 
+                                 + s.length
+                                 + track_container[path_index]->x();
+        float seq_end_x_length = s.end.x
+                               + s.length
+                               + track_container[path_index+1]->x();
+        glBegin(GL_QUADS);
+          glVertex3f(seq_start_x, s.start.y, zdepth);
+          glVertex3f(seq_end_x, s.end.y, zdepth);
+          glVertex3f(seq_end_x_length, s.end.y, zdepth);
+          glVertex3f(seq_start_x_length, s.start.y, zdepth);
+        glEnd();
+      }      
       // clear the names
       glPopName(); glPopName(); glPopName();
     }
   }
+  glDepthMask(GL_TRUE);
+  glDisable(GL_BLEND);
 }