view layer/IntervalLayer.cpp @ 18:d8e6709e9075

add - EasaierSessionManager - Easaier menus - Interval model
author lbajardsilogic
date Mon, 14 May 2007 13:13:14 +0000
parents
children 66af7c1b10d9
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sound Access
	EASAIER client application.
	Silogic 2007. Luc Barthélémy.

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#include <QObject>
#include <QColor>
#include <QPainter>
#include <QMouseEvent>
#include <QInputDialog>

#include "layer/IntervalLayer.h"
#include "view/View.h"
#include "system/System.h"
#include "widgets/ItemEditDialog.h"


const int gIntervalHeight = 30;

//****************************************************************
//* Function: Constructor
//* Description: declaration and initialisation of all private parameters
//****************************************************************
IntervalLayer::IntervalLayer():
	Layer(),
	m_model(0),
    m_editing(false),
    m_colour(Qt::darkRed),
	m_editingCommand(0)
{
}

IntervalLayer::~IntervalLayer()
{}

void IntervalLayer::setModel(IntervalModel* model)
{
    if (m_model == model) return;
    m_model = model;

    connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged()));
    connect(m_model, SIGNAL(modelChanged(size_t, size_t)),
	    this, SIGNAL(modelChanged(size_t, size_t)));

    connect(m_model, SIGNAL(completionChanged()),
	    this, SIGNAL(modelCompletionChanged()));

    emit modelReplaced();
}

Layer::PropertyList
IntervalLayer::getProperties() const
{
    PropertyList list;
    list.push_back("Colour");
    return list;
}

Layer::PropertyType
IntervalLayer::getPropertyType(const PropertyName &name) const
{
    return ValueProperty;
}

int
IntervalLayer::getPropertyRangeAndValue(const PropertyName &name,
                                    int *min, int *max, int *deflt) const
{
    //!!! factor this colour handling stuff out into a colour manager class
	int val = 0;

    if (name == "Colour") {

		if (min) *min = 0;
		if (max) *max = 5;
		if (deflt) *deflt = 0;

		if (m_colour == Qt::black) val = 0;
		else if (m_colour == Qt::darkRed) val = 1;
		else if (m_colour == Qt::darkBlue) val = 2;
		else if (m_colour == Qt::darkGreen) val = 3;
		else if (m_colour == QColor(200, 50, 255)) val = 4;
		else if (m_colour == QColor(255, 150, 50)) val = 5;
	}

    return val;
}

QString
IntervalLayer::getPropertyValueLabel(const PropertyName &name,
				    int value) const
{
    if (name == "Colour") {
		switch (value) {
			default:
			case 0: return tr("Black");
			case 1: return tr("Red");
			case 2: return tr("Blue");
			case 3: return tr("Green");
			case 4: return tr("Purple");
			case 5: return tr("Orange");
		}
	}
    return tr("<unknown>");
}


void IntervalLayer::setProperties(const QXmlAttributes &attributes)
{
    QString colourSpec = attributes.value("colour");
    if (colourSpec != "") {
		QColor colour(colourSpec);
		if (colour.isValid()) {
			setBaseColour(QColor(colourSpec));
		}
    }

}

void
IntervalLayer::setProperty(const PropertyName &name, int value)
{
    if (name == "Colour") {
		switch (value) {
			default:
			case 0:	setBaseColour(Qt::black); break;
			case 1: setBaseColour(Qt::darkRed); break;
			case 2: setBaseColour(Qt::darkBlue); break;
			case 3: setBaseColour(Qt::darkGreen); break;
			case 4: setBaseColour(QColor(200, 50, 255)); break;
			case 5: setBaseColour(QColor(255, 150, 50)); break;
		}
	}
}

void
IntervalLayer::setBaseColour(QColor colour)
{
    if (m_colour == colour) return;
    m_colour = colour;
    emit layerParametersChanged();
}

QString
IntervalLayer::toXmlString(QString indent, QString extraAttributes) const
{
    return Layer::toXmlString(indent, extraAttributes +
			      QString(" colour=\"%1\"")
			      .arg(encodeColour(m_colour)));
}

QString
IntervalLayer::toEasaierXmlString(QString indent, QString extraAttributes) const
{
    return Layer::toEasaierXmlString(indent, extraAttributes +
			      QString(" colour=\"%1\"")
			      .arg(encodeColour(m_colour)));
}

QString
IntervalLayer::getPropertyLabel(const PropertyName &name) const
{
    if (name == "Colour") return tr("Colour");
    return "";
}

int
IntervalLayer::getYForHeight(View *v, float height) const
{
    int h = v->height();
    return h - int(height * h);
}

float
IntervalLayer::getHeightForY(View *v, int y) const
{
    int h = v->height();
    return float(h - y) / h;
}


void 
IntervalLayer::paint(View *v, QPainter &paint, QRect rect) const
{
    if (!m_model || !m_model->isOK()) return;

    int sampleRate = m_model->getSampleRate();
    if (!sampleRate) return;

    int x0 = rect.left(), x1 = rect.right();
    long frame0 = v->getFrameForX(x0);
    long frame1 = v->getFrameForX(x1);
    	
	paint.save();

    QColor brushColour(m_colour);
    brushColour.setAlpha(100);
	QColor penColour = Qt::black;

	paint.setPen(m_colour);

	IntervalList& intervals = m_model->intervals();
	
	int	 xS,xE,y;
	bool draw, drawText, drawStart, drawEnd;

    QPoint localPos;
    long illuminateX = -1;

    if (v->shouldIlluminateLocalFeatures(this, localPos)) {
		// should highlight one endpoint ?
		illuminateX = localPos.x();
	}

    for (IntervalListConstIterator i = intervals.begin(); i != intervals.end(); ++i) 
	{
		draw = drawText = drawStart = drawEnd = false;
		
		TimeIntervalPtr ti = (*i);
		
		if (ti->start() >= frame0 && ti->start() < frame1)
		{
			xS = v->getXForFrame(ti->start());
			xE = v->getXForFrame(ti->end());
			if (xE <= x1)
				drawEnd = true;
			else
				xE = x1;	
			draw = drawText = drawStart = true;
		}
		else if (ti->end() > frame0 && ti->end <= frame1)
		{
			xS = v->getXForFrame(ti->start());
			if (xS < x0)
				xS = x0;
			xE = v->getXForFrame(ti->end());
			draw = drawEnd = true;
		}
		else if (ti->start <= frame0 && ti->end >= frame1)
		{
			xS = x0;
			xE = x1;
			draw = true;
		}
		if (draw)
		{
			y = getYForHeight(v, ti->value());
			paint.setBrush(brushColour);
			paint.fillRect(xS, y-gIntervalHeight/2, (xE-xS+1), gIntervalHeight, brushColour);
		}
		if (drawStart)
		{
			if (abs(illuminateX - xS) < 3)
				paint.setPen(Qt::black);
			else
				paint.setPen(brushColour);
			paint.drawLine(xS, 0, xS, v->height() - 1);
		}
		if (drawEnd)
		{
			if (abs (illuminateX - xE) < 3)
				paint.setPen(Qt::black);
			else
				paint.setPen(brushColour);
			paint.drawLine(xE, 0, xE, v->height() - 1);
		}
		if (drawText)
		{
			paint.setPen(penColour);
			paint.drawText(xS+10, y, ti->label());
		}
	}

	paint.restore();
}


bool
IntervalLayer::getValueExtents(float &min, float &max,
                           bool &logarithmic, QString &unit) const
{
    return false;
}

IntervalList 
IntervalLayer::getIntervalAt(View *v, int x, int y) // y < 0 means you don't make any test on it
{
	IntervalList result;
	IntervalList& intervals = m_model->intervals();

    for (IntervalListIterator i = intervals.begin(); i != intervals.end(); ++i) 
	{			
		TimeIntervalPtr ti = (*i);

		int yT = getYForHeight(v, ti->value());
		int xS = v->getXForFrame(ti->start());
		int xE = v->getXForFrame(ti->end());
		if ((x >= xS) && (x <= xE)) 
		{
			if ((y < 0) || (abs(y - yT) <= gIntervalHeight/2))
				result.push_back(ti);
		}
	}

	return result;
}

IntervalList 
IntervalLayer::getInterval(long start, long end) 
{
	IntervalList result;
	IntervalList& intervals = m_model->intervals();

    for (IntervalListIterator i = intervals.begin(); i != intervals.end(); ++i) 
	{			
		TimeIntervalPtr ti = (*i);

		if ( (start <= ti->start()) && (ti->start() <= end) &&
			 (start <= ti->end()) && (ti->end() <= end) ) 
		{
			result.push_back(ti);
		}
	}

	return result;
}

QString 
IntervalLayer::getFeatureDescription(View *v, QPoint& pos) const
{
    if (!m_model || !m_model->getSampleRate()) return "";

	QString description;

    int x = pos.x();
	int y = pos.y();

	long frame = v->getFrameForX(x);	

	IntervalList& intervals = m_model->intervals();
    for (IntervalListConstIterator i = intervals.begin(); i != intervals.end(); ++i) 
	{
		TimeIntervalPtr ti = (*i);
		
		if (ti->start() <= frame && ti->end() >= frame)
		{
			int y = getYForHeight(v, ti->value());
			
			if (abs(y - pos.y()) <= gIntervalHeight/2)
			{
				RealTime rtStart = RealTime::frame2RealTime(ti->start(), m_model->getSampleRate());
				RealTime rtEnd = RealTime::frame2RealTime(ti->end(), m_model->getSampleRate());
				RealTime rtDuration = rtEnd - rtStart;

				description = QString(tr("Interval:\t%1\nStart:\t%2 End:\t%3\tDuration:\t%4"))
					.arg(ti->label())
					.arg(rtStart.toText(true).c_str())
					.arg(rtEnd.toText(true).c_str())
					.arg(rtDuration.toText(true).c_str());
				break;
			}
		}
	}

	return description;
}


void
IntervalLayer::drawStart(View *v, QMouseEvent *e)
{
	if (!m_model) {
		std::cerr << "IntervalLayer::drawStart: no model" << std::endl;
		return;
    }

    long frame = v->getFrameForX(e->x());
    
	if (frame < 0) 
		frame = 0;
    
	frame = frame / m_model->getResolution() * m_model->getResolution();

    int height = (int) (getHeightForY(v, e->y())*100+0.5);
	float value = ((float) height)/100;
	
	m_editingInterval = new TimeInterval(frame, frame, "", value);

	m_model->addInterval(m_editingInterval);

    if (m_editingCommand) 
	{
		CommandHistory::getInstance()->addCommand(m_editingCommand, false);
		m_editingCommand = 0;
	}

	m_editingCommand = new IntervalModel::IntervalCommand(m_model, m_editingInterval, 
		IntervalModel::IntervalCommand::Creation ,
		frame, frame, value, "");
    
    m_editing = 1;
}

void
IntervalLayer::drawDrag(View *v, QMouseEvent *e)
{
	if (!m_model || !m_editing) return;

    long frame = v->getFrameForX(e->x());

    if (frame < 0) 
		frame = 0;
    
	frame = frame / m_model->getResolution() * m_model->getResolution();

	long start = m_editingInterval->start();
	long end = frame;

	if (start < end)
	{
		m_editing = 2;
		m_editingInterval->end(frame);
		m_editingCommand->newEnd(frame);
	} else {
		m_editing = 1;
		m_editingInterval->start(frame);
		m_editingCommand->newStart(frame);
	}
}

void
IntervalLayer::drawEnd(View *v, QMouseEvent *e)
{
	if (!m_model || !m_editing) 
		return;

    bool ok = false;
    QString label = QInputDialog::getText(v, tr("Enter label"),
					  tr("Please enter a new label:"),
					  QLineEdit::Normal, "", &ok);

    if (ok) {
		m_editingInterval->label(label);
		m_editingCommand->newLabel(label);
    }

	if (m_editingCommand) 
	{
		CommandHistory::getInstance()->addCommand(m_editingCommand, false);
		m_editingCommand = 0;
	}
    
	m_editing = false;
}

void
IntervalLayer::editStart(View *view, QMouseEvent *evt)
{
	if (!m_model)
		return;

	IntervalList intervals = getIntervalAt(view, evt->x(), evt->y());

	 for (IntervalListConstIterator i = intervals.begin(); i != intervals.end(); ++i) 
	 {
		TimeIntervalPtr ti = (*i);

		int xS = view->getXForFrame(ti->start());
		int xE = view->getXForFrame(ti->end());

		if (abs(xS - evt->x()) < 5)
		{
			m_editing  = 1; // editing start
			m_editingInterval = ti;
			break;
		}
		else if (abs(xE - evt->x()) < 5)
		{
			m_editing  = 2; // editing end
			m_editingInterval = ti;
			break;
		}
		else 
		{
			m_editing = 0;
		}
	 }

	if (m_editing)
		view->setCursor(Qt::SizeHorCursor);

    if (m_editingCommand) 
	{
		CommandHistory::getInstance()->addCommand(m_editingCommand, false);
		m_editingCommand = 0;
	}

}

void
IntervalLayer::editDrag(View *view, QMouseEvent *evt)
{
    if (!m_model || !m_editing) 
		return;

	long frame = view->getFrameForX(evt->x());

    if (!m_editingCommand) 
	{
		m_editingCommand = new IntervalModel::IntervalCommand(m_model, m_editingInterval, IntervalModel::IntervalCommand::Edition,
					m_editingInterval->start(),
					m_editingInterval->end(),
					m_editingInterval->value(),
					m_editingInterval->label());
    }

	if (m_editing == 1) // start
	{
		if (frame < m_editingInterval->end())
			m_editingCommand->newStart(frame);
	}
	else if (m_editing == 2) // end
	{
		if (frame > m_editingInterval->start())
			m_editingCommand->newEnd(frame);
	}
}

void
IntervalLayer::editEnd(View *view, QMouseEvent *evt)
{
    if (!m_model || !m_editing) 
		return;

	view->setCursor(Qt::UpArrowCursor);

	if (m_editingCommand)
	{
		CommandHistory::getInstance()->addCommand(m_editingCommand, false);
		m_editingCommand = 0;
    }

    
    m_editing = false;
}

void
IntervalLayer::editOpen(View *view, QMouseEvent *evt) // on double-click
{
    if (!m_model) return;

	IntervalList intervals = getIntervalAt(view, evt->x(), evt->y());
	if (! intervals.empty())
	{
		TimeIntervalPtr ti = intervals.front();

		bool ok = false;
	    ItemEditDialog *dialog = new ItemEditDialog
		        (m_model->getSampleRate(),
					 ItemEditDialog::ShowTime |
					 ItemEditDialog::ShowDuration |
					 ItemEditDialog::ShowText |
					 ItemEditDialog::ShowValue);

		dialog->setFrameTime(ti->start());
		dialog->setFrameDuration(ti->end() - ti->start());
	    dialog->setText(ti->label());
		dialog->setValue((int)(ti->value()*100+0.5));

	    if (dialog->exec() == QDialog::Accepted) 
		{

			IntervalModel::IntervalCommand *command =
				new IntervalModel::IntervalCommand(m_model, ti, IntervalModel::IntervalCommand::Edition,
					dialog->getFrameTime(),
					dialog->getFrameTime() + dialog->getFrameDuration(),
					dialog->getValue()/100,
					dialog->getText());

			CommandHistory::getInstance()->addCommand(command);
			
		}
	}
}

void
IntervalLayer::moveSelection(Selection s, size_t newStartFrame)
{
	if (!m_model) return;

	IntervalList intervals = getInterval(s.getStartFrame(), s.getEndFrame());

	MacroCommand * command = new MacroCommand("Drag Selection");

	long newStart = 0;
	long newEnd   = 0;

	for (IntervalListConstIterator i = intervals.begin(); i != intervals.end(); ++i) 
	{
		TimeIntervalPtr ti = (*i);

		newStart = ti->start() + ((long) (newStartFrame - s.getStartFrame()));
		newEnd   = ti->end() + ((long) (newStartFrame - s.getStartFrame()));
		
		IntervalModel::IntervalCommand *intervalCommand =
			new IntervalModel::IntervalCommand(m_model, ti, IntervalModel::IntervalCommand::Edition,
				newStart,
				newEnd,
				ti->value(),
				ti->label());

		m_model->changeInterval(ti, newStart, newEnd, ti->value(), ti->label() );
		
		command->addCommand(intervalCommand);
	}

	CommandHistory::getInstance()->addCommand(command, false);
}

void
IntervalLayer::resizeSelection(Selection s, Selection newSize)
{
    if (!m_model) return;

	IntervalList intervals = getInterval(s.getStartFrame(), s.getEndFrame());

	MacroCommand * command = new MacroCommand("Resize Selection");

	long newStart = 0;
	long newEnd   = 0;

	double ratio = ((double) (newSize.getEndFrame() - newSize.getStartFrame())) /
					((double) (s.getEndFrame() - s.getStartFrame()));

	for (IntervalListConstIterator i = intervals.begin(); i != intervals.end(); ++i) 
	{
		TimeIntervalPtr ti = (*i);

		newStart = (long) newSize.getStartFrame() + (long) ( ((double) ti->start() - (double) s.getStartFrame()) * ratio );
		newEnd = (long) newSize.getStartFrame() + (long) ( ((double) ti->end() - (double) s.getStartFrame()) * ratio );
		
		IntervalModel::IntervalCommand *intervalCommand =
			new IntervalModel::IntervalCommand(m_model, ti, IntervalModel::IntervalCommand::Edition,
				newStart,
				newEnd,
				ti->value(),
				ti->label());

		m_model->changeInterval(ti, newStart, newEnd, ti->value(), ti->label() );
		
		command->addCommand(intervalCommand);
	}

	CommandHistory::getInstance()->addCommand(command, false);
}

void
IntervalLayer::deleteSelection(Selection s)
{
    if (!m_model) return;

	IntervalList intervals = getInterval(s.getStartFrame(), s.getEndFrame());

	MacroCommand * command = new MacroCommand("Delete Selected Intervals");

	for (IntervalListConstIterator i = intervals.begin(); i != intervals.end(); ++i) 
	{
		TimeIntervalPtr ti = (*i);

		m_model->removeInterval(ti);
		
		IntervalModel::IntervalCommand *intervalCommand =
			new IntervalModel::IntervalCommand(m_model, ti, IntervalModel::IntervalCommand::Deletion,
				ti->start(),
				ti->end(),
				ti->value(),
				ti->label());

		command->addCommand(intervalCommand);
	}

	CommandHistory::getInstance()->addCommand(command, false);
}   

void
IntervalLayer::paste(const Clipboard &from, int frameOffset)
{
	if (!m_model) return;

    const Clipboard::PointList &points = from.getPoints();

    MacroCommand * command = new MacroCommand("Paste");

    for (Clipboard::PointList::const_iterator i = points.begin(); i != points.end(); ++i) 
	{    
        if (!i->haveFrame()) 
			continue;
        
		size_t frame = 0;
        
		if (frameOffset > 0 || -frameOffset < i->getFrame()) {
            frame = i->getFrame() + frameOffset;
        }

		TimeIntervalPtr ti = new TimeInterval((long) frame, (long) (frame+i->getDuration()), i->getLabel(), i->getValue());

		m_model->addInterval(ti);

		IntervalModel::IntervalCommand *intervalCommand =
			new IntervalModel::IntervalCommand(m_model, ti, IntervalModel::IntervalCommand::Creation,
				ti->start(),
				ti->end(),
				ti->value(),
				ti->label());

		command->addCommand(intervalCommand);
    
    }

    CommandHistory::getInstance()->addCommand(command, false);
}

void
IntervalLayer::copy(Selection s, Clipboard &to)
{
	if (!m_model) return;

	IntervalList intervals = getInterval(s.getStartFrame(), s.getEndFrame());

	for (IntervalListConstIterator i = intervals.begin(); i != intervals.end(); ++i) 
	{
		TimeIntervalPtr ti = (*i);

		Clipboard::Point point(ti->start(), ti->value(), ti->end()-ti->start(), ti->label());
        to.addPoint(point);
	}
}

bool
IntervalLayer::snapToFeatureFrame(View *v, int &frame,
			      size_t &resolution,
			      SnapType snap) const
{
    if (!m_model)
		return Layer::snapToFeatureFrame(v, frame, resolution, snap);

    resolution = m_model->getResolution();
    
	IntervalList& intervals = m_model->intervals();

    int snapped, best = frame;
    bool found = false;
	
	unsigned int dist, distMin;
	
	dist = distMin = m_model->getEndFrame();

	for (IntervalListConstIterator i = intervals.begin(); i != intervals.end(); ++i) 
	{
		TimeIntervalPtr ti = (*i);
		switch (snap)
		{
			case SnapRight:
				if (ti->start() >= frame)
				{
					dist = ti->start() - frame;
					snapped = ti->start();
				} 
				else if (ti->end() >= frame)
				{
					dist = ti->end() - frame;
					snapped = ti->end();
				}
			break;

			case SnapLeft:
				if (ti->end() <= frame)
				{
					dist = frame - ti->end();
					snapped = ti->end();
				} 
				else if (ti->start() <= frame)
				{
					dist = frame - ti->start();
					snapped = ti->start();
				}
			break;

			case SnapNearest:
			{
				int distS = abs(ti->start() - frame);
				int distE = abs(ti->end() - frame);
				if (distS < distE)
				{
					dist = distS;
					snapped = ti->start();
				} else
				{
					dist = distE;
					snapped = ti->end();
				}
			}
			break;

			case SnapNeighbouring:
			{
				int distS = abs(ti->start() - frame);
				int distE = abs(ti->end() - frame);
				if (distS < 5)
				{
					dist = distS;
					snapped = ti->start();
				} else if (distE < 5)
				{
					dist = distE;
					snapped = ti->end();
				}
			}
			break;
		}

		if (dist < distMin)
		{
			found = true;
			distMin = dist;
			best = snapped;
		}

	}


	if (found)
	{
		frame = best;
	}

    return found;
}