+unsigned Track::get_n_link_slots() const
+{
+ return links.size();
+}
+
+Track *Track::get_link(unsigned i) const
+{
+ if(i>=links.size())
+ throw out_of_range("Track::get_link");
+
+ return links[i];
+}
+
+int Track::get_link_slot(const Object &other) const
+{
+ for(unsigned i=0; i<links.size(); ++i)
+ if(links[i]==&other)
+ return i;
+
+ return -1;
+}
+
+bool Track::link_to(Object &other)
+{
+ Track *otrack = dynamic_cast<Track *>(&other);
+ if(!otrack)
+ return false;
+
+ float gauge_ratio = otrack->get_type().get_gauge()/type.get_gauge();
+ if(gauge_ratio<0.99 || gauge_ratio>1.01)
+ return false;
+
+ float limit = type.get_gauge();
+ if(!flex && !otrack->get_flex())
+ limit /= 10;
+ limit *= limit;
+
+ unsigned nsn = get_n_snap_nodes();
+ unsigned other_nsn = other.get_n_snap_nodes();
+ for(unsigned i=0; i<nsn; ++i)
+ {
+ Snap sn = get_snap_node(i);
+ for(unsigned j=0; j<other_nsn; ++j)
+ {
+ Snap osn = other.get_snap_node(j);
+ Vector span = osn.position-sn.position;
+ Angle da = wrap_balanced(osn.rotation-sn.rotation-Angle::half_turn());
+
+ if(dot(span, span)<limit && abs(da).radians()<0.01)
+ {
+ break_link(i);
+ otrack->break_link(j);
+ links[i] = otrack;
+ otrack->links[j] = this;
+ check_slope();
+ layout.create_blocks(*this);
+
+ signal_link_changed.emit(i, otrack);
+ otrack->signal_link_changed.emit(j, this);
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+bool Track::break_link(unsigned i)
+{
+ if(i>=links.size())
+ throw out_of_range("Track::break_link");
+
+ Track *other = links[i];
+ if(!other)
+ return false;
+
+ links[i] = 0;
+ if(!other->break_link(*this))
+ {
+ /* If the call doesn't succeed, it means that the other track already
+ broke the link and is calling us right now. Recreate blocks in the inner
+ call so it occurs before any signals are emitted. */
+ layout.create_blocks(*this);
+ }
+
+ signal_link_changed.emit(i, 0);
+
+ return true;
+}
+
+void Track::add_attachment(TrackAttachment &a)