]> git.tdb.fi Git - r2c2.git/blobdiff - source/libr2c2/trackchain.cpp
Create a base class to handle common operations in Block, Route and Zone
[r2c2.git] / source / libr2c2 / trackchain.cpp
diff --git a/source/libr2c2/trackchain.cpp b/source/libr2c2/trackchain.cpp
new file mode 100644 (file)
index 0000000..3d9d66a
--- /dev/null
@@ -0,0 +1,135 @@
+#include <msp/strings/utils.h>
+#include "layout.h"
+#include "track.h"
+#include "trackchain.h"
+
+namespace R2C2 {
+
+TrackChain::TrackChain(Layout &l):
+       layout(l)
+{
+       layout.signal_object_removed.connect(sigc::mem_fun(this, &TrackChain::object_removed));
+}
+
+void TrackChain::add_track(Track &track)
+{
+       if(tracks.count(&track))
+               return;
+
+       Validity valid = check_validity(track);
+       if(valid!=VALID)
+               throw_bad_chain(valid);
+
+       tracks.insert(&track);
+       update_ends(track);
+       on_track_added(track);
+}
+
+void TrackChain::add_tracks(const TrackSet &trks)
+{
+       TrackSet pending;
+       for(TrackSet::const_iterator i=trks.begin(); i!=trks.end(); ++i)
+               if(!tracks.count(*i))
+                       pending.insert(*i);
+
+       while(!pending.empty())
+       {
+               Validity valid = UNLINKED;
+               for(TrackSet::iterator i=pending.begin(); i!=pending.end(); ++i)
+                       if((valid=check_validity(**i))==VALID)
+                       {
+                               tracks.insert(*i);
+                               update_ends(**i);
+                               on_track_added(**i);
+                               pending.erase(i);
+                               break;
+                       }
+
+               if(valid!=VALID)
+                       throw_bad_chain(valid);
+       }
+}
+
+TrackChain::Validity TrackChain::check_validity(Track &track) const
+{
+       if(tracks.empty())
+               return VALID;
+
+       if(!ends[1])
+       {
+               if(!ends[0])
+                       return UNLINKED;
+               return ends[0]->get_link_slot(track)>=0 ? VALID : UNLINKED;
+       }
+
+       Validity result = UNLINKED;
+       for(unsigned i=0; i<2; ++i)
+       {
+               int j = ends[i]->get_link_slot(track);
+               if(j>=0)
+               {
+                       const TrackType::Endpoint &ep1 = ends[i].endpoint();
+                       const TrackType::Endpoint &ep2 = ends[i]->get_type().get_endpoint(j);
+                       if(!ep1.has_common_paths(ep2))
+                               return BAD_PATH;
+
+                       result = VALID;
+               }
+       }
+
+       return result;
+}
+
+void TrackChain::throw_bad_chain(Validity v)
+{
+       if(v==UNLINKED)
+               throw bad_chain("unlinked");
+       else if(v==BAD_PATH)
+               throw bad_chain("bad path");
+       else if(v==INCOMPATIBLE)
+               throw bad_chain("incompatible");
+}
+
+void TrackChain::update_ends(Track &track)
+{
+       if(!ends[0])
+               // We don't really know the proper endpoint yet; just pick something
+               ends[0] = TrackIter(&track, 0);
+       else if(!ends[1])
+       {
+               // We're adding the second track; determine endpoints for both
+               ends[0] = TrackIter(&*ends[0], ends[0]->get_link_slot(track));
+               ends[1] = TrackIter(&track, track.get_link_slot(*ends[0]));
+       }
+       else
+       {
+               unsigned linked = 0;
+               for(unsigned i=0; i<2; ++i)
+                       if(ends[i]->get_link_slot(track)>=0)
+                               linked |= 1<<i;
+
+               if(linked==3)
+               {
+                       // Closed loop; clear the ends
+                       ends[0] = TrackIter();
+                       ends[1] = TrackIter();
+               }
+               else
+                       ends[linked-1] = TrackIter(&track, track.get_link_slot(*ends[linked-1]));
+       }
+}
+
+bool TrackChain::has_track(Track &t) const
+{
+       return tracks.count(&t);
+}
+
+void TrackChain::object_removed(Object &obj)
+{
+       if(Track *track = dynamic_cast<Track *>(&obj))
+               tracks.erase(track);
+               /* TODO If the track was in the middle of the chain, keep only the
+               longest fragment */
+}
+
+} // namespace R2C2