Chris@58: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@35: 
Chris@35: /*
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@35:     
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@35: */
Chris@35: 
Chris@35: #include "TextLayer.h"
Chris@35: 
Chris@128: #include "data/model/Model.h"
Chris@35: #include "base/RealTime.h"
Chris@35: #include "base/Profiler.h"
Chris@376: #include "ColourDatabase.h"
Chris@128: #include "view/View.h"
Chris@35: 
Chris@128: #include "data/model/TextModel.h"
Chris@35: 
Chris@35: #include <QPainter>
Chris@35: #include <QMouseEvent>
Chris@36: #include <QInputDialog>
Chris@316: #include <QTextStream>
Chris@360: #include <QMessageBox>
Chris@35: 
Chris@35: #include <iostream>
Chris@35: #include <cmath>
Chris@35: 
Chris@44: TextLayer::TextLayer() :
Chris@287:     SingleColourLayer(),
Chris@35:     m_model(0),
Chris@35:     m_editing(false),
Chris@35:     m_originalPoint(0, 0.0, tr("Empty Label")),
Chris@35:     m_editingPoint(0, 0.0, tr("Empty Label")),
Chris@287:     m_editingCommand(0)
Chris@35: {
Chris@44:     
Chris@35: }
Chris@35: 
Chris@35: void
Chris@35: TextLayer::setModel(TextModel *model)
Chris@35: {
Chris@35:     if (m_model == model) return;
Chris@35:     m_model = model;
Chris@35: 
Chris@320:     connectSignals(m_model);
Chris@35: 
Chris@587: //    SVDEBUG << "TextLayer::setModel(" << model << ")" << endl;
Chris@35: 
Chris@35:     emit modelReplaced();
Chris@35: }
Chris@35: 
Chris@35: Layer::PropertyList
Chris@35: TextLayer::getProperties() const
Chris@35: {
Chris@287:     PropertyList list = SingleColourLayer::getProperties();
Chris@35:     return list;
Chris@35: }
Chris@35: 
Chris@87: QString
Chris@87: TextLayer::getPropertyLabel(const PropertyName &name) const
Chris@87: {
Chris@287:     return SingleColourLayer::getPropertyLabel(name);
Chris@87: }
Chris@87: 
Chris@35: Layer::PropertyType
Chris@287: TextLayer::getPropertyType(const PropertyName &name) const
Chris@35: {
Chris@287:     return SingleColourLayer::getPropertyType(name);
Chris@35: }
Chris@35: 
Chris@35: int
Chris@35: TextLayer::getPropertyRangeAndValue(const PropertyName &name,
Chris@216: 				    int *min, int *max, int *deflt) const
Chris@35: {
Chris@287:     return SingleColourLayer::getPropertyRangeAndValue(name, min, max, deflt);
Chris@35: }
Chris@35: 
Chris@35: QString
Chris@35: TextLayer::getPropertyValueLabel(const PropertyName &name,
Chris@35: 				 int value) const
Chris@35: {
Chris@287:     return SingleColourLayer::getPropertyValueLabel(name, value);
Chris@35: }
Chris@35: 
Chris@35: void
Chris@35: TextLayer::setProperty(const PropertyName &name, int value)
Chris@35: {
Chris@287:     SingleColourLayer::setProperty(name, value);
Chris@35: }
Chris@35: 
Chris@79: bool
Chris@248: TextLayer::getValueExtents(float &, float &, bool &, QString &) const
Chris@79: {
Chris@79:     return false;
Chris@79: }
Chris@79: 
Chris@35: bool
Chris@44: TextLayer::isLayerScrollable(const View *v) const
Chris@35: {
Chris@35:     QPoint discard;
Chris@44:     return !v->shouldIlluminateLocalFeatures(this, discard);
Chris@35: }
Chris@35: 
Chris@35: 
Chris@35: TextModel::PointList
Chris@44: TextLayer::getLocalPoints(View *v, int x, int y) const
Chris@35: {
Chris@35:     if (!m_model) return TextModel::PointList();
Chris@35: 
Chris@44:     long frame0 = v->getFrameForX(-150);
Chris@44:     long frame1 = v->getFrameForX(v->width() + 150);
Chris@35:     
Chris@35:     TextModel::PointList points(m_model->getPoints(frame0, frame1));
Chris@35: 
Chris@35:     TextModel::PointList rv;
Chris@552:     QFontMetrics metrics = QFontMetrics(QFont());
Chris@35: 
Chris@35:     for (TextModel::PointList::iterator i = points.begin();
Chris@35: 	 i != points.end(); ++i) {
Chris@35: 
Chris@35: 	const TextModel::Point &p(*i);
Chris@35: 
Chris@44: 	int px = v->getXForFrame(p.frame);
Chris@44: 	int py = getYForHeight(v, p.height);
Chris@35: 
Chris@35: 	QString label = p.label;
Chris@35: 	if (label == "") {
Chris@35: 	    label = tr("<no text>");
Chris@35: 	}
Chris@35: 
Chris@35: 	QRect rect = metrics.boundingRect
Chris@35: 	    (QRect(0, 0, 150, 200),
Chris@35: 	     Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
Chris@35: 
Chris@44: 	if (py + rect.height() > v->height()) {
Chris@44: 	    if (rect.height() > v->height()) py = 0;
Chris@44: 	    else py = v->height() - rect.height() - 1;
Chris@35: 	}
Chris@35: 
Chris@35: 	if (x >= px && x < px + rect.width() &&
Chris@35: 	    y >= py && y < py + rect.height()) {
Chris@35: 	    rv.insert(p);
Chris@35: 	}
Chris@35:     }
Chris@35: 
Chris@35:     return rv;
Chris@35: }
Chris@35: 
Chris@552: bool
Chris@552: TextLayer::getPointToDrag(View *v, int x, int y, TextModel::Point &p) const
Chris@552: {
Chris@552:     if (!m_model) return false;
Chris@552: 
Chris@552:     long a = v->getFrameForX(x - 120);
Chris@552:     long b = v->getFrameForX(x + 10);
Chris@552:     TextModel::PointList onPoints = m_model->getPoints(a, b);
Chris@552:     if (onPoints.empty()) return false;
Chris@552: 
Chris@552:     float nearestDistance = -1;
Chris@552: 
Chris@552:     for (TextModel::PointList::const_iterator i = onPoints.begin();
Chris@552:          i != onPoints.end(); ++i) {
Chris@552: 
Chris@552:         int yd = getYForHeight(v, (*i).height) - y;
Chris@552:         int xd = v->getXForFrame((*i).frame) - x;
Chris@562:         float distance = sqrtf(yd*yd + xd*xd);
Chris@552: 
Chris@552:         if (nearestDistance == -1 || distance < nearestDistance) {
Chris@552:             nearestDistance = distance;
Chris@552:             p = *i;
Chris@552:         }
Chris@552:     }
Chris@552: 
Chris@552:     return true;
Chris@552: }
Chris@552: 
Chris@35: QString
Chris@44: TextLayer::getFeatureDescription(View *v, QPoint &pos) const
Chris@35: {
Chris@35:     int x = pos.x();
Chris@35: 
Chris@35:     if (!m_model || !m_model->getSampleRate()) return "";
Chris@35: 
Chris@44:     TextModel::PointList points = getLocalPoints(v, x, pos.y());
Chris@35: 
Chris@35:     if (points.empty()) {
Chris@35: 	if (!m_model->isReady()) {
Chris@35: 	    return tr("In progress");
Chris@35: 	} else {
Chris@35: 	    return "";
Chris@35: 	}
Chris@35:     }
Chris@35: 
Chris@35:     long useFrame = points.begin()->frame;
Chris@35: 
Chris@35:     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate());
Chris@35:     
Chris@35:     QString text;
Chris@35: 
Chris@35:     if (points.begin()->label == "") {
Chris@35: 	text = QString(tr("Time:\t%1\nHeight:\t%2\nLabel:\t%3"))
Chris@35: 	    .arg(rt.toText(true).c_str())
Chris@35: 	    .arg(points.begin()->height)
Chris@35: 	    .arg(points.begin()->label);
Chris@35:     }
Chris@35: 
Chris@44:     pos = QPoint(v->getXForFrame(useFrame),
Chris@44: 		 getYForHeight(v, points.begin()->height));
Chris@35:     return text;
Chris@35: }
Chris@35: 
Chris@35: 
Chris@35: //!!! too much overlap with TimeValueLayer/TimeInstantLayer
Chris@35: 
Chris@35: bool
Chris@44: TextLayer::snapToFeatureFrame(View *v, int &frame,
Chris@805: 			      int &resolution,
Chris@35: 			      SnapType snap) const
Chris@35: {
Chris@35:     if (!m_model) {
Chris@44: 	return Layer::snapToFeatureFrame(v, frame, resolution, snap);
Chris@35:     }
Chris@35: 
Chris@35:     resolution = m_model->getResolution();
Chris@35:     TextModel::PointList points;
Chris@35: 
Chris@35:     if (snap == SnapNeighbouring) {
Chris@35: 	
Chris@44: 	points = getLocalPoints(v, v->getXForFrame(frame), -1);
Chris@35: 	if (points.empty()) return false;
Chris@35: 	frame = points.begin()->frame;
Chris@35: 	return true;
Chris@35:     }    
Chris@35: 
Chris@35:     points = m_model->getPoints(frame, frame);
Chris@35:     int snapped = frame;
Chris@35:     bool found = false;
Chris@35: 
Chris@35:     for (TextModel::PointList::const_iterator i = points.begin();
Chris@35: 	 i != points.end(); ++i) {
Chris@35: 
Chris@35: 	if (snap == SnapRight) {
Chris@35: 
Chris@35: 	    if (i->frame > frame) {
Chris@35: 		snapped = i->frame;
Chris@35: 		found = true;
Chris@35: 		break;
Chris@35: 	    }
Chris@35: 
Chris@35: 	} else if (snap == SnapLeft) {
Chris@35: 
Chris@35: 	    if (i->frame <= frame) {
Chris@35: 		snapped = i->frame;
Chris@35: 		found = true; // don't break, as the next may be better
Chris@35: 	    } else {
Chris@35: 		break;
Chris@35: 	    }
Chris@35: 
Chris@35: 	} else { // nearest
Chris@35: 
Chris@35: 	    TextModel::PointList::const_iterator j = i;
Chris@35: 	    ++j;
Chris@35: 
Chris@35: 	    if (j == points.end()) {
Chris@35: 
Chris@35: 		snapped = i->frame;
Chris@35: 		found = true;
Chris@35: 		break;
Chris@35: 
Chris@35: 	    } else if (j->frame >= frame) {
Chris@35: 
Chris@35: 		if (j->frame - frame < frame - i->frame) {
Chris@35: 		    snapped = j->frame;
Chris@35: 		} else {
Chris@35: 		    snapped = i->frame;
Chris@35: 		}
Chris@35: 		found = true;
Chris@35: 		break;
Chris@35: 	    }
Chris@35: 	}
Chris@35:     }
Chris@35: 
Chris@35:     frame = snapped;
Chris@35:     return found;
Chris@35: }
Chris@35: 
Chris@35: int
Chris@44: TextLayer::getYForHeight(View *v, float height) const
Chris@35: {
Chris@44:     int h = v->height();
Chris@35:     return h - int(height * h);
Chris@35: }
Chris@35: 
Chris@35: float
Chris@44: TextLayer::getHeightForY(View *v, int y) const
Chris@35: {
Chris@44:     int h = v->height();
Chris@35:     return float(h - y) / h;
Chris@35: }
Chris@35: 
Chris@35: void
Chris@44: TextLayer::paint(View *v, QPainter &paint, QRect rect) const
Chris@35: {
Chris@35:     if (!m_model || !m_model->isOK()) return;
Chris@35: 
Chris@35:     int sampleRate = m_model->getSampleRate();
Chris@35:     if (!sampleRate) return;
Chris@35: 
Chris@35: //    Profiler profiler("TextLayer::paint", true);
Chris@35: 
Chris@35:     int x0 = rect.left(), x1 = rect.right();
Chris@44:     long frame0 = v->getFrameForX(x0);
Chris@44:     long frame1 = v->getFrameForX(x1);
Chris@35: 
Chris@35:     TextModel::PointList points(m_model->getPoints(frame0, frame1));
Chris@35:     if (points.empty()) return;
Chris@35: 
Chris@287:     QColor brushColour(getBaseQColor());
Chris@35: 
Chris@44:     int h, s, val;
Chris@44:     brushColour.getHsv(&h, &s, &val);
Chris@36:     brushColour.setHsv(h, s, 255, 100);
Chris@36: 
Chris@36:     QColor penColour;
Chris@287:     penColour = v->getForeground();
Chris@35: 
Chris@587: //    SVDEBUG << "TextLayer::paint: resolution is "
Chris@585: //	      << m_model->getResolution() << " frames" << endl;
Chris@35: 
Chris@35:     QPoint localPos;
Chris@552:     TextModel::Point illuminatePoint(0);
Chris@552:     bool shouldIlluminate;
Chris@35: 
Chris@44:     if (v->shouldIlluminateLocalFeatures(this, localPos)) {
Chris@552:         shouldIlluminate = getPointToDrag(v, localPos.x(), localPos.y(),
Chris@552:                                           illuminatePoint);
Chris@35:     }
Chris@35: 
Chris@35:     int boxMaxWidth = 150;
Chris@35:     int boxMaxHeight = 200;
Chris@35: 
Chris@35:     paint.save();
Chris@44:     paint.setClipRect(rect.x(), 0, rect.width() + boxMaxWidth, v->height());
Chris@35:     
Chris@35:     for (TextModel::PointList::const_iterator i = points.begin();
Chris@35: 	 i != points.end(); ++i) {
Chris@35: 
Chris@35: 	const TextModel::Point &p(*i);
Chris@35: 
Chris@44: 	int x = v->getXForFrame(p.frame);
Chris@44: 	int y = getYForHeight(v, p.height);
Chris@35: 
Chris@552:         if (!shouldIlluminate ||
Chris@552:             // "illuminatePoint != p"
Chris@552:             TextModel::Point::Comparator()(illuminatePoint, p) ||
Chris@552:             TextModel::Point::Comparator()(p, illuminatePoint)) {
Chris@552: 	    paint.setPen(penColour);
Chris@552: 	    paint.setBrush(brushColour);
Chris@552:         } else {
Chris@36: 	    paint.setBrush(penColour);
Chris@287:             paint.setPen(v->getBackground());
Chris@35: 	}
Chris@35: 
Chris@35: 	QString label = p.label;
Chris@35: 	if (label == "") {
Chris@35: 	    label = tr("<no text>");
Chris@35: 	}
Chris@35: 
Chris@35: 	QRect boxRect = paint.fontMetrics().boundingRect
Chris@35: 	    (QRect(0, 0, boxMaxWidth, boxMaxHeight),
Chris@35: 	     Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, label);
Chris@35: 
Chris@35: 	QRect textRect = QRect(3, 2, boxRect.width(), boxRect.height());
Chris@35: 	boxRect = QRect(0, 0, boxRect.width() + 6, boxRect.height() + 2);
Chris@35: 
Chris@44: 	if (y + boxRect.height() > v->height()) {
Chris@44: 	    if (boxRect.height() > v->height()) y = 0;
Chris@44: 	    else y = v->height() - boxRect.height() - 1;
Chris@35: 	}
Chris@35: 
Chris@35: 	boxRect = QRect(x, y, boxRect.width(), boxRect.height());
Chris@35: 	textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height());
Chris@35: 
Chris@35: //	boxRect = QRect(x, y, boxRect.width(), boxRect.height());
Chris@35: //	textRect = QRect(x + 3, y + 2, textRect.width(), textRect.height());
Chris@35: 
Chris@35: 	paint.setRenderHint(QPainter::Antialiasing, false);
Chris@35: 	paint.drawRect(boxRect);
Chris@35: 
Chris@35: 	paint.setRenderHint(QPainter::Antialiasing, true);
Chris@35: 	paint.drawText(textRect,
Chris@35: 		       Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap,
Chris@35: 		       label);
Chris@35: 
Chris@35: ///	if (p.label != "") {
Chris@35: ///	    paint.drawText(x + 5, y - paint.fontMetrics().height() + paint.fontMetrics().ascent(), p.label);
Chris@35: ///	}
Chris@35:     }
Chris@35: 
Chris@35:     paint.restore();
Chris@35: 
Chris@35:     // looks like save/restore doesn't deal with this:
Chris@35:     paint.setRenderHint(QPainter::Antialiasing, false);
Chris@35: }
Chris@35: 
Chris@35: void
Chris@44: TextLayer::drawStart(View *v, QMouseEvent *e)
Chris@35: {
Chris@587: //    SVDEBUG << "TextLayer::drawStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@35: 
Chris@35:     if (!m_model) {
Chris@587: 	SVDEBUG << "TextLayer::drawStart: no model" << endl;
Chris@35: 	return;
Chris@35:     }
Chris@35: 
Chris@44:     long frame = v->getFrameForX(e->x());
Chris@35:     if (frame < 0) frame = 0;
Chris@35:     frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@35: 
Chris@44:     float height = getHeightForY(v, e->y());
Chris@35: 
Chris@35:     m_editingPoint = TextModel::Point(frame, height, "");
Chris@35:     m_originalPoint = m_editingPoint;
Chris@35: 
Chris@376:     if (m_editingCommand) finish(m_editingCommand);
Chris@35:     m_editingCommand = new TextModel::EditCommand(m_model, "Add Label");
Chris@35:     m_editingCommand->addPoint(m_editingPoint);
Chris@35: 
Chris@35:     m_editing = true;
Chris@35: }
Chris@35: 
Chris@35: void
Chris@44: TextLayer::drawDrag(View *v, QMouseEvent *e)
Chris@35: {
Chris@587: //    SVDEBUG << "TextLayer::drawDrag(" << e->x() << "," << e->y() << ")" << endl;
Chris@35: 
Chris@35:     if (!m_model || !m_editing) return;
Chris@35: 
Chris@44:     long frame = v->getFrameForX(e->x());
Chris@35:     if (frame < 0) frame = 0;
Chris@35:     frame = frame / m_model->getResolution() * m_model->getResolution();
Chris@35: 
Chris@44:     float height = getHeightForY(v, e->y());
Chris@35: 
Chris@35:     m_editingCommand->deletePoint(m_editingPoint);
Chris@35:     m_editingPoint.frame = frame;
Chris@35:     m_editingPoint.height = height;
Chris@35:     m_editingCommand->addPoint(m_editingPoint);
Chris@35: }
Chris@35: 
Chris@35: void
Chris@248: TextLayer::drawEnd(View *v, QMouseEvent *)
Chris@35: {
Chris@587: //    SVDEBUG << "TextLayer::drawEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@35:     if (!m_model || !m_editing) return;
Chris@36: 
Chris@36:     bool ok = false;
Chris@44:     QString label = QInputDialog::getText(v, tr("Enter label"),
Chris@36: 					  tr("Please enter a new label:"),
Chris@36: 					  QLineEdit::Normal, "", &ok);
Chris@36: 
Chris@36:     if (ok) {
Chris@36: 	TextModel::RelabelCommand *command =
Chris@36: 	    new TextModel::RelabelCommand(m_model, m_editingPoint, label);
Chris@36: 	m_editingCommand->addCommand(command);
Chris@307:     } else {
Chris@307:         m_editingCommand->deletePoint(m_editingPoint);
Chris@36:     }
Chris@36: 
Chris@376:     finish(m_editingCommand);
Chris@35:     m_editingCommand = 0;
Chris@35:     m_editing = false;
Chris@35: }
Chris@35: 
Chris@35: void
Chris@335: TextLayer::eraseStart(View *v, QMouseEvent *e)
Chris@335: {
Chris@335:     if (!m_model) return;
Chris@335: 
Chris@552:     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) return;
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: TextLayer::eraseDrag(View *, QMouseEvent *)
Chris@335: {
Chris@335: }
Chris@335: 
Chris@335: void
Chris@335: TextLayer::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@552:     TextModel::Point p(0);
Chris@552:     if (!getPointToDrag(v, e->x(), e->y(), p)) return;
Chris@552:     if (p.frame != m_editingPoint.frame || p.height != m_editingPoint.height) return;
Chris@335: 
Chris@335:     m_editingCommand = new TextModel::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: TextLayer::editStart(View *v, QMouseEvent *e)
Chris@35: {
Chris@587: //    SVDEBUG << "TextLayer::editStart(" << e->x() << "," << e->y() << ")" << endl;
Chris@35: 
Chris@35:     if (!m_model) return;
Chris@35: 
Chris@552:     if (!getPointToDrag(v, e->x(), e->y(), m_editingPoint)) {
Chris@552:         return;
Chris@552:     }
Chris@35: 
Chris@36:     m_editOrigin = e->pos();
Chris@35:     m_originalPoint = m_editingPoint;
Chris@35: 
Chris@35:     if (m_editingCommand) {
Chris@376: 	finish(m_editingCommand);
Chris@35: 	m_editingCommand = 0;
Chris@35:     }
Chris@35: 
Chris@35:     m_editing = true;
Chris@35: }
Chris@35: 
Chris@35: void
Chris@44: TextLayer::editDrag(View *v, QMouseEvent *e)
Chris@35: {
Chris@35:     if (!m_model || !m_editing) return;
Chris@35: 
Chris@44:     long frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x());
Chris@44:     float heightDiff = getHeightForY(v, e->y()) - getHeightForY(v, m_editOrigin.y());
Chris@36: 
Chris@36:     long frame = m_originalPoint.frame + frameDiff;
Chris@36:     float height = m_originalPoint.height + heightDiff;
Chris@36: 
Chris@44: //    long frame = v->getFrameForX(e->x());
Chris@35:     if (frame < 0) frame = 0;
Chris@36:     frame = (frame / m_model->getResolution()) * m_model->getResolution();
Chris@35: 
Chris@44: //    float height = getHeightForY(v, e->y());
Chris@35: 
Chris@35:     if (!m_editingCommand) {
Chris@35: 	m_editingCommand = new TextModel::EditCommand(m_model, tr("Drag Label"));
Chris@35:     }
Chris@35: 
Chris@35:     m_editingCommand->deletePoint(m_editingPoint);
Chris@35:     m_editingPoint.frame = frame;
Chris@35:     m_editingPoint.height = height;
Chris@35:     m_editingCommand->addPoint(m_editingPoint);
Chris@35: }
Chris@35: 
Chris@35: void
Chris@248: TextLayer::editEnd(View *, QMouseEvent *)
Chris@35: {
Chris@587: //    SVDEBUG << "TextLayer::editEnd(" << e->x() << "," << e->y() << ")" << endl;
Chris@35:     if (!m_model || !m_editing) return;
Chris@35: 
Chris@35:     if (m_editingCommand) {
Chris@35: 
Chris@35: 	QString newName = m_editingCommand->getName();
Chris@35: 
Chris@35: 	if (m_editingPoint.frame != m_originalPoint.frame) {
Chris@35: 	    if (m_editingPoint.height != m_originalPoint.height) {
Chris@35: 		newName = tr("Move Label");
Chris@35: 	    } else {
Chris@36: 		newName = tr("Move Label Horizontally");
Chris@35: 	    }
Chris@35: 	} else {
Chris@36: 	    newName = tr("Move Label Vertically");
Chris@35: 	}
Chris@35: 
Chris@35: 	m_editingCommand->setName(newName);
Chris@376: 	finish(m_editingCommand);
Chris@35:     }
Chris@35:     
Chris@35:     m_editingCommand = 0;
Chris@35:     m_editing = false;
Chris@35: }
Chris@35: 
Chris@255: bool
Chris@44: TextLayer::editOpen(View *v, QMouseEvent *e)
Chris@36: {
Chris@255:     if (!m_model) return false;
Chris@36: 
Chris@552:     TextModel::Point text(0);
Chris@552:     if (!getPointToDrag(v, e->x(), e->y(), text)) return false;
Chris@36: 
Chris@552:     QString label = text.label;
Chris@36: 
Chris@36:     bool ok = false;
Chris@44:     label = QInputDialog::getText(v, tr("Enter label"),
Chris@36: 				  tr("Please enter a new label:"),
Chris@36: 				  QLineEdit::Normal, label, &ok);
Chris@552:     if (ok && label != text.label) {
Chris@36: 	TextModel::RelabelCommand *command =
Chris@552: 	    new TextModel::RelabelCommand(m_model, text, label);
Chris@99: 	CommandHistory::getInstance()->addCommand(command);
Chris@36:     }
Chris@255: 
Chris@255:     return true;
Chris@36: }    
Chris@36: 
Chris@43: void
Chris@805: TextLayer::moveSelection(Selection s, int newStartFrame)
Chris@43: {
Chris@99:     if (!m_model) return;
Chris@99: 
Chris@43:     TextModel::EditCommand *command =
Chris@43: 	new TextModel::EditCommand(m_model, tr("Drag Selection"));
Chris@43: 
Chris@43:     TextModel::PointList points =
Chris@43: 	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@43: 
Chris@43:     for (TextModel::PointList::iterator i = points.begin();
Chris@43: 	 i != points.end(); ++i) {
Chris@43: 
Chris@43: 	if (s.contains(i->frame)) {
Chris@43: 	    TextModel::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: TextLayer::resizeSelection(Selection s, Selection newSize)
Chris@43: {
Chris@99:     if (!m_model) return;
Chris@99: 
Chris@43:     TextModel::EditCommand *command =
Chris@43: 	new TextModel::EditCommand(m_model, tr("Resize Selection"));
Chris@43: 
Chris@43:     TextModel::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 (TextModel::PointList::iterator i = points.begin();
Chris@43: 	 i != points.end(); ++i) {
Chris@43: 
Chris@43: 	if (s.contains(i->frame)) {
Chris@43: 
Chris@43: 	    double target = i->frame;
Chris@43: 	    target = newSize.getStartFrame() + 
Chris@43: 		double(target - s.getStartFrame()) * ratio;
Chris@43: 
Chris@43: 	    TextModel::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@76: void
Chris@76: TextLayer::deleteSelection(Selection s)
Chris@76: {
Chris@99:     if (!m_model) return;
Chris@99: 
Chris@76:     TextModel::EditCommand *command =
Chris@76: 	new TextModel::EditCommand(m_model, tr("Delete Selection"));
Chris@76: 
Chris@76:     TextModel::PointList points =
Chris@76: 	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76: 
Chris@76:     for (TextModel::PointList::iterator i = points.begin();
Chris@76: 	 i != points.end(); ++i) {
Chris@76: 	if (s.contains(i->frame)) command->deletePoint(*i);
Chris@76:     }
Chris@76: 
Chris@376:     finish(command);
Chris@76: }
Chris@76: 
Chris@76: void
Chris@359: TextLayer::copy(View *v, Selection s, Clipboard &to)
Chris@76: {
Chris@99:     if (!m_model) return;
Chris@99: 
Chris@76:     TextModel::PointList points =
Chris@76: 	m_model->getPoints(s.getStartFrame(), s.getEndFrame());
Chris@76: 
Chris@76:     for (TextModel::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->height, i->label);
Chris@360:             point.setReferenceFrame(alignToReference(v, i->frame));
Chris@76:             to.addPoint(point);
Chris@76:         }
Chris@76:     }
Chris@76: }
Chris@76: 
Chris@125: bool
Chris@805: TextLayer::paste(View *v, const Clipboard &from, int /* frameOffset */, bool /* interactive */)
Chris@76: {
Chris@125:     if (!m_model) return false;
Chris@99: 
Chris@76:     const Clipboard::PointList &points = from.getPoints();
Chris@76: 
Chris@360:     bool realign = false;
Chris@360: 
Chris@360:     if (clipboardHasDifferentAlignment(v, from)) {
Chris@360: 
Chris@360:         QMessageBox::StandardButton button =
Chris@360:             QMessageBox::question(v, tr("Re-align pasted items?"),
Chris@360:                                   tr("The items 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@360: 
Chris@360:         if (button == QMessageBox::Cancel) {
Chris@360:             return false;
Chris@360:         }
Chris@360: 
Chris@360:         if (button == QMessageBox::Yes) {
Chris@360:             realign = true;
Chris@360:         }
Chris@360:     }
Chris@360: 
Chris@76:     TextModel::EditCommand *command =
Chris@76: 	new TextModel::EditCommand(m_model, tr("Paste"));
Chris@76: 
Chris@125:     float valueMin = 0.0, valueMax = 1.0;
Chris@125:     for (Clipboard::PointList::const_iterator i = points.begin();
Chris@125:          i != points.end(); ++i) {
Chris@125:         if (i->haveValue()) {
Chris@125:             if (i->getValue() < valueMin) valueMin = i->getValue();
Chris@125:             if (i->getValue() > valueMax) valueMax = i->getValue();
Chris@125:         }
Chris@125:     }
Chris@125:     if (valueMax < valueMin + 1.0) valueMax = valueMin + 1.0;
Chris@125: 
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@805:         int frame = 0;
Chris@360:         
Chris@360:         if (!realign) {
Chris@360:             
Chris@360:             frame = i->getFrame();
Chris@360: 
Chris@360:         } else {
Chris@360: 
Chris@360:             if (i->haveReferenceFrame()) {
Chris@360:                 frame = i->getReferenceFrame();
Chris@360:                 frame = alignFromReference(v, frame);
Chris@360:             } else {
Chris@360:                 frame = i->getFrame();
Chris@360:             }
Chris@76:         }
Chris@360: 
Chris@76:         TextModel::Point newPoint(frame);
Chris@125: 
Chris@125:         if (i->haveValue()) {
Chris@125:             newPoint.height = (i->getValue() - valueMin) / (valueMax - valueMin);
Chris@125:         } else {
Chris@125:             newPoint.height = 0.5;
Chris@125:         }
Chris@125: 
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:         } else {
Chris@125:             newPoint.label = tr("New Point");
Chris@125:         }
Chris@76:         
Chris@76:         command->addPoint(newPoint);
Chris@76:     }
Chris@76: 
Chris@376:     finish(command);
Chris@125:     return true;
Chris@76: }
Chris@76: 
Chris@287: int
Chris@287: TextLayer::getDefaultColourHint(bool darkbg, bool &impose)
Chris@287: {
Chris@287:     impose = false;
Chris@287:     return ColourDatabase::getInstance()->getColourIndex
Chris@287:         (QString(darkbg ? "Bright Orange" : "Orange"));
Chris@287: }
Chris@287: 
Chris@316: void
Chris@316: TextLayer::toXml(QTextStream &stream,
Chris@316:                  QString indent, QString extraAttributes) const
Chris@35: {
Chris@316:     SingleColourLayer::toXml(stream, indent, extraAttributes);
Chris@35: }
Chris@35: 
Chris@35: void
Chris@35: TextLayer::setProperties(const QXmlAttributes &attributes)
Chris@35: {
Chris@287:     SingleColourLayer::setProperties(attributes);
Chris@35: }
Chris@35: