Chris@49: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@8: 
Chris@8: /*
Chris@52:     Sonic Visualiser
Chris@52:     An audio file viewer and annotation editor.
Chris@52:     Centre for Digital Music, Queen Mary, University of London.
Chris@52:     This file copyright 2006 Chris Cannam.
Chris@8:     
Chris@52:     This program is free software; you can redistribute it and/or
Chris@52:     modify it under the terms of the GNU General Public License as
Chris@52:     published by the Free Software Foundation; either version 2 of the
Chris@52:     License, or (at your option) any later version.  See the file
Chris@52:     COPYING included with this distribution for more information.
Chris@8: */
Chris@8: 
Chris@8: #include "Selection.h"
Chris@314: #include <QTextStream>
Chris@8: 
Chris@8: Selection::Selection() :
Chris@8:     m_startFrame(0),
Chris@8:     m_endFrame(0)
Chris@8: {
Chris@8: }
Chris@8: 
Chris@1038: Selection::Selection(sv_frame_t startFrame, sv_frame_t endFrame) :
Chris@8:     m_startFrame(startFrame),
Chris@8:     m_endFrame(endFrame)
Chris@8: {
Chris@8:     if (m_startFrame > m_endFrame) {
Chris@1038: 	sv_frame_t tmp = m_endFrame;
Chris@8: 	m_endFrame = m_startFrame;
Chris@8: 	m_startFrame = tmp;
Chris@8:     }
Chris@8: }
Chris@8: 
Chris@8: Selection::Selection(const Selection &s) :
Chris@8:     m_startFrame(s.m_startFrame),
Chris@8:     m_endFrame(s.m_endFrame)
Chris@8: {
Chris@8: }
Chris@8: 
Chris@8: Selection &
Chris@8: Selection::operator=(const Selection &s)
Chris@8: {
Chris@8:     if (this != &s) {
Chris@8: 	m_startFrame = s.m_startFrame;
Chris@8: 	m_endFrame = s.m_endFrame;
Chris@8:     } 
Chris@8:     return *this;
Chris@8: }
Chris@8: 
Chris@8: Selection::~Selection()
Chris@8: {
Chris@8: }
Chris@8: 
Chris@8: bool
Chris@8: Selection::isEmpty() const
Chris@8: {
Chris@8:     return m_startFrame == m_endFrame;
Chris@8: }
Chris@8: 
Chris@1038: sv_frame_t
Chris@8: Selection::getStartFrame() const
Chris@8: {
Chris@8:     return m_startFrame;
Chris@8: }
Chris@8: 
Chris@1038: sv_frame_t
Chris@8: Selection::getEndFrame() const
Chris@8: {
Chris@8:     return m_endFrame;
Chris@8: }
Chris@8: 
Chris@8: bool
Chris@1038: Selection::contains(sv_frame_t frame) const
Chris@9: {
Chris@9:     return (frame >= m_startFrame) && (frame < m_endFrame);
Chris@9: }
Chris@9: 
Chris@9: bool
Chris@8: Selection::operator<(const Selection &s) const
Chris@8: {
Chris@9:     if (isEmpty()) {
Chris@9: 	if (s.isEmpty()) return false;
Chris@9: 	else return true;
Chris@9:     } else {
Chris@9: 	if (s.isEmpty()) return false;
Chris@9: 	else return (m_startFrame < s.m_startFrame);
Chris@9:     }
Chris@8: }
Chris@8: 
Chris@8: bool
Chris@8: Selection::operator==(const Selection &s) const
Chris@8: {
Chris@9:     if (isEmpty()) return s.isEmpty();
Chris@9: 
Chris@8:     return (m_startFrame == s.m_startFrame &&
Chris@8: 	    m_endFrame == s.m_endFrame);
Chris@8: }
Chris@8: 
Chris@24: 
Chris@24: MultiSelection::MultiSelection()
Chris@24: {
Chris@24: }
Chris@24: 
Chris@24: MultiSelection::~MultiSelection()
Chris@24: {
Chris@24: }
Chris@24: 
Chris@24: const MultiSelection::SelectionList &
Chris@24: MultiSelection::getSelections() const
Chris@24: {
Chris@24:     return m_selections;
Chris@24: }
Chris@24: 
Chris@24: void
Chris@24: MultiSelection::setSelection(const Selection &selection)
Chris@24: {
Chris@24:     clearSelections();
Chris@24:     addSelection(selection);
Chris@24: }
Chris@24: 
Chris@24: void
Chris@24: MultiSelection::addSelection(const Selection &selection)
Chris@24: {
Chris@24:     m_selections.insert(selection);
Chris@24: 
Chris@24:     // Cope with a sitation where the new selection overlaps one or
Chris@24:     // more existing ones.  This is a terribly inefficient way to do
Chris@24:     // this, but that probably isn't significant in real life.
Chris@24: 
Chris@24:     // It's essential for the correct operation of
Chris@24:     // getContainingSelection that the selections do not overlap, so
Chris@24:     // this is not just a frill.
Chris@24: 
Chris@24:     for (SelectionList::iterator i = m_selections.begin();
Chris@24: 	 i != m_selections.end(); ) {
Chris@24: 	
Chris@24: 	SelectionList::iterator j = i;
Chris@24: 	if (++j == m_selections.end()) break;
Chris@24: 
Chris@24: 	if (i->getEndFrame() >= j->getStartFrame()) {
Chris@24: 	    Selection merged(i->getStartFrame(),
Chris@24: 			     std::max(i->getEndFrame(), j->getEndFrame()));
Chris@24: 	    m_selections.erase(i);
Chris@24: 	    m_selections.erase(j);
Chris@24: 	    m_selections.insert(merged);
Chris@24: 	    i = m_selections.begin();
Chris@24: 	} else {
Chris@24: 	    ++i;
Chris@24: 	}
Chris@24:     }
Chris@24: }
Chris@24: 
Chris@24: void
Chris@24: MultiSelection::removeSelection(const Selection &selection)
Chris@24: {
Chris@24:     //!!! Likewise this needs to cope correctly with the situation
Chris@24:     //where selection is not one of the original selection set but
Chris@24:     //simply overlaps one of them (cutting down the original selection
Chris@24:     //appropriately)
Chris@24: 
Chris@24:     if (m_selections.find(selection) != m_selections.end()) {
Chris@24: 	m_selections.erase(selection);
Chris@24:     }
Chris@24: }
Chris@24: 
Chris@24: void
Chris@24: MultiSelection::clearSelections()
Chris@24: {
Chris@24:     if (!m_selections.empty()) {
Chris@24: 	m_selections.clear();
Chris@24:     }
Chris@24: }
Chris@24: 
Chris@300: void
Chris@1038: MultiSelection::getExtents(sv_frame_t &startFrame, sv_frame_t &endFrame) const
Chris@300: {
Chris@300:     startFrame = 0;
Chris@300:     endFrame = 0;
Chris@300:     
Chris@300:     for (SelectionList::const_iterator i = m_selections.begin();
Chris@300: 	 i != m_selections.end(); ++i) {
Chris@300: 
Chris@300:         if (i == m_selections.begin() || i->getStartFrame() < startFrame) {
Chris@300:             startFrame = i->getStartFrame();
Chris@300:         }
Chris@300: 
Chris@300:         if (i == m_selections.begin() || i->getEndFrame() > endFrame) {
Chris@300:             endFrame = i->getEndFrame();
Chris@300:         }
Chris@300:     }
Chris@300: }
Chris@300: 
Chris@24: Selection
Chris@1038: MultiSelection::getContainingSelection(sv_frame_t frame, bool defaultToFollowing) const
Chris@24: {
Chris@24:     // This scales very badly with the number of selections, but it's
Chris@24:     // more efficient for very small numbers of selections than a more
Chris@24:     // scalable method, and I think that may be what we need
Chris@24: 
Chris@24:     for (SelectionList::const_iterator i = m_selections.begin();
Chris@24: 	 i != m_selections.end(); ++i) {
Chris@24: 
Chris@24: 	if (i->contains(frame)) return *i;
Chris@24: 
Chris@24: 	if (i->getStartFrame() > frame) {
Chris@24: 	    if (defaultToFollowing) return *i;
Chris@24: 	    else return Selection();
Chris@24: 	}
Chris@24:     }
Chris@24: 
Chris@24:     return Selection();
Chris@24: }
Chris@46: 
Chris@314: void
Chris@314: MultiSelection::toXml(QTextStream &stream, QString indent,
Chris@314:                       QString extraAttributes) const
Chris@46: {
Chris@314:     stream << indent << QString("<selections %1>\n").arg(extraAttributes);
Chris@46:     for (SelectionList::iterator i = m_selections.begin();
Chris@46: 	 i != m_selections.end(); ++i) {
Chris@314: 	stream << indent
Chris@314:                << QString("  <selection start=\"%1\" end=\"%2\"/>\n")
Chris@46: 	    .arg(i->getStartFrame()).arg(i->getEndFrame());
Chris@46:     }
Chris@314:     stream << indent << "</selections>\n";
Chris@46: }
Chris@46: