+ 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;