+ 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)
+{
+ if(find(attachments.begin(), attachments.end(), &a)!=attachments.end())
+ throw key_error(&a);
+ attachments.push_back(&a);
+}
+
+void Track::remove_attachment(TrackAttachment &a)
+{
+ AttachmentList::iterator i = find(attachments.begin(), attachments.end(), &a);
+ if(i==attachments.end())
+ throw key_error(&a);
+ attachments.erase(i);
+}
+
+Track::AttachmentList Track::get_attachments_ordered(unsigned epi) const
+{
+ AttachmentList result = attachments;
+ result.sort(AttachmentCompare(epi));
+ return result;