view data/model/ImageModel.h @ 1648:86bbccb79c9b single-point

Switch to a single external set of commands for modifying editables with events
author Chris Cannam
date Fri, 15 Mar 2019 10:57:35 +0000
parents ad5f892c0c4d
children a77a7e8c085c
line wrap: on
line source
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */

/*
    Sonic Visualiser
    An audio file viewer and annotation editor.
    Centre for Digital Music, Queen Mary, University of London.
    This file copyright 2006-2007 Chris Cannam and QMUL.
    
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License as
    published by the Free Software Foundation; either version 2 of the
    License, or (at your option) any later version.  See the file
    COPYING included with this distribution for more information.
*/

#ifndef SV_IMAGE_MODEL_H
#define SV_IMAGE_MODEL_H

#include "SparseModel.h"
#include "base/XmlExportable.h"
#include "base/RealTime.h"

#include <QStringList>

/**
 * Image point type for use in a SparseModel.  This represents an
 * image, identified by filename, at a given time.  The filename can
 * be empty, in which case we instead have a space to put an image in.
 */

struct ImagePoint : public XmlExportable
{
public:
    ImagePoint(sv_frame_t _frame) : frame(_frame) { }
    ImagePoint(sv_frame_t _frame, QString _image, QString _label) :
        frame(_frame), image(_image), label(_label) { }

    int getDimensions() const { return 1; }
    
    sv_frame_t frame;
    QString image;
    QString label;

    QString getLabel() const { return label; }
    
    void toXml(QTextStream &stream,
               QString indent = "",
               QString extraAttributes = "") const override
    {
        stream <<
            QString("%1<point frame=\"%2\" image=\"%3\" label=\"%4\" %5/>\n")
            .arg(indent).arg(frame)
            .arg(encodeEntities(image))
            .arg(encodeEntities(label))
            .arg(extraAttributes);
    }

    QString toDelimitedDataString(QString delimiter, DataExportOptions, sv_samplerate_t sampleRate) const
    {
        QStringList list;
        list << RealTime::frame2RealTime(frame, sampleRate).toString().c_str();
        list << image;
        if (label != "") list << label;
        return list.join(delimiter);
    }

    struct Comparator {
        bool operator()(const ImagePoint &p1,
                        const ImagePoint &p2) const {
            if (p1.frame != p2.frame) return p1.frame < p2.frame;
            if (p1.label != p2.label) return p1.label < p2.label;
            return p1.image < p2.image;
        }
    };
    
    struct OrderComparator {
        bool operator()(const ImagePoint &p1,
                        const ImagePoint &p2) const {
            return p1.frame < p2.frame;
        }
    };
};


// Make this a class rather than a typedef so it can be predeclared.

class ImageModel : public SparseModel<ImagePoint>
{
    Q_OBJECT

public:
    ImageModel(sv_samplerate_t sampleRate, int resolution, bool notifyOnAdd = true) :
        SparseModel<ImagePoint>(sampleRate, resolution, notifyOnAdd)
    { }

    QString getTypeName() const override { return tr("Image"); }

    void toXml(QTextStream &out,
                       QString indent = "",
                       QString extraAttributes = "") const override
    {
        SparseModel<ImagePoint>::toXml
            (out, 
             indent,
             QString("%1 subtype=\"image\"")
             .arg(extraAttributes));
    }

    /**
     * Command to change the image for a point.
     */
    class ChangeImageCommand : public Command
    {
    public:
        ChangeImageCommand(ImageModel *model,
                           const ImagePoint &point,
                           QString newImage,
                           QString newLabel) :
            m_model(model), m_oldPoint(point), m_newPoint(point) {
            m_newPoint.image = newImage;
            m_newPoint.label = newLabel;
        }

        QString getName() const override { return tr("Edit Image"); }

        void execute() override { 
            m_model->deletePoint(m_oldPoint);
            m_model->addPoint(m_newPoint);
            std::swap(m_oldPoint, m_newPoint);
        }

        void unexecute() override { execute(); }

    private:
        ImageModel *m_model;
        ImagePoint m_oldPoint;
        ImagePoint m_newPoint;
    };

    /**
     * TabularModel methods.  
     */
    
    int getColumnCount() const override
    {
        return 4;
    }

    QString getHeading(int column) const override
    {
        switch (column) {
        case 0: return tr("Time");
        case 1: return tr("Frame");
        case 2: return tr("Image");
        case 3: return tr("Label");
        default: return tr("Unknown");
        }
    }

    QVariant getData(int row, int column, int role) const override
    {
        if (column < 2) {
            return SparseModel<ImagePoint>::getData
                (row, column, role);
        }

        PointListConstIterator i = getPointListIteratorForRow(row);
        if (i == m_points.end()) return QVariant();

        switch (column) {
        case 2: return i->image;
        case 3: return i->label;
        default: return QVariant();
        }
    }

    Command *getSetDataCommand(int row, int column, const QVariant &value, int role) override
    {
        if (column < 2) {
            return SparseModel<ImagePoint>::getSetDataCommand
                (row, column, value, role);
        }

        if (role != Qt::EditRole) return 0;
        PointListIterator i = getPointListIteratorForRow(row);
        if (i == m_points.end()) return 0;
        EditCommand *command = new EditCommand(this, tr("Edit Data"));

        Point point(*i);
        command->deletePoint(point);

        switch (column) {
        case 2: point.image = value.toString(); break;
        case 3: point.label = value.toString(); break;
        }

        command->addPoint(point);
        return command->finish();
    }

    bool isColumnTimeValue(int column) const override
    {
        return (column < 2); 
    }

    SortType getSortType(int column) const override
    {
        if (column > 2) return SortAlphabetical;
        return SortNumeric;
    }
};


#endif