Chris@58: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@0: 
Chris@0: /*
Chris@59:     Sonic Visualiser
Chris@59:     An audio file viewer and annotation editor.
Chris@59:     Centre for Digital Music, Queen Mary, University of London.
Chris@59:     This file copyright 2006 Chris Cannam.
Chris@0:     
Chris@59:     This program is free software; you can redistribute it and/or
Chris@59:     modify it under the terms of the GNU General Public License as
Chris@59:     published by the Free Software Foundation; either version 2 of the
Chris@59:     License, or (at your option) any later version.  See the file
Chris@59:     COPYING included with this distribution for more information.
Chris@0: */
Chris@0: 
Chris@0: #include "TimeInstantLayer.h"
Chris@0: 
Chris@128: #include "data/model/Model.h"
Chris@0: #include "base/RealTime.h"
Chris@128: #include "view/View.h"
Chris@0: #include "base/Profiler.h"
Chris@76: #include "base/Clipboard.h"
Chris@376: #include "ColourDatabase.h"
Chris@0: 
Chris@128: #include "data/model/SparseOneDimensionalModel.h"
Chris@0: 
Chris@70: #include "widgets/ItemEditDialog.h"
Chris@358: #include "widgets/ListInputDialog.h"
Chris@70: 
Chris@0: #include <QPainter>
Chris@17: #include <QMouseEvent>
Chris@316: #include <QTextStream>
Chris@360: #include <QMessageBox>
Chris@0: 
Chris@0: #include <iostream>
Martin@46: #include <cmath>
Chris@0: 
Chris@429: //#define DEBUG_TIME_INSTANT_LAYER 1
Chris@387: 
Chris@44: TimeInstantLayer::TimeInstantLayer() :
Chris@287:     SingleColourLayer(),
Chris@0:     m_model(0),
Chris@18:     m_editing(false),
Chris@17:     m_editingPoint(0, tr("New Point")),
Chris@22:     m_editingCommand(0),
Chris@28:     m_plotStyle(PlotInstants)
Chris@0: {
Chris@308: }
Chris@308: 
Chris@308: TimeInstantLayer::~TimeInstantLayer()
Chris@308: {
Chris@0: }
Chris@0: 
Chris@0: void
Chris@0: TimeInstantLayer::setModel(SparseOneDimensionalModel *model)
Chris@0: {
Chris@0:     if (m_model == model) return;
Chris@0:     m_model = model;
Chris@0: 
Chris@320:     connectSignals(m_model);
Chris@0: 
Chris@387: #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682:     cerr << "TimeInstantLayer::setModel(" << model << ")" << endl;
Chris@387: #endif
Chris@0: 
Chris@494:     if (m_model && m_model->getRDFTypeURI().endsWith("Segment")) {
Chris@494:         setPlotStyle(PlotSegmentation);
Chris@494:     }
Chris@494: 
Chris@0:     emit modelReplaced();
Chris@0: }
Chris@0: 
Chris@0: Layer::PropertyList
Chris@0: TimeInstantLayer::getProperties() const
Chris@0: {
Chris@287:     PropertyList list = SingleColourLayer::getProperties();
Chris@87:     list.push_back("Plot Type");
Chris@0:     return list;
Chris@0: }
Chris@0: 
Chris@87: QString
Chris@87: TimeInstantLayer::getPropertyLabel(const PropertyName &name) const
Chris@87: {
Chris@87:     if (name == "Plot Type") return tr("Plot Type");
Chris@287:     return SingleColourLayer::getPropertyLabel(name);
Chris@87: }
Chris@87: 
Chris@0: Layer::PropertyType
Chris@287: TimeInstantLayer::getPropertyType(const PropertyName &name) const
Chris@0: {
Chris@287:     if (name == "Plot Type") return ValueProperty;
Chris@287:     return SingleColourLayer::getPropertyType(name);
Chris@0: }
Chris@0: 
Chris@0: int
Chris@0: TimeInstantLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216:                                            int *min, int *max, int *deflt) const
Chris@0: {
Chris@216:     int val = 0;
Chris@0: 
Chris@287:     if (name == "Plot Type") {
Chris@28: 	
Chris@28: 	if (min) *min = 0;
Chris@28: 	if (max) *max = 1;
Chris@216:         if (deflt) *deflt = 0;
Chris@28: 	
Chris@216: 	val = int(m_plotStyle);
Chris@28: 
Chris@0:     } else {
Chris@0: 	
Chris@287: 	val = SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@0:     }
Chris@0: 
Chris@216:     return val;
Chris@0: }
Chris@0: 
Chris@0: QString
Chris@0: TimeInstantLayer::getPropertyValueLabel(const PropertyName &name,
Chris@287:                                         int value) const
Chris@0: {
Chris@287:     if (name == "Plot Type") {
Chris@28: 	switch (value) {
Chris@28: 	default:
Chris@28: 	case 0: return tr("Instants");
Chris@28: 	case 1: return tr("Segmentation");
Chris@28: 	}
Chris@0:     }
Chris@287:     return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@0: }
Chris@0: 
Chris@0: void
Chris@0: TimeInstantLayer::setProperty(const PropertyName &name, int value)
Chris@0: {
Chris@287:     if (name == "Plot Type") {
Chris@28: 	setPlotStyle(PlotStyle(value));
Chris@287:     } else {
Chris@287:         SingleColourLayer::setProperty(name, value);
Chris@0:     }
Chris@0: }
Chris@0: 
Chris@0: void
Chris@28: TimeInstantLayer::setPlotStyle(PlotStyle style)
Chris@28: {
Chris@28:     if (m_plotStyle == style) return;
Chris@28:     m_plotStyle = style;
Chris@28:     emit layerParametersChanged();
Chris@28: }
Chris@28: 
Chris@0: bool
Chris@44: TimeInstantLayer::isLayerScrollable(const View *v) const
Chris@0: {
Chris@0:     QPoint discard;
Chris@44:     return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@0: }
Chris@0: 
Chris@0: SparseOneDimensionalModel::PointList
Chris@44: TimeInstantLayer::getLocalPoints(View *v, int x) const
Chris@0: {
Chris@28:     // Return a set of points that all have the same frame number, the
Chris@28:     // nearest to the given x coordinate, and that are within a
Chris@28:     // certain fuzz distance of that x coordinate.
Chris@28: 
Chris@0:     if (!m_model) return SparseOneDimensionalModel::PointList();
Chris@0: 
Chris@989:     sv_frame_t frame = v->getFrameForX(x);
Chris@0: 
Chris@0:     SparseOneDimensionalModel::PointList onPoints =
Chris@0: 	m_model->getPoints(frame);
Chris@0: 
Chris@0:     if (!onPoints.empty()) {
Chris@0: 	return onPoints;
Chris@0:     }
Chris@0: 
Chris@0:     SparseOneDimensionalModel::PointList prevPoints =
Chris@0: 	m_model->getPreviousPoints(frame);
Chris@0:     SparseOneDimensionalModel::PointList nextPoints =
Chris@0: 	m_model->getNextPoints(frame);
Chris@0: 
Chris@0:     SparseOneDimensionalModel::PointList usePoints = prevPoints;
Chris@0: 
Chris@0:     if (prevPoints.empty()) {
Chris@0: 	usePoints = nextPoints;
Chris@248:     } else if (long(prevPoints.begin()->frame) < v->getStartFrame() &&
Chris@44: 	       !(nextPoints.begin()->frame > v->getEndFrame())) {
Chris@0: 	usePoints = nextPoints;
Chris@0:     } else if (nextPoints.begin()->frame - frame <
Chris@0: 	       frame - prevPoints.begin()->frame) {
Chris@0: 	usePoints = nextPoints;
Chris@0:     }
Chris@0: 
Chris@28:     if (!usePoints.empty()) {
Chris@28: 	int fuzz = 2;
Chris@44: 	int px = v->getXForFrame(usePoints.begin()->frame);
Chris@28: 	if ((px > x && px - x > fuzz) ||
Chris@28: 	    (px < x && x - px > fuzz + 1)) {
Chris@28: 	    usePoints.clear();
Chris@28: 	}
Chris@28:     }
Chris@28: 
Chris@0:     return usePoints;
Chris@0: }
Chris@0: 
Chris@25: QString
Chris@909: TimeInstantLayer::getLabelPreceding(sv_frame_t frame) const
Chris@552: {
Chris@552:     if (!m_model) return "";
Chris@552:     SparseOneDimensionalModel::PointList points = m_model->getPreviousPoints(frame);
Chris@552:     for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin();
Chris@552:          i != points.end(); ++i) {
Chris@552:         if (i->label != "") return i->label;
Chris@552:     }
Chris@552:     return "";
Chris@552: }
Chris@552: 
Chris@552: QString
Chris@44: TimeInstantLayer::getFeatureDescription(View *v, QPoint &pos) const
Chris@0: {
Chris@25:     int x = pos.x();
Chris@0: 
Chris@25:     if (!m_model || !m_model->getSampleRate()) return "";
Chris@0: 
Chris@44:     SparseOneDimensionalModel::PointList points = getLocalPoints(v, x);
Chris@0: 
Chris@0:     if (points.empty()) {
Chris@0: 	if (!m_model->isReady()) {
Chris@25: 	    return tr("In progress");
Chris@25: 	} else {
Chris@25: 	    return tr("No local points");
Chris@0: 	}
Chris@0:     }
Chris@0: 
Chris@989:     sv_frame_t useFrame = points.begin()->frame;
Chris@0: 
Chris@0:     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
Chris@25:     
Chris@25:     QString text;
Chris@0: 
Chris@25:     if (points.begin()->label == "") {
Chris@25: 	text = QString(tr("Time:\t%1\nNo label"))
Chris@25: 	    .arg(rt.toText(true).c_str());
Chris@25:     } else {
Chris@25: 	text = QString(tr("Time:\t%1\nLabel:\t%2"))
Chris@25: 	    .arg(rt.toText(true).c_str())
Chris@25: 	    .arg(points.begin()->label);
Chris@25:     }
Chris@0: 
Chris@44:     pos = QPoint(v->getXForFrame(useFrame), pos.y());
Chris@25:     return text;
Chris@0: }
Chris@0: 
Chris@28: bool
Chris@908: TimeInstantLayer::snapToFeatureFrame(View *v, sv_frame_t &frame,
Chris@805: 				     int &resolution,
Chris@28: 				     SnapType snap) const
Chris@13: {
Chris@13:     if (!m_model) {
Chris@44: 	return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@13:     }
Chris@13: 
Chris@13:     resolution = m_model->getResolution();
Chris@28:     SparseOneDimensionalModel::PointList points;
Chris@13: 
Chris@28:     if (snap == SnapNeighbouring) {
Chris@28: 	
Chris@44: 	points = getLocalPoints(v, v->getXForFrame(frame));
Chris@28: 	if (points.empty()) return false;
Chris@28: 	frame = points.begin()->frame;
Chris@28: 	return true;
Chris@28:     }    
Chris@28: 
Chris@28:     points = m_model->getPoints(frame, frame);
Chris@908:     sv_frame_t snapped = frame;
Chris@28:     bool found = false;
Chris@13: 
Chris@13:     for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin();
Chris@13: 	 i != points.end(); ++i) {
Chris@13: 
Chris@28: 	if (snap == SnapRight) {
Chris@28: 
Chris@28: 	    if (i->frame >= frame) {
Chris@28: 		snapped = i->frame;
Chris@28: 		found = true;
Chris@13: 		break;
Chris@13: 	    }
Chris@28: 
Chris@28: 	} else if (snap == SnapLeft) {
Chris@28: 
Chris@13: 	    if (i->frame <= frame) {
Chris@28: 		snapped = i->frame;
Chris@28: 		found = true; // don't break, as the next may be better
Chris@28: 	    } else {
Chris@28: 		break;
Chris@28: 	    }
Chris@28: 
Chris@28: 	} else { // nearest
Chris@28: 
Chris@28: 	    SparseOneDimensionalModel::PointList::const_iterator j = i;
Chris@28: 	    ++j;
Chris@28: 
Chris@28: 	    if (j == points.end()) {
Chris@28: 
Chris@28: 		snapped = i->frame;
Chris@28: 		found = true;
Chris@28: 		break;
Chris@28: 
Chris@28: 	    } else if (j->frame >= frame) {
Chris@28: 
Chris@28: 		if (j->frame - frame < frame - i->frame) {
Chris@28: 		    snapped = j->frame;
Chris@28: 		} else {
Chris@28: 		    snapped = i->frame;
Chris@28: 		}
Chris@28: 		found = true;
Chris@28: 		break;
Chris@13: 	    }
Chris@13: 	}
Chris@13:     }
Chris@13: 
Chris@28:     frame = snapped;
Chris@28:     return found;
Chris@13: }
Chris@13: 
Chris@0: void
Chris@44: TimeInstantLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@0: {
Chris@0:     if (!m_model || !m_model->isOK()) return;
Chris@0: 
Chris@0: //    Profiler profiler("TimeInstantLayer::paint", true);
Chris@0: 
Chris@20:     int x0 = rect.left(), x1 = rect.right();
Chris@0: 
Chris@989:     sv_frame_t frame0 = v->getFrameForX(x0);
Chris@989:     sv_frame_t frame1 = v->getFrameForX(x1);
Chris@0: 
Chris@0:     SparseOneDimensionalModel::PointList points(m_model->getPoints
Chris@0: 						(frame0, frame1));
Chris@0: 
Chris@28:     bool odd = false;
Chris@28:     if (m_plotStyle == PlotSegmentation && !points.empty()) {
Chris@28: 	int index = m_model->getIndexOf(*points.begin());
Chris@28: 	odd = ((index % 2) == 1);
Chris@28:     }
Chris@28: 
Chris@287:     paint.setPen(getBaseQColor());
Chris@0: 
Chris@287:     QColor brushColour(getBaseQColor());
Chris@0:     brushColour.setAlpha(100);
Chris@0:     paint.setBrush(brushColour);
Chris@0: 
Chris@28:     QColor oddBrushColour(brushColour);
Chris@28:     if (m_plotStyle == PlotSegmentation) {
Chris@287: 	if (getBaseQColor() == Qt::black) {
Chris@28: 	    oddBrushColour = Qt::gray;
Chris@287: 	} else if (getBaseQColor() == Qt::darkRed) {
Chris@28: 	    oddBrushColour = Qt::red;
Chris@287: 	} else if (getBaseQColor() == Qt::darkBlue) {
Chris@28: 	    oddBrushColour = Qt::blue;
Chris@287: 	} else if (getBaseQColor() == Qt::darkGreen) {
Chris@28: 	    oddBrushColour = Qt::green;
Chris@28: 	} else {
Chris@28: 	    oddBrushColour = oddBrushColour.light(150);
Chris@28: 	}
Chris@28: 	oddBrushColour.setAlpha(100);
Chris@28:     }
Chris@28: 
Chris@587: //    SVDEBUG << "TimeInstantLayer::paint: resolution is "
Chris@585: //	      << m_model->getResolution() << " frames" << endl;
Chris@0: 
Chris@0:     QPoint localPos;
Chris@989:     sv_frame_t illuminateFrame = -1;
Chris@0: 
Chris@44:     if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@0: 	SparseOneDimensionalModel::PointList localPoints =
Chris@44: 	    getLocalPoints(v, localPos.x());
Chris@0: 	if (!localPoints.empty()) illuminateFrame = localPoints.begin()->frame;
Chris@0:     }
Chris@0: 	
Chris@23:     int prevX = -1;
Chris@79:     int textY = v->getTextLabelHeight(this, paint);
Chris@79:     
Chris@0:     for (SparseOneDimensionalModel::PointList::const_iterator i = points.begin();
Chris@0: 	 i != points.end(); ++i) {
Chris@0: 
Chris@0: 	const SparseOneDimensionalModel::Point &p(*i);
Chris@17: 	SparseOneDimensionalModel::PointList::const_iterator j = i;
Chris@17: 	++j;
Chris@0: 
Chris@44: 	int x = v->getXForFrame(p.frame);
Chris@576:         if (x == prevX && m_plotStyle == PlotInstants &&
Chris@576:             p.frame != illuminateFrame) continue;
Chris@23: 
Chris@44: 	int iw = v->getXForFrame(p.frame + m_model->getResolution()) - x;
Chris@16: 	if (iw < 2) {
Chris@17: 	    if (iw < 1) {
Chris@17: 		iw = 2;
Chris@17: 		if (j != points.end()) {
Chris@44: 		    int nx = v->getXForFrame(j->frame);
Chris@17: 		    if (nx < x + 3) iw = 1;
Chris@17: 		}
Chris@17: 	    } else {
Chris@17: 		iw = 2;
Chris@17: 	    }
Chris@16: 	}
Chris@20: 		
Chris@0: 	if (p.frame == illuminateFrame) {
Chris@287: 	    paint.setPen(getForegroundQColor(v));
Chris@0: 	} else {
Chris@0: 	    paint.setPen(brushColour);
Chris@0: 	}
Chris@23: 
Chris@28: 	if (m_plotStyle == PlotInstants) {
Chris@28: 	    if (iw > 1) {
Chris@44: 		paint.drawRect(x, 0, iw - 1, v->height() - 1);
Chris@28: 	    } else {
Chris@44: 		paint.drawLine(x, 0, x, v->height() - 1);
Chris@28: 	    }
Chris@23: 	} else {
Chris@28: 
Chris@28: 	    if (odd) paint.setBrush(oddBrushColour);
Chris@28: 	    else paint.setBrush(brushColour);
Chris@28: 	    
Chris@28: 	    int nx;
Chris@28: 	    
Chris@28: 	    if (j != points.end()) {
Chris@28: 		const SparseOneDimensionalModel::Point &q(*j);
Chris@44: 		nx = v->getXForFrame(q.frame);
Chris@28: 	    } else {
Chris@44: 		nx = v->getXForFrame(m_model->getEndFrame());
Chris@28: 	    }
Chris@28: 
Chris@28: 	    if (nx >= x) {
Chris@28: 		
Chris@28: 		if (illuminateFrame != p.frame &&
Chris@44: 		    (nx < x + 5 || x >= v->width() - 1)) {
Chris@28: 		    paint.setPen(Qt::NoPen);
Chris@28: 		}
Chris@28: 
Chris@576:                 paint.drawRect(x, -1, nx - x, v->height() + 1);
Chris@28: 	    }
Chris@28: 
Chris@28: 	    odd = !odd;
Chris@23: 	}
Chris@28: 
Chris@287: 	paint.setPen(getBaseQColor());
Chris@0: 	
Chris@0: 	if (p.label != "") {
Chris@0: 
Chris@0: 	    // only draw if there's enough room from here to the next point
Chris@0: 
Chris@0: 	    int lw = paint.fontMetrics().width(p.label);
Chris@0: 	    bool good = true;
Chris@0: 
Chris@17: 	    if (j != points.end()) {
Chris@44: 		int nx = v->getXForFrame(j->frame);
Chris@20: 		if (nx >= x && nx - x - iw - 3 <= lw) good = false;
Chris@0: 	    }
Chris@0: 
Chris@0: 	    if (good) {
Chris@576:                 v->drawVisibleText(paint, x + iw + 2, textY, p.label, View::OutlinedText);
Chris@576: //		paint.drawText(x + iw + 2, textY, p.label);
Chris@0: 	    }
Chris@0: 	}
Chris@23: 
Chris@23: 	prevX = x;
Chris@0:     }
Chris@0: }
Chris@0: 
Chris@17: void
Chris@44: TimeInstantLayer::drawStart(View *v, QMouseEvent *e)
Chris@17: {
Chris@387: #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682:     cerr << "TimeInstantLayer::drawStart(" << e->x() << ")" << endl;
Chris@387: #endif
Chris@17: 
Chris@17:     if (!m_model) return;
Chris@17: 
Chris@989:     sv_frame_t frame = v->getFrameForX(e->x());
Chris@17:     if (frame < 0) frame = 0;
Chris@21:     frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@22: 
Chris@17:     m_editingPoint = SparseOneDimensionalModel::Point(frame, tr("New Point"));
Chris@22: 
Chris@376:     if (m_editingCommand) finish(m_editingCommand);
Chris@22:     m_editingCommand = new SparseOneDimensionalModel::EditCommand(m_model,
Chris@22: 								  tr("Draw Point"));
Chris@22:     m_editingCommand->addPoint(m_editingPoint);
Chris@22: 
Chris@18:     m_editing = true;
Chris@17: }
Chris@17: 
Chris@17: void
Chris@44: TimeInstantLayer::drawDrag(View *v, QMouseEvent *e)
Chris@17: {
Chris@387: #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682:     cerr << "TimeInstantLayer::drawDrag(" << e->x() << ")" << endl;
Chris@387: #endif
Chris@17: 
Chris@18:     if (!m_model || !m_editing) return;
Chris@17: 
Chris@989:     sv_frame_t frame = v->getFrameForX(e->x());
Chris@17:     if (frame < 0) frame = 0;
Chris@21:     frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@22:     m_editingCommand->deletePoint(m_editingPoint);
Chris@17:     m_editingPoint.frame = frame;
Chris@22:     m_editingCommand->addPoint(m_editingPoint);
Chris@17: }
Chris@17: 
Chris@17: void
Chris@805: TimeInstantLayer::drawEnd(View *, QMouseEvent *)
Chris@17: {
Chris@387: #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682:     cerr << "TimeInstantLayer::drawEnd(" << e->x() << ")" << endl;
Chris@387: #endif
Chris@18:     if (!m_model || !m_editing) return;
Chris@23:     QString newName = tr("Add Point at %1 s")
Chris@23: 	.arg(RealTime::frame2RealTime(m_editingPoint.frame,
Chris@23: 				      m_model->getSampleRate())
Chris@23: 	     .toText(false).c_str());
Chris@23:     m_editingCommand->setName(newName);
Chris@376:     finish(m_editingCommand);
Chris@22:     m_editingCommand = 0;
Chris@18:     m_editing = false;
Chris@18: }
Chris@18: 
Chris@18: void
Chris@335: TimeInstantLayer::eraseStart(View *v, QMouseEvent *e)
Chris@335: {
Chris@335:     if (!m_model) return;
Chris@335: 
Chris@335:     SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
Chris@335:     if (points.empty()) return;
Chris@335: 
Chris@335:     m_editingPoint = *points.begin();
Chris@335: 
Chris@335:     if (m_editingCommand) {
Chris@376: 	finish(m_editingCommand);
Chris@335: 	m_editingCommand = 0;
Chris@335:     }
Chris@335: 
Chris@335:     m_editing = true;
Chris@335: }
Chris@335: 
Chris@335: void
Chris@805: TimeInstantLayer::eraseDrag(View *, QMouseEvent *)
Chris@335: {
Chris@335: }
Chris@335: 
Chris@335: void
Chris@335: TimeInstantLayer::eraseEnd(View *v, QMouseEvent *e)
Chris@335: {
Chris@335:     if (!m_model || !m_editing) return;
Chris@335: 
Chris@335:     m_editing = false;
Chris@335: 
Chris@335:     SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
Chris@335:     if (points.empty()) return;
Chris@335:     if (points.begin()->frame != m_editingPoint.frame) return;
Chris@335: 
Chris@335:     m_editingCommand = new SparseOneDimensionalModel::EditCommand
Chris@335:         (m_model, tr("Erase Point"));
Chris@335: 
Chris@335:     m_editingCommand->deletePoint(m_editingPoint);
Chris@335: 
Chris@376:     finish(m_editingCommand);
Chris@335:     m_editingCommand = 0;
Chris@335:     m_editing = false;
Chris@335: }
Chris@335: 
Chris@335: void
Chris@44: TimeInstantLayer::editStart(View *v, QMouseEvent *e)
Chris@18: {
Chris@387: #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682:     cerr << "TimeInstantLayer::editStart(" << e->x() << ")" << endl;
Chris@387: #endif
Chris@18: 
Chris@17:     if (!m_model) return;
Chris@18: 
Chris@44:     SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
Chris@18:     if (points.empty()) return;
Chris@18: 
Chris@18:     m_editingPoint = *points.begin();
Chris@22: 
Chris@22:     if (m_editingCommand) {
Chris@376: 	finish(m_editingCommand);
Chris@22: 	m_editingCommand = 0;
Chris@22:     }
Chris@22: 
Chris@18:     m_editing = true;
Chris@18: }
Chris@18: 
Chris@18: void
Chris@44: TimeInstantLayer::editDrag(View *v, QMouseEvent *e)
Chris@18: {
Chris@387: #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682:     cerr << "TimeInstantLayer::editDrag(" << e->x() << ")" << endl;
Chris@387: #endif
Chris@18: 
Chris@18:     if (!m_model || !m_editing) return;
Chris@18: 
Chris@989:     sv_frame_t frame = v->getFrameForX(e->x());
Chris@18:     if (frame < 0) frame = 0;
Chris@21:     frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@22: 
Chris@22:     if (!m_editingCommand) {
Chris@22: 	m_editingCommand = new SparseOneDimensionalModel::EditCommand(m_model,
Chris@22: 								      tr("Drag Point"));
Chris@22:     }
Chris@22: 
Chris@22:     m_editingCommand->deletePoint(m_editingPoint);
Chris@18:     m_editingPoint.frame = frame;
Chris@22:     m_editingCommand->addPoint(m_editingPoint);
Chris@18: }
Chris@18: 
Chris@18: void
Chris@805: TimeInstantLayer::editEnd(View *, QMouseEvent *)
Chris@18: {
Chris@387: #ifdef DEBUG_TIME_INSTANT_LAYER
Chris@682:     cerr << "TimeInstantLayer::editEnd(" << e->x() << ")" << endl;
Chris@387: #endif
Chris@18:     if (!m_model || !m_editing) return;
Chris@23:     if (m_editingCommand) {
Chris@23: 	QString newName = tr("Move Point to %1 s")
Chris@23: 	    .arg(RealTime::frame2RealTime(m_editingPoint.frame,
Chris@23: 					  m_model->getSampleRate())
Chris@23: 		 .toText(false).c_str());
Chris@23: 	m_editingCommand->setName(newName);
Chris@376: 	finish(m_editingCommand);
Chris@23:     }
Chris@22:     m_editingCommand = 0;
Chris@18:     m_editing = false;
Chris@17: }
Chris@17: 
Chris@255: bool
Chris@70: TimeInstantLayer::editOpen(View *v, QMouseEvent *e)
Chris@70: {
Chris@255:     if (!m_model) return false;
Chris@70: 
Chris@70:     SparseOneDimensionalModel::PointList points = getLocalPoints(v, e->x());
Chris@255:     if (points.empty()) return false;
Chris@70: 
Chris@70:     SparseOneDimensionalModel::Point point = *points.begin();
Chris@70: 
Chris@70:     ItemEditDialog *dialog = new ItemEditDialog
Chris@70:         (m_model->getSampleRate(),
Chris@70:          ItemEditDialog::ShowTime |
Chris@70:          ItemEditDialog::ShowText);
Chris@70: 
Chris@70:     dialog->setFrameTime(point.frame);
Chris@70:     dialog->setText(point.label);
Chris@70: 
Chris@70:     if (dialog->exec() == QDialog::Accepted) {
Chris@70: 
Chris@70:         SparseOneDimensionalModel::Point newPoint = point;
Chris@70:         newPoint.frame = dialog->getFrameTime();
Chris@70:         newPoint.label = dialog->getText();
Chris@70:         
Chris@70:         SparseOneDimensionalModel::EditCommand *command =
Chris@70:             new SparseOneDimensionalModel::EditCommand(m_model, tr("Edit Point"));
Chris@70:         command->deletePoint(point);
Chris@70:         command->addPoint(newPoint);
Chris@376:         finish(command);
Chris@70:     }
Chris@70: 
Chris@70:     delete dialog;
Chris@255:     return true;
Chris@70: }
Chris@70: 
Chris@70: void
Chris@908: TimeInstantLayer::moveSelection(Selection s, sv_frame_t newStartFrame)
Chris@43: {
Chris@99:     if (!m_model) return;
Chris@99: 
Chris@43:     SparseOneDimensionalModel::EditCommand *command =
Chris@43: 	new SparseOneDimensionalModel::EditCommand(m_model,
Chris@43: 						   tr("Drag Selection"));
Chris@43: 
Chris@43:     SparseOneDimensionalModel::PointList points =
Chris@43: 	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43: 
Chris@43:     for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
Chris@43: 	 i != points.end(); ++i) {
Chris@43: 
Chris@43: 	if (s.contains(i->frame)) {
Chris@43: 	    SparseOneDimensionalModel::Point newPoint(*i);
Chris@43: 	    newPoint.frame = i->frame + newStartFrame - s.getStartFrame();
Chris@43: 	    command->deletePoint(*i);
Chris@43: 	    command->addPoint(newPoint);
Chris@43: 	}
Chris@43:     }
Chris@43: 
Chris@376:     finish(command);
Chris@43: }
Chris@43: 
Chris@43: void
Chris@43: TimeInstantLayer::resizeSelection(Selection s, Selection newSize)
Chris@43: {
Chris@99:     if (!m_model) return;
Chris@99: 
Chris@43:     SparseOneDimensionalModel::EditCommand *command =
Chris@43: 	new SparseOneDimensionalModel::EditCommand(m_model,
Chris@43: 						   tr("Resize Selection"));
Chris@43: 
Chris@43:     SparseOneDimensionalModel::PointList points =
Chris@43: 	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43: 
Chris@43:     double ratio =
Chris@43: 	double(newSize.getEndFrame() - newSize.getStartFrame()) /
Chris@43: 	double(s.getEndFrame() - s.getStartFrame());
Chris@43: 
Chris@43:     for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
Chris@43: 	 i != points.end(); ++i) {
Chris@43: 
Chris@43: 	if (s.contains(i->frame)) {
Chris@43: 
Chris@908: 	    double target = double(i->frame);
Chris@908: 	    target = double(newSize.getStartFrame()) +
Chris@908: 		target - double(s.getStartFrame()) * ratio;
Chris@43: 
Chris@43: 	    SparseOneDimensionalModel::Point newPoint(*i);
Chris@43: 	    newPoint.frame = lrint(target);
Chris@43: 	    command->deletePoint(*i);
Chris@43: 	    command->addPoint(newPoint);
Chris@43: 	}
Chris@43:     }
Chris@43: 
Chris@376:     finish(command);
Chris@43: }
Chris@43: 
Chris@43: void
Chris@43: TimeInstantLayer::deleteSelection(Selection s)
Chris@43: {
Chris@99:     if (!m_model) return;
Chris@99: 
Chris@43:     SparseOneDimensionalModel::EditCommand *command =
Chris@43: 	new SparseOneDimensionalModel::EditCommand(m_model,
Chris@43: 						   tr("Delete Selection"));
Chris@43: 
Chris@43:     SparseOneDimensionalModel::PointList points =
Chris@43: 	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43: 
Chris@43:     for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
Chris@43: 	 i != points.end(); ++i) {
Chris@43: 	if (s.contains(i->frame)) command->deletePoint(*i);
Chris@43:     }
Chris@43: 
Chris@376:     finish(command);
Chris@43: }
Chris@76: 
Chris@76: void
Chris@359: TimeInstantLayer::copy(View *v, Selection s, Clipboard &to)
Chris@76: {
Chris@99:     if (!m_model) return;
Chris@99: 
Chris@76:     SparseOneDimensionalModel::PointList points =
Chris@76: 	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76: 
Chris@76:     for (SparseOneDimensionalModel::PointList::iterator i = points.begin();
Chris@76: 	 i != points.end(); ++i) {
Chris@76: 	if (s.contains(i->frame)) {
Chris@76:             Clipboard::Point point(i->frame, i->label);
Chris@359:             point.setReferenceFrame(alignToReference(v, i->frame));
Chris@76:             to.addPoint(point);
Chris@76:         }
Chris@76:     }
Chris@76: }
Chris@76: 
Chris@125: bool
Chris@908: TimeInstantLayer::paste(View *v, const Clipboard &from, sv_frame_t frameOffset, bool)
Chris@76: {
Chris@125:     if (!m_model) return false;
Chris@99: 
Chris@76:     const Clipboard::PointList &points = from.getPoints();
Chris@76: 
Chris@358:     bool realign = false;
Chris@358: 
Chris@360:     if (clipboardHasDifferentAlignment(v, from)) {
Chris@358: 
Chris@360:         QMessageBox::StandardButton button =
Chris@360:             QMessageBox::question(v, tr("Re-align pasted instants?"),
Chris@360:                                   tr("The instants you are pasting came from a layer with different source material from this one.  Do you want to re-align them in time, to match the source material for this layer?"),
Chris@360:                                   QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
Chris@360:                                   QMessageBox::Yes);
Chris@358: 
Chris@360:         if (button == QMessageBox::Cancel) {
Chris@360:             return false;
Chris@360:         }
Chris@358: 
Chris@360:         if (button == QMessageBox::Yes) {
Chris@360:             realign = true;
Chris@360:         }
Chris@358:     }
Chris@358: 
Chris@358:     SparseOneDimensionalModel::EditCommand *command =
Chris@358: 	new SparseOneDimensionalModel::EditCommand(m_model, tr("Paste"));
Chris@358: 
Chris@76:     for (Clipboard::PointList::const_iterator i = points.begin();
Chris@76:          i != points.end(); ++i) {
Chris@76:         
Chris@76:         if (!i->haveFrame()) continue;
Chris@359: 
Chris@908:         sv_frame_t frame = 0;
Chris@359: 
Chris@359:         if (!realign) {
Chris@359:             
Chris@359:             frame = i->getFrame();
Chris@359: 
Chris@359:         } else {
Chris@359: 
Chris@359:             if (i->haveReferenceFrame()) {
Chris@359:                 frame = i->getReferenceFrame();
Chris@359:                 frame = alignFromReference(v, frame);
Chris@359:             } else {
Chris@359:                 frame = i->getFrame();
Chris@359:             }
Chris@76:         }
Chris@359: 
Chris@359:         if (frameOffset > 0) frame += frameOffset;
Chris@359:         else if (frameOffset < 0) {
Chris@359:             if (frame > -frameOffset) frame += frameOffset;
Chris@359:             else frame = 0;
Chris@359:         }
Chris@359: 
Chris@76:         SparseOneDimensionalModel::Point newPoint(frame);
Chris@125:         if (i->haveLabel()) {
Chris@125:             newPoint.label = i->getLabel();
Chris@125:         } else if (i->haveValue()) {
Chris@125:             newPoint.label = QString("%1").arg(i->getValue());
Chris@125:         }
Chris@76:         
Chris@76:         command->addPoint(newPoint);
Chris@76:     }
Chris@76: 
Chris@376:     finish(command);
Chris@125:     return true;
Chris@76: }
Chris@43: 
Chris@287: int
Chris@287: TimeInstantLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287: {
Chris@287:     impose = false;
Chris@287:     return ColourDatabase::getInstance()->getColourIndex
Chris@287:         (QString(darkbg ? "Bright Purple" : "Purple"));
Chris@287: }
Chris@287: 
Chris@316: void
Chris@316: TimeInstantLayer::toXml(QTextStream &stream,
Chris@316:                         QString indent, QString extraAttributes) const
Chris@6: {
Chris@316:     SingleColourLayer::toXml(stream, indent,
Chris@316:                              extraAttributes +
Chris@316:                              QString(" plotStyle=\"%1\"")
Chris@316:                              .arg(m_plotStyle));
Chris@6: }
Chris@0: 
Chris@11: void
Chris@11: TimeInstantLayer::setProperties(const QXmlAttributes &attributes)
Chris@11: {
Chris@287:     SingleColourLayer::setProperties(attributes);
Chris@28: 
Chris@28:     bool ok;
Chris@28:     PlotStyle style = (PlotStyle)
Chris@28: 	attributes.value("plotStyle").toInt(&ok);
Chris@28:     if (ok) setPlotStyle(style);
Chris@11: }
Chris@11: