| Chris@303 | 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */ | 
| Chris@303 | 2 | 
| Chris@303 | 3 /* | 
| Chris@303 | 4     Sonic Visualiser | 
| Chris@303 | 5     An audio file viewer and annotation editor. | 
| Chris@303 | 6     Centre for Digital Music, Queen Mary, University of London. | 
| Chris@303 | 7     This file copyright 2006 Chris Cannam. | 
| Chris@303 | 8 | 
| Chris@303 | 9     This program is free software; you can redistribute it and/or | 
| Chris@303 | 10     modify it under the terms of the GNU General Public License as | 
| Chris@303 | 11     published by the Free Software Foundation; either version 2 of the | 
| Chris@303 | 12     License, or (at your option) any later version.  See the file | 
| Chris@303 | 13     COPYING included with this distribution for more information. | 
| Chris@303 | 14 */ | 
| Chris@303 | 15 | 
| Chris@303 | 16 #include "ImageLayer.h" | 
| Chris@303 | 17 | 
| Chris@303 | 18 #include "data/model/Model.h" | 
| Chris@303 | 19 #include "base/RealTime.h" | 
| Chris@303 | 20 #include "base/Profiler.h" | 
| Chris@303 | 21 #include "view/View.h" | 
| Chris@303 | 22 | 
| Chris@303 | 23 #include "data/model/ImageModel.h" | 
| Chris@303 | 24 | 
| Chris@303 | 25 #include "widgets/ImageDialog.h" | 
| Chris@303 | 26 | 
| Chris@303 | 27 #include <QPainter> | 
| Chris@303 | 28 #include <QMouseEvent> | 
| Chris@303 | 29 #include <QInputDialog> | 
| Chris@303 | 30 | 
| Chris@303 | 31 #include <iostream> | 
| Chris@303 | 32 #include <cmath> | 
| Chris@303 | 33 | 
| Chris@303 | 34 ImageLayer::ImageMap | 
| Chris@303 | 35 ImageLayer::m_images; | 
| Chris@303 | 36 | 
| Chris@303 | 37 ImageLayer::ImageLayer() : | 
| Chris@303 | 38     Layer(), | 
| Chris@303 | 39     m_model(0), | 
| Chris@303 | 40     m_editing(false), | 
| Chris@303 | 41     m_originalPoint(0, "", ""), | 
| Chris@303 | 42     m_editingPoint(0, "", ""), | 
| Chris@303 | 43     m_editingCommand(0) | 
| Chris@303 | 44 { | 
| Chris@303 | 45 | 
| Chris@303 | 46 } | 
| Chris@303 | 47 | 
| Chris@303 | 48 void | 
| Chris@303 | 49 ImageLayer::setModel(ImageModel *model) | 
| Chris@303 | 50 { | 
| Chris@303 | 51     if (m_model == model) return; | 
| Chris@303 | 52     m_model = model; | 
| Chris@303 | 53 | 
| Chris@303 | 54     connect(m_model, SIGNAL(modelChanged()), this, SIGNAL(modelChanged())); | 
| Chris@303 | 55     connect(m_model, SIGNAL(modelChanged(size_t, size_t)), | 
| Chris@303 | 56 	    this, SIGNAL(modelChanged(size_t, size_t))); | 
| Chris@303 | 57 | 
| Chris@303 | 58     connect(m_model, SIGNAL(completionChanged()), | 
| Chris@303 | 59 	    this, SIGNAL(modelCompletionChanged())); | 
| Chris@303 | 60 | 
| Chris@303 | 61 //    std::cerr << "ImageLayer::setModel(" << model << ")" << std::endl; | 
| Chris@303 | 62 | 
| Chris@303 | 63     emit modelReplaced(); | 
| Chris@303 | 64 } | 
| Chris@303 | 65 | 
| Chris@303 | 66 Layer::PropertyList | 
| Chris@303 | 67 ImageLayer::getProperties() const | 
| Chris@303 | 68 { | 
| Chris@303 | 69     return Layer::getProperties(); | 
| Chris@303 | 70 } | 
| Chris@303 | 71 | 
| Chris@303 | 72 QString | 
| Chris@303 | 73 ImageLayer::getPropertyLabel(const PropertyName &name) const | 
| Chris@303 | 74 { | 
| Chris@303 | 75     return ""; | 
| Chris@303 | 76 } | 
| Chris@303 | 77 | 
| Chris@303 | 78 Layer::PropertyType | 
| Chris@303 | 79 ImageLayer::getPropertyType(const PropertyName &name) const | 
| Chris@303 | 80 { | 
| Chris@303 | 81     return Layer::getPropertyType(name); | 
| Chris@303 | 82 } | 
| Chris@303 | 83 | 
| Chris@303 | 84 int | 
| Chris@303 | 85 ImageLayer::getPropertyRangeAndValue(const PropertyName &name, | 
| Chris@303 | 86 				    int *min, int *max, int *deflt) const | 
| Chris@303 | 87 { | 
| Chris@303 | 88     return Layer::getPropertyRangeAndValue(name, min, max, deflt); | 
| Chris@303 | 89 } | 
| Chris@303 | 90 | 
| Chris@303 | 91 QString | 
| Chris@303 | 92 ImageLayer::getPropertyValueLabel(const PropertyName &name, | 
| Chris@303 | 93 				 int value) const | 
| Chris@303 | 94 { | 
| Chris@303 | 95     return Layer::getPropertyValueLabel(name, value); | 
| Chris@303 | 96 } | 
| Chris@303 | 97 | 
| Chris@303 | 98 void | 
| Chris@303 | 99 ImageLayer::setProperty(const PropertyName &name, int value) | 
| Chris@303 | 100 { | 
| Chris@303 | 101     Layer::setProperty(name, value); | 
| Chris@303 | 102 } | 
| Chris@303 | 103 | 
| Chris@303 | 104 bool | 
| Chris@303 | 105 ImageLayer::getValueExtents(float &, float &, bool &, QString &) const | 
| Chris@303 | 106 { | 
| Chris@303 | 107     return false; | 
| Chris@303 | 108 } | 
| Chris@303 | 109 | 
| Chris@303 | 110 bool | 
| Chris@303 | 111 ImageLayer::isLayerScrollable(const View *v) const | 
| Chris@303 | 112 { | 
| Chris@304 | 113     return true; | 
| Chris@303 | 114 } | 
| Chris@303 | 115 | 
| Chris@303 | 116 | 
| Chris@303 | 117 ImageModel::PointList | 
| Chris@303 | 118 ImageLayer::getLocalPoints(View *v, int x, int y) const | 
| Chris@303 | 119 { | 
| Chris@303 | 120     if (!m_model) return ImageModel::PointList(); | 
| Chris@303 | 121 | 
| Chris@304 | 122 //    std::cerr << "ImageLayer::getLocalPoints(" << x << "," << y << "):"; | 
| Chris@304 | 123     const ImageModel::PointList &points(m_model->getPoints()); | 
| Chris@303 | 124 | 
| Chris@303 | 125     ImageModel::PointList rv; | 
| Chris@303 | 126 | 
| Chris@304 | 127     for (ImageModel::PointList::const_iterator i = points.begin(); | 
| Chris@304 | 128 	 i != points.end(); ) { | 
| Chris@303 | 129 | 
| Chris@303 | 130 	const ImageModel::Point &p(*i); | 
| Chris@304 | 131 	int px = v->getXForFrame(p.frame); | 
| Chris@304 | 132         if (px > x) break; | 
| Chris@303 | 133 | 
| Chris@304 | 134         ++i; | 
| Chris@304 | 135         if (i != points.end()) { | 
| Chris@304 | 136             int nx = v->getXForFrame((*i).frame); | 
| Chris@304 | 137             if (nx < x) { | 
| Chris@304 | 138                 // as we aim not to overlap the images, if the following | 
| Chris@304 | 139                 // image begins to the left of a point then the current | 
| Chris@304 | 140                 // one may be assumed to end to the left of it as well. | 
| Chris@304 | 141                 continue; | 
| Chris@304 | 142             } | 
| Chris@304 | 143         } | 
| Chris@303 | 144 | 
| Chris@304 | 145         // this image is a candidate, test it properly | 
| Chris@304 | 146 | 
| Chris@304 | 147         int width = 32; | 
| Chris@304 | 148         if (m_scaled[v].find(p.image) != m_scaled[v].end()) { | 
| Chris@304 | 149             width = m_scaled[v][p.image].width(); | 
| Chris@304 | 150             std::cerr << "scaled width = " << width << std::endl; | 
| Chris@304 | 151         } | 
| Chris@304 | 152 | 
| Chris@304 | 153         if (x >= px && x < px + width) { | 
| Chris@303 | 154             rv.insert(p); | 
| Chris@303 | 155         } | 
| Chris@303 | 156     } | 
| Chris@303 | 157 | 
| Chris@304 | 158 //    std::cerr << rv.size() << " point(s)" << std::endl; | 
| Chris@303 | 159 | 
| Chris@303 | 160     return rv; | 
| Chris@303 | 161 } | 
| Chris@303 | 162 | 
| Chris@303 | 163 QString | 
| Chris@303 | 164 ImageLayer::getFeatureDescription(View *v, QPoint &pos) const | 
| Chris@303 | 165 { | 
| Chris@303 | 166     int x = pos.x(); | 
| Chris@303 | 167 | 
| Chris@303 | 168     if (!m_model || !m_model->getSampleRate()) return ""; | 
| Chris@303 | 169 | 
| Chris@303 | 170     ImageModel::PointList points = getLocalPoints(v, x, pos.y()); | 
| Chris@303 | 171 | 
| Chris@303 | 172     if (points.empty()) { | 
| Chris@303 | 173 	if (!m_model->isReady()) { | 
| Chris@303 | 174 	    return tr("In progress"); | 
| Chris@303 | 175 	} else { | 
| Chris@303 | 176 	    return ""; | 
| Chris@303 | 177 	} | 
| Chris@303 | 178     } | 
| Chris@303 | 179 | 
| Chris@303 | 180     long useFrame = points.begin()->frame; | 
| Chris@303 | 181 | 
| Chris@303 | 182     RealTime rt = RealTime::frame2RealTime(useFrame, m_model->getSampleRate()); | 
| Chris@303 | 183 | 
| Chris@303 | 184     QString text; | 
| Chris@303 | 185 /* | 
| Chris@303 | 186     if (points.begin()->label == "") { | 
| Chris@303 | 187 	text = QString(tr("Time:\t%1\nHeight:\t%2\nLabel:\t%3")) | 
| Chris@303 | 188 	    .arg(rt.toText(true).c_str()) | 
| Chris@303 | 189 	    .arg(points.begin()->height) | 
| Chris@303 | 190 	    .arg(points.begin()->label); | 
| Chris@303 | 191     } | 
| Chris@303 | 192 | 
| Chris@303 | 193     pos = QPoint(v->getXForFrame(useFrame), | 
| Chris@303 | 194 		 getYForHeight(v, points.begin()->height)); | 
| Chris@303 | 195 */ | 
| Chris@303 | 196     return text; | 
| Chris@303 | 197 } | 
| Chris@303 | 198 | 
| Chris@303 | 199 | 
| Chris@303 | 200 //!!! too much overlap with TimeValueLayer/TimeInstantLayer/TextLayer | 
| Chris@303 | 201 | 
| Chris@303 | 202 bool | 
| Chris@303 | 203 ImageLayer::snapToFeatureFrame(View *v, int &frame, | 
| Chris@303 | 204 			      size_t &resolution, | 
| Chris@303 | 205 			      SnapType snap) const | 
| Chris@303 | 206 { | 
| Chris@303 | 207     if (!m_model) { | 
| Chris@303 | 208 	return Layer::snapToFeatureFrame(v, frame, resolution, snap); | 
| Chris@303 | 209     } | 
| Chris@303 | 210 | 
| Chris@303 | 211     resolution = m_model->getResolution(); | 
| Chris@303 | 212     ImageModel::PointList points; | 
| Chris@303 | 213 | 
| Chris@303 | 214     if (snap == SnapNeighbouring) { | 
| Chris@303 | 215 | 
| Chris@303 | 216 	points = getLocalPoints(v, v->getXForFrame(frame), -1); | 
| Chris@303 | 217 	if (points.empty()) return false; | 
| Chris@303 | 218 	frame = points.begin()->frame; | 
| Chris@303 | 219 	return true; | 
| Chris@303 | 220     } | 
| Chris@303 | 221 | 
| Chris@303 | 222     points = m_model->getPoints(frame, frame); | 
| Chris@303 | 223     int snapped = frame; | 
| Chris@303 | 224     bool found = false; | 
| Chris@303 | 225 | 
| Chris@303 | 226     for (ImageModel::PointList::const_iterator i = points.begin(); | 
| Chris@303 | 227 	 i != points.end(); ++i) { | 
| Chris@303 | 228 | 
| Chris@303 | 229 	if (snap == SnapRight) { | 
| Chris@303 | 230 | 
| Chris@303 | 231 	    if (i->frame > frame) { | 
| Chris@303 | 232 		snapped = i->frame; | 
| Chris@303 | 233 		found = true; | 
| Chris@303 | 234 		break; | 
| Chris@303 | 235 	    } | 
| Chris@303 | 236 | 
| Chris@303 | 237 	} else if (snap == SnapLeft) { | 
| Chris@303 | 238 | 
| Chris@303 | 239 	    if (i->frame <= frame) { | 
| Chris@303 | 240 		snapped = i->frame; | 
| Chris@303 | 241 		found = true; // don't break, as the next may be better | 
| Chris@303 | 242 	    } else { | 
| Chris@303 | 243 		break; | 
| Chris@303 | 244 	    } | 
| Chris@303 | 245 | 
| Chris@303 | 246 	} else { // nearest | 
| Chris@303 | 247 | 
| Chris@303 | 248 	    ImageModel::PointList::const_iterator j = i; | 
| Chris@303 | 249 	    ++j; | 
| Chris@303 | 250 | 
| Chris@303 | 251 	    if (j == points.end()) { | 
| Chris@303 | 252 | 
| Chris@303 | 253 		snapped = i->frame; | 
| Chris@303 | 254 		found = true; | 
| Chris@303 | 255 		break; | 
| Chris@303 | 256 | 
| Chris@303 | 257 	    } else if (j->frame >= frame) { | 
| Chris@303 | 258 | 
| Chris@303 | 259 		if (j->frame - frame < frame - i->frame) { | 
| Chris@303 | 260 		    snapped = j->frame; | 
| Chris@303 | 261 		} else { | 
| Chris@303 | 262 		    snapped = i->frame; | 
| Chris@303 | 263 		} | 
| Chris@303 | 264 		found = true; | 
| Chris@303 | 265 		break; | 
| Chris@303 | 266 	    } | 
| Chris@303 | 267 	} | 
| Chris@303 | 268     } | 
| Chris@303 | 269 | 
| Chris@303 | 270     frame = snapped; | 
| Chris@303 | 271     return found; | 
| Chris@303 | 272 } | 
| Chris@303 | 273 | 
| Chris@303 | 274 void | 
| Chris@303 | 275 ImageLayer::paint(View *v, QPainter &paint, QRect rect) const | 
| Chris@303 | 276 { | 
| Chris@303 | 277     if (!m_model || !m_model->isOK()) return; | 
| Chris@303 | 278 | 
| Chris@303 | 279     int sampleRate = m_model->getSampleRate(); | 
| Chris@303 | 280     if (!sampleRate) return; | 
| Chris@303 | 281 | 
| Chris@303 | 282 //    Profiler profiler("ImageLayer::paint", true); | 
| Chris@303 | 283 | 
| Chris@304 | 284 //    int x0 = rect.left(), x1 = rect.right(); | 
| Chris@304 | 285     int x0 = 0, x1 = v->width(); | 
| Chris@304 | 286 | 
| Chris@303 | 287     long frame0 = v->getFrameForX(x0); | 
| Chris@303 | 288     long frame1 = v->getFrameForX(x1); | 
| Chris@303 | 289 | 
| Chris@303 | 290     ImageModel::PointList points(m_model->getPoints(frame0, frame1)); | 
| Chris@303 | 291     if (points.empty()) return; | 
| Chris@303 | 292 | 
| Chris@304 | 293     paint.save(); | 
| Chris@304 | 294     paint.setClipRect(rect.x(), 0, rect.width(), v->height()); | 
| Chris@304 | 295 | 
| Chris@303 | 296     QColor penColour; | 
| Chris@303 | 297     penColour = v->getForeground(); | 
| Chris@303 | 298 | 
| Chris@304 | 299     QColor brushColour; | 
| Chris@304 | 300     brushColour = v->getBackground(); | 
| Chris@303 | 301 | 
| Chris@304 | 302     int h, s, val; | 
| Chris@304 | 303     brushColour.getHsv(&h, &s, &val); | 
| Chris@304 | 304     brushColour.setHsv(h, s, 255, 240); | 
| Chris@303 | 305 | 
| Chris@304 | 306     paint.setPen(penColour); | 
| Chris@304 | 307     paint.setBrush(brushColour); | 
| Chris@304 | 308     paint.setRenderHint(QPainter::Antialiasing, true); | 
| Chris@303 | 309 | 
| Chris@303 | 310     for (ImageModel::PointList::const_iterator i = points.begin(); | 
| Chris@303 | 311 	 i != points.end(); ++i) { | 
| Chris@303 | 312 | 
| Chris@303 | 313 	const ImageModel::Point &p(*i); | 
| Chris@303 | 314 | 
| Chris@303 | 315 	int x = v->getXForFrame(p.frame); | 
| Chris@303 | 316 | 
| Chris@303 | 317         int nx = x + 2000; | 
| Chris@303 | 318         ImageModel::PointList::const_iterator j = i; | 
| Chris@303 | 319         ++j; | 
| Chris@303 | 320         if (j != points.end()) { | 
| Chris@303 | 321             int jx = v->getXForFrame(j->frame); | 
| Chris@303 | 322             if (jx < nx) nx = jx; | 
| Chris@303 | 323         } | 
| Chris@303 | 324 | 
| Chris@304 | 325         drawImage(v, paint, p, x, nx); | 
| Chris@304 | 326     } | 
| Chris@303 | 327 | 
| Chris@304 | 328     paint.setRenderHint(QPainter::Antialiasing, false); | 
| Chris@304 | 329     paint.restore(); | 
| Chris@304 | 330 } | 
| Chris@303 | 331 | 
| Chris@304 | 332 void | 
| Chris@304 | 333 ImageLayer::drawImage(View *v, QPainter &paint, const ImageModel::Point &p, | 
| Chris@304 | 334                       int x, int nx) const | 
| Chris@304 | 335 { | 
| Chris@304 | 336     QString label = p.label; | 
| Chris@304 | 337     QString imageName = p.image; | 
| Chris@304 | 338 | 
| Chris@304 | 339     QImage image; | 
| Chris@304 | 340     QString additionalText; | 
| Chris@304 | 341 | 
| Chris@304 | 342     QSize imageSize; | 
| Chris@304 | 343     if (!getImageOriginalSize(imageName, imageSize)) { | 
| Chris@304 | 344         image = QImage(":icons/emptypage.png"); | 
| Chris@304 | 345         imageSize = image.size(); | 
| Chris@304 | 346         additionalText = imageName; | 
| Chris@304 | 347     } | 
| Chris@304 | 348 | 
| Chris@304 | 349     int topMargin = 10; | 
| Chris@304 | 350     int bottomMargin = 10; | 
| Chris@304 | 351     int spacing = 5; | 
| Chris@304 | 352 | 
| Chris@304 | 353     if (v->height() < 100) { | 
| Chris@304 | 354         topMargin = 5; | 
| Chris@304 | 355         bottomMargin = 5; | 
| Chris@304 | 356     } | 
| Chris@304 | 357 | 
| Chris@304 | 358     int maxBoxHeight = v->height() - topMargin - bottomMargin; | 
| Chris@304 | 359 | 
| Chris@304 | 360     int availableWidth = nx - x - 3; | 
| Chris@304 | 361     if (availableWidth < 20) availableWidth = 20; | 
| Chris@304 | 362 | 
| Chris@304 | 363     QRect labelRect; | 
| Chris@304 | 364 | 
| Chris@304 | 365     if (label != "") { | 
| Chris@304 | 366 | 
| Chris@304 | 367         int likelyHeight = v->height() / 4; | 
| Chris@304 | 368 | 
| Chris@304 | 369         int likelyWidth = // available height times image aspect | 
| Chris@304 | 370             ((maxBoxHeight - likelyHeight) * imageSize.width()) | 
| Chris@304 | 371             / imageSize.height(); | 
| Chris@304 | 372 | 
| Chris@304 | 373         if (likelyWidth > imageSize.width()) { | 
| Chris@304 | 374             likelyWidth = imageSize.width(); | 
| Chris@303 | 375         } | 
| Chris@303 | 376 | 
| Chris@304 | 377         if (likelyWidth > availableWidth) { | 
| Chris@304 | 378             likelyWidth = availableWidth; | 
| Chris@303 | 379         } | 
| Chris@303 | 380 | 
| Chris@304 | 381         int singleWidth = paint.fontMetrics().width(label); | 
| Chris@304 | 382         if (singleWidth < availableWidth && singleWidth < likelyWidth * 2) { | 
| Chris@304 | 383             likelyWidth = singleWidth + 4; | 
| Chris@303 | 384         } | 
| Chris@303 | 385 | 
| Chris@304 | 386         labelRect = paint.fontMetrics().boundingRect | 
| Chris@304 | 387             (QRect(0, 0, likelyWidth, likelyHeight), | 
| Chris@304 | 388              Qt::AlignCenter | Qt::TextWordWrap, label); | 
| Chris@303 | 389 | 
| Chris@304 | 390         labelRect.setWidth(labelRect.width() + 6); | 
| Chris@303 | 391     } | 
| Chris@303 | 392 | 
| Chris@304 | 393     if (image.isNull()) { | 
| Chris@304 | 394         image = getImage(v, imageName, | 
| Chris@304 | 395                          QSize(availableWidth, | 
| Chris@304 | 396                                maxBoxHeight - labelRect.height())); | 
| Chris@304 | 397     } | 
| Chris@304 | 398 | 
| Chris@304 | 399     int boxWidth = image.width(); | 
| Chris@304 | 400     if (boxWidth < labelRect.width()) { | 
| Chris@304 | 401         boxWidth = labelRect.width(); | 
| Chris@304 | 402     } | 
| Chris@304 | 403 | 
| Chris@304 | 404     int boxHeight = image.height(); | 
| Chris@304 | 405     if (label != "") { | 
| Chris@304 | 406         boxHeight += labelRect.height() + spacing; | 
| Chris@304 | 407     } | 
| Chris@304 | 408 | 
| Chris@304 | 409     int division = image.height(); | 
| Chris@304 | 410 | 
| Chris@304 | 411     if (additionalText != "") { | 
| Chris@304 | 412 | 
| Chris@304 | 413         paint.save(); | 
| Chris@304 | 414 | 
| Chris@304 | 415         QFont font(paint.font()); | 
| Chris@304 | 416         font.setItalic(true); | 
| Chris@304 | 417         paint.setFont(font); | 
| Chris@304 | 418 | 
| Chris@304 | 419         int tw = paint.fontMetrics().width(additionalText); | 
| Chris@304 | 420         if (tw > availableWidth) { | 
| Chris@304 | 421             tw = availableWidth; | 
| Chris@304 | 422         } | 
| Chris@304 | 423         if (boxWidth < tw) { | 
| Chris@304 | 424             boxWidth = tw; | 
| Chris@304 | 425         } | 
| Chris@304 | 426         boxHeight += paint.fontMetrics().height(); | 
| Chris@304 | 427         division += paint.fontMetrics().height(); | 
| Chris@304 | 428     } | 
| Chris@304 | 429 | 
| Chris@304 | 430     bottomMargin = v->height() - topMargin - boxHeight; | 
| Chris@304 | 431     if (bottomMargin > topMargin + v->height()/7) { | 
| Chris@304 | 432         topMargin += v->height()/8; | 
| Chris@304 | 433         bottomMargin -= v->height()/8; | 
| Chris@304 | 434     } | 
| Chris@304 | 435 | 
| Chris@304 | 436     paint.drawRect(x - 1, | 
| Chris@304 | 437                    topMargin - 1, | 
| Chris@304 | 438                    boxWidth + 2, | 
| Chris@304 | 439                    boxHeight + 2); | 
| Chris@304 | 440 | 
| Chris@304 | 441     int imageY; | 
| Chris@304 | 442     if (label != "") { | 
| Chris@304 | 443         imageY = topMargin + labelRect.height() + spacing; | 
| Chris@304 | 444     } else { | 
| Chris@304 | 445         imageY = topMargin; | 
| Chris@304 | 446     } | 
| Chris@304 | 447 | 
| Chris@304 | 448     paint.drawImage(x + (boxWidth - image.width())/2, | 
| Chris@304 | 449                     imageY, | 
| Chris@304 | 450                     image); | 
| Chris@304 | 451 | 
| Chris@304 | 452     if (additionalText != "") { | 
| Chris@304 | 453         paint.drawText(x, | 
| Chris@304 | 454                        imageY + image.height() + paint.fontMetrics().ascent(), | 
| Chris@304 | 455                        additionalText); | 
| Chris@304 | 456         paint.restore(); | 
| Chris@304 | 457     } | 
| Chris@304 | 458 | 
| Chris@304 | 459     if (label != "") { | 
| Chris@304 | 460         paint.drawLine(x, | 
| Chris@304 | 461                        topMargin + labelRect.height() + spacing, | 
| Chris@304 | 462                        x + boxWidth, | 
| Chris@304 | 463                        topMargin + labelRect.height() + spacing); | 
| Chris@304 | 464 | 
| Chris@304 | 465         paint.drawText(QRect(x, | 
| Chris@304 | 466                              topMargin, | 
| Chris@304 | 467                              boxWidth, | 
| Chris@304 | 468                              labelRect.height()), | 
| Chris@304 | 469                        Qt::AlignCenter | Qt::TextWordWrap, | 
| Chris@304 | 470                        label); | 
| Chris@304 | 471     } | 
| Chris@303 | 472 } | 
| Chris@303 | 473 | 
| Chris@303 | 474 void | 
| Chris@303 | 475 ImageLayer::setLayerDormant(const View *v, bool dormant) | 
| Chris@303 | 476 { | 
| Chris@303 | 477     if (dormant) { | 
| Chris@303 | 478         // Delete the images named in the view's scaled map from the | 
| Chris@303 | 479         // general image map as well.  They can always be re-loaded | 
| Chris@303 | 480         // if it turns out another view still needs them. | 
| Chris@303 | 481         for (ImageMap::iterator i = m_scaled[v].begin(); | 
| Chris@303 | 482              i != m_scaled[v].end(); ++i) { | 
| Chris@303 | 483             m_images.erase(i->first); | 
| Chris@303 | 484         } | 
| Chris@303 | 485         m_scaled.erase(v); | 
| Chris@303 | 486     } | 
| Chris@303 | 487 } | 
| Chris@303 | 488 | 
| Chris@303 | 489 //!!! how to reap no-longer-used images? | 
| Chris@303 | 490 | 
| Chris@304 | 491 bool | 
| Chris@304 | 492 ImageLayer::getImageOriginalSize(QString name, QSize &size) const | 
| Chris@303 | 493 { | 
| Chris@303 | 494     if (m_images.find(name) == m_images.end()) { | 
| Chris@303 | 495         m_images[name] = QImage(name); | 
| Chris@303 | 496     } | 
| Chris@304 | 497     if (m_images[name].isNull()) { | 
| Chris@304 | 498         return false; | 
| Chris@304 | 499     } else { | 
| Chris@304 | 500         size = m_images[name].size(); | 
| Chris@304 | 501         return true; | 
| Chris@304 | 502     } | 
| Chris@303 | 503 } | 
| Chris@303 | 504 | 
| Chris@303 | 505 QImage | 
| Chris@303 | 506 ImageLayer::getImage(View *v, QString name, QSize maxSize) const | 
| Chris@303 | 507 { | 
| Chris@303 | 508     bool need = false; | 
| Chris@303 | 509 | 
| Chris@304 | 510     std::cerr << "ImageLayer::getImage(" << v << ", " << name.toStdString() << ", (" | 
| Chris@304 | 511               << maxSize.width() << "x" << maxSize.height() << "))" << std::endl; | 
| Chris@303 | 512 | 
| Chris@304 | 513     if (!m_scaled[v][name].isNull()  && | 
| Chris@304 | 514         ((m_scaled[v][name].width()  == maxSize.width() && | 
| Chris@304 | 515           m_scaled[v][name].height() <= maxSize.height()) || | 
| Chris@304 | 516          (m_scaled[v][name].width()  <= maxSize.width() && | 
| Chris@304 | 517           m_scaled[v][name].height() == maxSize.height()))) { | 
| Chris@304 | 518         std::cerr << "cache hit" << std::endl; | 
| Chris@303 | 519         return m_scaled[v][name]; | 
| Chris@303 | 520     } | 
| Chris@303 | 521 | 
| Chris@303 | 522     if (m_images.find(name) == m_images.end()) { | 
| Chris@303 | 523         m_images[name] = QImage(name); | 
| Chris@303 | 524     } | 
| Chris@303 | 525 | 
| Chris@303 | 526     if (m_images[name].isNull()) { | 
| Chris@304 | 527         std::cerr << "null image" << std::endl; | 
| Chris@303 | 528         m_scaled[v][name] = QImage(); | 
| Chris@304 | 529     } else if (m_images[name].width() <= maxSize.width() && | 
| Chris@304 | 530                m_images[name].height() <= maxSize.height()) { | 
| Chris@304 | 531         m_scaled[v][name] = m_images[name]; | 
| Chris@303 | 532     } else { | 
| Chris@303 | 533         m_scaled[v][name] = | 
| Chris@303 | 534             m_images[name].scaled(maxSize, | 
| Chris@303 | 535                                   Qt::KeepAspectRatio, | 
| Chris@303 | 536                                   Qt::SmoothTransformation); | 
| Chris@303 | 537     } | 
| Chris@303 | 538 | 
| Chris@303 | 539     return m_scaled[v][name]; | 
| Chris@303 | 540 } | 
| Chris@303 | 541 | 
| Chris@303 | 542 void | 
| Chris@303 | 543 ImageLayer::drawStart(View *v, QMouseEvent *e) | 
| Chris@303 | 544 { | 
| Chris@303 | 545 //    std::cerr << "ImageLayer::drawStart(" << e->x() << "," << e->y() << ")" << std::endl; | 
| Chris@303 | 546 | 
| Chris@303 | 547     if (!m_model) { | 
| Chris@303 | 548 	std::cerr << "ImageLayer::drawStart: no model" << std::endl; | 
| Chris@303 | 549 	return; | 
| Chris@303 | 550     } | 
| Chris@303 | 551 | 
| Chris@303 | 552     long frame = v->getFrameForX(e->x()); | 
| Chris@303 | 553     if (frame < 0) frame = 0; | 
| Chris@303 | 554     frame = frame / m_model->getResolution() * m_model->getResolution(); | 
| Chris@303 | 555 | 
| Chris@303 | 556     m_editingPoint = ImageModel::Point(frame, "", ""); | 
| Chris@303 | 557     m_originalPoint = m_editingPoint; | 
| Chris@303 | 558 | 
| Chris@303 | 559     if (m_editingCommand) m_editingCommand->finish(); | 
| Chris@303 | 560     m_editingCommand = new ImageModel::EditCommand(m_model, "Add Image"); | 
| Chris@303 | 561     m_editingCommand->addPoint(m_editingPoint); | 
| Chris@303 | 562 | 
| Chris@303 | 563     m_editing = true; | 
| Chris@303 | 564 } | 
| Chris@303 | 565 | 
| Chris@303 | 566 void | 
| Chris@303 | 567 ImageLayer::drawDrag(View *v, QMouseEvent *e) | 
| Chris@303 | 568 { | 
| Chris@303 | 569 //    std::cerr << "ImageLayer::drawDrag(" << e->x() << "," << e->y() << ")" << std::endl; | 
| Chris@303 | 570 | 
| Chris@303 | 571     if (!m_model || !m_editing) return; | 
| Chris@303 | 572 | 
| Chris@303 | 573     long frame = v->getFrameForX(e->x()); | 
| Chris@303 | 574     if (frame < 0) frame = 0; | 
| Chris@303 | 575     frame = frame / m_model->getResolution() * m_model->getResolution(); | 
| Chris@303 | 576 | 
| Chris@303 | 577     m_editingCommand->deletePoint(m_editingPoint); | 
| Chris@303 | 578     m_editingPoint.frame = frame; | 
| Chris@303 | 579     m_editingCommand->addPoint(m_editingPoint); | 
| Chris@303 | 580 } | 
| Chris@303 | 581 | 
| Chris@303 | 582 void | 
| Chris@303 | 583 ImageLayer::drawEnd(View *v, QMouseEvent *) | 
| Chris@303 | 584 { | 
| Chris@303 | 585 //    std::cerr << "ImageLayer::drawEnd(" << e->x() << "," << e->y() << ")" << std::endl; | 
| Chris@303 | 586     if (!m_model || !m_editing) return; | 
| Chris@303 | 587 | 
| Chris@303 | 588     bool ok = false; | 
| Chris@303 | 589 | 
| Chris@303 | 590     ImageDialog dialog(tr("Select image"), "", tr("<no label>")); | 
| Chris@303 | 591     if (dialog.exec() == QDialog::Accepted) { | 
| Chris@303 | 592 	ImageModel::ChangeImageCommand *command = | 
| Chris@303 | 593 	    new ImageModel::ChangeImageCommand | 
| Chris@303 | 594             (m_model, m_editingPoint, dialog.getImage(), dialog.getLabel()); | 
| Chris@303 | 595 	m_editingCommand->addCommand(command); | 
| Chris@303 | 596     } | 
| Chris@303 | 597 | 
| Chris@303 | 598     m_editingCommand->finish(); | 
| Chris@303 | 599     m_editingCommand = 0; | 
| Chris@303 | 600     m_editing = false; | 
| Chris@303 | 601 } | 
| Chris@303 | 602 | 
| Chris@303 | 603 void | 
| Chris@303 | 604 ImageLayer::editStart(View *v, QMouseEvent *e) | 
| Chris@303 | 605 { | 
| Chris@303 | 606 //    std::cerr << "ImageLayer::editStart(" << e->x() << "," << e->y() << ")" << std::endl; | 
| Chris@303 | 607 | 
| Chris@303 | 608     if (!m_model) return; | 
| Chris@303 | 609 | 
| Chris@303 | 610     ImageModel::PointList points = getLocalPoints(v, e->x(), e->y()); | 
| Chris@303 | 611     if (points.empty()) return; | 
| Chris@303 | 612 | 
| Chris@303 | 613     m_editOrigin = e->pos(); | 
| Chris@303 | 614     m_editingPoint = *points.begin(); | 
| Chris@303 | 615     m_originalPoint = m_editingPoint; | 
| Chris@303 | 616 | 
| Chris@303 | 617     if (m_editingCommand) { | 
| Chris@303 | 618 	m_editingCommand->finish(); | 
| Chris@303 | 619 	m_editingCommand = 0; | 
| Chris@303 | 620     } | 
| Chris@303 | 621 | 
| Chris@303 | 622     m_editing = true; | 
| Chris@303 | 623 } | 
| Chris@303 | 624 | 
| Chris@303 | 625 void | 
| Chris@303 | 626 ImageLayer::editDrag(View *v, QMouseEvent *e) | 
| Chris@303 | 627 { | 
| Chris@303 | 628     if (!m_model || !m_editing) return; | 
| Chris@303 | 629 | 
| Chris@303 | 630     long frameDiff = v->getFrameForX(e->x()) - v->getFrameForX(m_editOrigin.x()); | 
| Chris@303 | 631     long frame = m_originalPoint.frame + frameDiff; | 
| Chris@303 | 632 | 
| Chris@303 | 633     if (frame < 0) frame = 0; | 
| Chris@303 | 634     frame = (frame / m_model->getResolution()) * m_model->getResolution(); | 
| Chris@303 | 635 | 
| Chris@303 | 636     if (!m_editingCommand) { | 
| Chris@303 | 637 	m_editingCommand = new ImageModel::EditCommand(m_model, tr("Move Image")); | 
| Chris@303 | 638     } | 
| Chris@303 | 639 | 
| Chris@303 | 640     m_editingCommand->deletePoint(m_editingPoint); | 
| Chris@303 | 641     m_editingPoint.frame = frame; | 
| Chris@303 | 642     m_editingCommand->addPoint(m_editingPoint); | 
| Chris@303 | 643 } | 
| Chris@303 | 644 | 
| Chris@303 | 645 void | 
| Chris@303 | 646 ImageLayer::editEnd(View *, QMouseEvent *) | 
| Chris@303 | 647 { | 
| Chris@303 | 648 //    std::cerr << "ImageLayer::editEnd(" << e->x() << "," << e->y() << ")" << std::endl; | 
| Chris@303 | 649     if (!m_model || !m_editing) return; | 
| Chris@303 | 650 | 
| Chris@303 | 651     if (m_editingCommand) { | 
| Chris@303 | 652 	m_editingCommand->finish(); | 
| Chris@303 | 653     } | 
| Chris@303 | 654 | 
| Chris@303 | 655     m_editingCommand = 0; | 
| Chris@303 | 656     m_editing = false; | 
| Chris@303 | 657 } | 
| Chris@303 | 658 | 
| Chris@303 | 659 bool | 
| Chris@303 | 660 ImageLayer::editOpen(View *v, QMouseEvent *e) | 
| Chris@303 | 661 { | 
| Chris@303 | 662     if (!m_model) return false; | 
| Chris@303 | 663 | 
| Chris@303 | 664     ImageModel::PointList points = getLocalPoints(v, e->x(), e->y()); | 
| Chris@303 | 665     if (points.empty()) return false; | 
| Chris@303 | 666 | 
| Chris@303 | 667     QString image = points.begin()->image; | 
| Chris@303 | 668     QString label = points.begin()->label; | 
| Chris@303 | 669 | 
| Chris@303 | 670     ImageDialog dialog(tr("Select image"), | 
| Chris@303 | 671                        image, | 
| Chris@303 | 672                        label); | 
| Chris@303 | 673 | 
| Chris@303 | 674     if (dialog.exec() == QDialog::Accepted) { | 
| Chris@303 | 675 	ImageModel::ChangeImageCommand *command = | 
| Chris@303 | 676 	    new ImageModel::ChangeImageCommand | 
| Chris@303 | 677             (m_model, *points.begin(), dialog.getImage(), dialog.getLabel()); | 
| Chris@303 | 678         CommandHistory::getInstance()->addCommand(command); | 
| Chris@303 | 679     } | 
| Chris@303 | 680 | 
| Chris@303 | 681     return true; | 
| Chris@303 | 682 } | 
| Chris@303 | 683 | 
| Chris@303 | 684 void | 
| Chris@303 | 685 ImageLayer::moveSelection(Selection s, size_t newStartFrame) | 
| Chris@303 | 686 { | 
| Chris@303 | 687     if (!m_model) return; | 
| Chris@303 | 688 | 
| Chris@303 | 689     ImageModel::EditCommand *command = | 
| Chris@303 | 690 	new ImageModel::EditCommand(m_model, tr("Drag Selection")); | 
| Chris@303 | 691 | 
| Chris@303 | 692     ImageModel::PointList points = | 
| Chris@303 | 693 	m_model->getPoints(s.getStartFrame(), s.getEndFrame()); | 
| Chris@303 | 694 | 
| Chris@303 | 695     for (ImageModel::PointList::iterator i = points.begin(); | 
| Chris@303 | 696 	 i != points.end(); ++i) { | 
| Chris@303 | 697 | 
| Chris@303 | 698 	if (s.contains(i->frame)) { | 
| Chris@303 | 699 	    ImageModel::Point newPoint(*i); | 
| Chris@303 | 700 	    newPoint.frame = i->frame + newStartFrame - s.getStartFrame(); | 
| Chris@303 | 701 	    command->deletePoint(*i); | 
| Chris@303 | 702 	    command->addPoint(newPoint); | 
| Chris@303 | 703 	} | 
| Chris@303 | 704     } | 
| Chris@303 | 705 | 
| Chris@303 | 706     command->finish(); | 
| Chris@303 | 707 } | 
| Chris@303 | 708 | 
| Chris@303 | 709 void | 
| Chris@303 | 710 ImageLayer::resizeSelection(Selection s, Selection newSize) | 
| Chris@303 | 711 { | 
| Chris@303 | 712     if (!m_model) return; | 
| Chris@303 | 713 | 
| Chris@303 | 714     ImageModel::EditCommand *command = | 
| Chris@303 | 715 	new ImageModel::EditCommand(m_model, tr("Resize Selection")); | 
| Chris@303 | 716 | 
| Chris@303 | 717     ImageModel::PointList points = | 
| Chris@303 | 718 	m_model->getPoints(s.getStartFrame(), s.getEndFrame()); | 
| Chris@303 | 719 | 
| Chris@303 | 720     double ratio = | 
| Chris@303 | 721 	double(newSize.getEndFrame() - newSize.getStartFrame()) / | 
| Chris@303 | 722 	double(s.getEndFrame() - s.getStartFrame()); | 
| Chris@303 | 723 | 
| Chris@303 | 724     for (ImageModel::PointList::iterator i = points.begin(); | 
| Chris@303 | 725 	 i != points.end(); ++i) { | 
| Chris@303 | 726 | 
| Chris@303 | 727 	if (s.contains(i->frame)) { | 
| Chris@303 | 728 | 
| Chris@303 | 729 	    double target = i->frame; | 
| Chris@303 | 730 	    target = newSize.getStartFrame() + | 
| Chris@303 | 731 		double(target - s.getStartFrame()) * ratio; | 
| Chris@303 | 732 | 
| Chris@303 | 733 	    ImageModel::Point newPoint(*i); | 
| Chris@303 | 734 	    newPoint.frame = lrint(target); | 
| Chris@303 | 735 	    command->deletePoint(*i); | 
| Chris@303 | 736 	    command->addPoint(newPoint); | 
| Chris@303 | 737 	} | 
| Chris@303 | 738     } | 
| Chris@303 | 739 | 
| Chris@303 | 740     command->finish(); | 
| Chris@303 | 741 } | 
| Chris@303 | 742 | 
| Chris@303 | 743 void | 
| Chris@303 | 744 ImageLayer::deleteSelection(Selection s) | 
| Chris@303 | 745 { | 
| Chris@303 | 746     if (!m_model) return; | 
| Chris@303 | 747 | 
| Chris@303 | 748     ImageModel::EditCommand *command = | 
| Chris@303 | 749 	new ImageModel::EditCommand(m_model, tr("Delete Selection")); | 
| Chris@303 | 750 | 
| Chris@303 | 751     ImageModel::PointList points = | 
| Chris@303 | 752 	m_model->getPoints(s.getStartFrame(), s.getEndFrame()); | 
| Chris@303 | 753 | 
| Chris@303 | 754     for (ImageModel::PointList::iterator i = points.begin(); | 
| Chris@303 | 755 	 i != points.end(); ++i) { | 
| Chris@303 | 756 	if (s.contains(i->frame)) command->deletePoint(*i); | 
| Chris@303 | 757     } | 
| Chris@303 | 758 | 
| Chris@303 | 759     command->finish(); | 
| Chris@303 | 760 } | 
| Chris@303 | 761 | 
| Chris@303 | 762 void | 
| Chris@303 | 763 ImageLayer::copy(Selection s, Clipboard &to) | 
| Chris@303 | 764 { | 
| Chris@303 | 765     if (!m_model) return; | 
| Chris@303 | 766 | 
| Chris@303 | 767     ImageModel::PointList points = | 
| Chris@303 | 768 	m_model->getPoints(s.getStartFrame(), s.getEndFrame()); | 
| Chris@303 | 769 | 
| Chris@303 | 770     for (ImageModel::PointList::iterator i = points.begin(); | 
| Chris@303 | 771 	 i != points.end(); ++i) { | 
| Chris@303 | 772 	if (s.contains(i->frame)) { | 
| Chris@303 | 773             //!!! inadequate | 
| Chris@303 | 774             Clipboard::Point point(i->frame, i->label); | 
| Chris@303 | 775             to.addPoint(point); | 
| Chris@303 | 776         } | 
| Chris@303 | 777     } | 
| Chris@303 | 778 } | 
| Chris@303 | 779 | 
| Chris@303 | 780 bool | 
| Chris@303 | 781 ImageLayer::paste(const Clipboard &from, int frameOffset, bool /* interactive */) | 
| Chris@303 | 782 { | 
| Chris@303 | 783     if (!m_model) return false; | 
| Chris@303 | 784 | 
| Chris@303 | 785     const Clipboard::PointList &points = from.getPoints(); | 
| Chris@303 | 786 | 
| Chris@303 | 787     ImageModel::EditCommand *command = | 
| Chris@303 | 788 	new ImageModel::EditCommand(m_model, tr("Paste")); | 
| Chris@303 | 789 | 
| Chris@303 | 790     for (Clipboard::PointList::const_iterator i = points.begin(); | 
| Chris@303 | 791          i != points.end(); ++i) { | 
| Chris@303 | 792 | 
| Chris@303 | 793         if (!i->haveFrame()) continue; | 
| Chris@303 | 794         size_t frame = 0; | 
| Chris@303 | 795         if (frameOffset > 0 || -frameOffset < i->getFrame()) { | 
| Chris@303 | 796             frame = i->getFrame() + frameOffset; | 
| Chris@303 | 797         } | 
| Chris@303 | 798         ImageModel::Point newPoint(frame); | 
| Chris@303 | 799 | 
| Chris@303 | 800         //!!! inadequate | 
| Chris@303 | 801 | 
| Chris@303 | 802         if (i->haveLabel()) { | 
| Chris@303 | 803             newPoint.label = i->getLabel(); | 
| Chris@303 | 804         } else if (i->haveValue()) { | 
| Chris@303 | 805             newPoint.label = QString("%1").arg(i->getValue()); | 
| Chris@303 | 806         } else { | 
| Chris@303 | 807             newPoint.label = tr("New Point"); | 
| Chris@303 | 808         } | 
| Chris@303 | 809 | 
| Chris@303 | 810         command->addPoint(newPoint); | 
| Chris@303 | 811     } | 
| Chris@303 | 812 | 
| Chris@303 | 813     command->finish(); | 
| Chris@303 | 814     return true; | 
| Chris@303 | 815 } | 
| Chris@303 | 816 | 
| Chris@303 | 817 QString | 
| Chris@303 | 818 ImageLayer::toXmlString(QString indent, QString extraAttributes) const | 
| Chris@303 | 819 { | 
| Chris@303 | 820     return Layer::toXmlString(indent, extraAttributes); | 
| Chris@303 | 821 } | 
| Chris@303 | 822 | 
| Chris@303 | 823 void | 
| Chris@303 | 824 ImageLayer::setProperties(const QXmlAttributes &attributes) | 
| Chris@303 | 825 { | 
| Chris@303 | 826 } | 
| Chris@303 | 827 |