QTableView上的DragDrop无法正常工作

时间:2018-03-13 13:35:46

标签: c++ qt5

我讨厌以下基本上包含std::vector<PassoProgramma>的类,并且构成了QTableView的模型。

Programma.h

#ifndef PROGRAMMA_H
#define PROGRAMMA_H
#include <vector>
#include "PassoProgramma.h"
#include <QString>
#include <string>
#include <QAbstractTableModel>
#include <QFont>


class Programma  : public QAbstractTableModel
{
    Q_OBJECT
public:
    Programma(QString, std::vector<PassoProgramma>,  std::map<unsigned char, Asse>* const);
    Programma(const std::string, std::map<unsigned char, Asse>* const);

    void salva(const std::string, const std::string = "");
    int sizeHintForRow() const ;
    bool isIndexValid(const QModelIndex& index) const;
    inline std::string getNome() const  { return nome; };
    inline std::vector<PassoProgramma>* const getPassi() { return &Passi; }
    inline bool isSalvato() const { return salvato; }

    int rowCount(const QModelIndex&) const override;
    int columnCount(const QModelIndex&) const override;
    QVariant data(const QModelIndex&, int) const override;
    QVariant headerData(int, Qt::Orientation, int) const override;
    bool dropMimeData(const QMimeData*, Qt::DropAction , int , int , const QModelIndex&) override;
    bool removeRows(int, int, const QModelIndex&) override;
    bool insertRows(int, int, const QModelIndex&) override;
    bool moveRows(const QModelIndex&, const int, const int, const QModelIndex&, int) override;
    QMimeData* mimeData(const QModelIndexList&) const override;
    Qt::DropActions supportedDropActions() const override;
    Qt::ItemFlags flags(const QModelIndex&) const override;
    bool setData(const QModelIndex&, const QVariant&, int) override;
    QStringList mimeTypes() const override;

    PassoProgramma* operator[](int i)
    {
        if(i < Passi.size())    return &Passi.at(i);
        else throw new std::exception();
    };
    static const QFont headerFont;
    static const QFont dataFont;

private:
    struct myLocale : std::numpunct<char>
    {
    protected :
        char do_thousands_sep() const override { return '\0' ; }
        char do_decimal_point() const override { return '.' ; }
        std::string do_grouping() const override { return "" ; }
    };

    std::vector<PassoProgramma> Passi;
    std::string nome;
    bool salvato;
    std::map<unsigned char, Asse>* const assi;

};


class ELoadException : public std::exception
{
private:
    std::string message_;
public:
    ELoadException(const std::string& message) : message_(message) {};
    virtual const char* what() const throw()
    {
        return message_.c_str();
    }
};

#endif

Programma.cpp

#include "Programma.h"
#include "Editor.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <cstring>
#include <regex>
#include <QSize>
#include <QMimeData>
#include <QDataStream>

const QFont Programma::headerFont("Open Sans", 17, QFont::Weight::DemiBold);
const QFont Programma::dataFont("Open Sans", 22, QFont::Weight::DemiBold);


inline std::string& ltrim(std::string& str, const std::string& chars = "\t\v\f ")
{
    str.erase(0, str.find_first_not_of(chars));
    return str;
}

inline std::string& rtrim(std::string& str, const std::string& chars = "\t\v\f ")
{
    str.erase(str.find_last_not_of(chars) + 1);
    return str;
}

inline std::string& trim(std::string& str, const std::string& chars = "\t\v\f ")
{
    return ltrim(rtrim(str, chars), chars);
}


Programma::Programma(QString nome, std::vector<PassoProgramma> passi, std::map<unsigned char, Asse>* const assi) : Passi(passi), assi(assi)
{
    this->nome = nome.toStdString();

}


Programma::Programma(std::string fileFullName, std::map<unsigned char, Asse>* const assi) : assi(assi)
{
    std::ifstream stream;
    try
    {
        stream.open(fileFullName, std::ios::in);
    }
    catch(std::exception& e)
    {
        std::stringstream st;
        st << "Error opening file \"" << fileFullName << "\".\nError:   " << e.what();
        throw ELoadException(st.str());
    }

    int slash = fileFullName.find_last_of("/");
    nome = fileFullName.substr(slash + 1);
    Passi.reserve(100);

    std::string line;
    char* token;
    unsigned int lineCount = 0, tokenCount;

    while(std::getline(stream, line))  //  Per ogni linea del file estraggo fino a 6 token
    {
        if(trim(line).length() == 0) continue;

        //  Controllo che la linea appena letta dal file contenga esattamente 2, 4 o 6 coppie <lettera, float>.
        if(!std::regex_match(line, std::regex(R"(^(?:[A-Z] [-+]?[0-9]+(?:\.[0-9]+)?(?: |$)){1,3}$)")))
        {
            std::stringstream st;
            st << "Line #" << lineCount << " of file \"" << fileFullName << "\" is invalid.";
            throw ELoadException(st.str());
        }

        ++lineCount;
        PassoProgramma passo;
        tokenCount = 0;
        char* cstr = new char[line.length() + 1];
        std::strcpy(cstr, line.c_str());  //  Converto la stringa costante in char* non costante, per poterla fornire a strtok.
        token = strtok(cstr, " ");
        while(token)
        {
            ++tokenCount;
            switch(tokenCount)
            {
                case 1:
                    passo.asse1 = &assi->at(token[0]);
                    break;
                case 2:
                {
                    std::istringstream iStr(token);
                    iStr >> passo.target1;
                    break;
                }
                case 3:
                    passo.asse2 = &assi->at(token[0]);
                    break;
                case 4:
                {
                    std::istringstream iStr(token);
                    iStr >> passo.target2;
                    break;
                }
                case 5:
                    passo.asse3 = &assi->at(token[0]);
                    break;
                case 6:
                {
                    std::istringstream iStr(token);
                    iStr >> passo.target3;
                    break;
                }
            }
            token = strtok(NULL, " ");  //  Vado al prossimo token senza cambiare la stringa da dividere
        }
        Passi.push_back(passo);
    }
    salvato = true;
}


void Programma::salva(const std::string path, const std::string nome /* facoltativo; se specificato costituir?? il nome del file */)
{
    if(nome != "")
        this->nome = nome;
    std::stringstream destFileFullName("");
    destFileFullName << path << "/" << this->nome << ".prg";

    std::ofstream f;
    f.imbue(std::locale(std::locale("en_US.UTF-8"), new Programma::myLocale()));
    try
    {
        f.open(destFileFullName.str());
    }
    catch(std::exception& e)
    {
        std::stringstream st;
        st << "Error writing to file \"" << st.str() << "\".\nError:   " << e.what();
        throw ELoadException(st.str());
    }

    for(const PassoProgramma& passo : Passi)
    {
        f << passo.toString() << std::endl;
    }
    //  f.flush();
    f.close();
    salvato = true;
}


int Programma::rowCount(const QModelIndex& parent) const
{
    Q_UNUSED(parent);
    return Passi.size();
}

int Programma::columnCount(const QModelIndex& parent) const
{
    Q_UNUSED(parent);
    return 6;
}

QVariant Programma::data(const QModelIndex& index, int role) const
{
    if(!index.isValid())
        return QVariant();

    if(role == Qt::TextAlignmentRole)
    {
        return QVariant(Qt::AlignHCenter | Qt::AlignVCenter);
    }
    else if(role == Qt::DisplayRole || role == Qt::EditRole)
    {
        switch(index.column())
        {
            case 0:
                return QString(Passi.at(index.row()).asse1->asse);
                break;
            case 1:
                return QString::number(Passi.at(index.row()).target1, 'f', Passi.at(index.row()).asse1->getCifreDecimali()).replace("-", Editor::MENO);
                break;
            case 2:
                return (Passi.at(index.row()).getNumeroAssi() < 2) ? QVariant() : QString(Passi.at(index.row()).asse2->asse);
                break;
            case 3:
                return (Passi.at(index.row()).getNumeroAssi() < 2) ? QVariant() : QString::number(Passi.at(index.row()).target2, 'f', Passi.at(index.row()).asse2->getCifreDecimali()).replace("-", Editor::MENO);
                break;
            case 4:
                return (Passi.at(index.row()).getNumeroAssi() < 3) ? QVariant() : QString(Passi.at(index.row()).asse3->asse);
                break;
            case 5:
                return (Passi.at(index.row()).getNumeroAssi() < 3) ? QVariant() : QString::number(Passi.at(index.row()).target3, 'f', Passi.at(index.row()).asse3->getCifreDecimali()).replace("-", Editor::MENO);
                break;
        }
    }
    else if(role == Qt::SizeHintRole)
    {
        switch(index.column())
        {
            case 0:
            case 2:
            case 4:
                return QSize(50, 42);
            case 1:
            case 3:
            case 5:
                return QSize(105, 42);
        }
    }
    else if(role == Qt::FontRole) return dataFont;
    return QVariant();
}

QVariant Programma::headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
{
    if(Qt::Orientation::Horizontal == orientation)
    {
        if(role == Qt::DisplayRole)
        {
            switch(section)
            {
                case 0:
                case 2:
                case 4:
                    return QString("ASSE");
                case 1:
                case 3:
                case 5:
                    return QString("TARGET");
            }
        }
        else if(role == Qt::TextAlignmentRole)
        {
            return Qt::AlignCenter;
        }
        else if(role == Qt::FontRole) return headerFont;
        else if(role == Qt::SizeHintRole)
        {
            switch(section)
            {
                case 0:
                case 2:
                case 4: return QSize(50, 28);
                case 1:
                case 3:
                case 5: return QSize(105, 28);
            }
        }

    }
    else
    {
        if(role == Qt::DisplayRole)
        {
            return QString::number(section + 1);
        }
        else if(role == Qt::TextAlignmentRole)
        {
            return Qt::AlignRight;
        }
        /*
        else if(role == Qt::SizeHintRole)
        {
            return QSize(45, 45);
        }
        */
        else if(role == Qt::FontRole) return headerFont;
    }

    return QVariant();
}

int Programma::sizeHintForRow() const
{
    return 42;
}

bool Programma::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent)
{
    QByteArray encodedData = data->data("application/text");
    QDataStream stream(&encodedData, QIODevice::ReadOnly);
    QStringList newItems;
    int rows = 0;

    //  I dati contengono una stringa multilinea: devo dividerli nelle varie linee.
    while(!stream.atEnd())
    {
        QString text;
        stream >> text;
        newItems << text;
        ++rows;
    }

    //  Divido ogni linea nei token di cui è composta.
    for(int i = 0; i < rows; i++)
    {
        PassoProgramma nuovoPasso;
        int tokenCount = 0;
        std::string token(strtok(&(newItems[i].toStdString()[0]), " "));
        while(token.length() > 0)
        {
            ++tokenCount;
            switch(tokenCount)
            {
                case 1:
                    nuovoPasso.asse1 = &assi->at(token[0]);;
                    break;
                case 2:
                {
                    nuovoPasso.target1 = std::stof(token);
                    break;
                }
                case 3:
                    nuovoPasso.asse2 = &assi->at(token[0]);
                    break;
                case 4:
                {
                    nuovoPasso.target2 = std::stof(token);
                    break;
                }
                case 5:
                    nuovoPasso.asse3 = &assi->at(token[0]);
                    break;
                case 6:
                {
                    nuovoPasso.target3 = std::stof(token);
                    break;
                }
            }
            token = strtok(NULL, " ");  //  Va al prossimo token senza modificare la stringa da dividere
        }
        Passi.insert(Passi.begin() + row, nuovoPasso);
    }
    salvato = false;
    return true;
}

bool Programma::removeRows(int row, int count, const QModelIndex& parent = QModelIndex())
{
    if(row < 0 || row >= Passi.size() || count <= 0 || (row + count) > Passi.size())
    {
        return false;
    }
    beginRemoveRows(parent, row, row + count - 1);
    Passi.erase(Passi.begin() + row, Passi.begin() + row + count);
    endRemoveRows();

    salvato = false;
    return true;
}


bool Programma::insertRows(int row, int count, const QModelIndex& parent = QModelIndex())
{
    if(row < 0 || count < 0 || row + count >= Passi.size())
    {
        return false;
    }
    beginInsertRows(parent, row, row + count - 1);
    for(int i = 0; i < count; i++)
    {
        Passi.insert(Passi.begin() + row, PassoProgramma());
    }
    endInsertRows();
    return true;
}

bool Programma::moveRows(const QModelIndex& sourceParent, const int sourceRow, const int count, const QModelIndex& destinationParent, int destinationChild)
{
    for(int i = 0; i < count; ++i)
    {
        PassoProgramma toBeMoved = Passi.at(sourceRow + i);
        Passi.insert(Passi.begin() + destinationChild + i, toBeMoved);
        Passi.erase(Passi.begin() + sourceRow + i);
    }
    salvato = false;
    return true;
}

QMimeData* Programma::mimeData(const QModelIndexList& indexes) const
{
    QMimeData* mimeData = new QMimeData();
    QByteArray encodedData;

    QDataStream stream(&encodedData, QIODevice::WriteOnly);

    foreach(const QModelIndex& index, indexes)
    {
        if(index.isValid())
        {
            stream << Passi.at(index.row()).toString().c_str() << '\n';
        }
    }

    mimeData->setData("application/text", encodedData);
    return mimeData;
}

Qt::DropActions Programma::supportedDropActions() const
{
    return Qt::MoveAction;
}

Qt::ItemFlags Programma::flags(const QModelIndex& index) const
{
    if(index.isValid())
    {
        if(index.column() < 2 && Passi[index.row()].getNumeroAssi() == 0
                || index.column() < 4 && Passi[index.row()].getNumeroAssi() == 1
                || Passi[index.row()].getNumeroAssi() >= 2
        )
            return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsEnabled;
    }
    return QAbstractItemModel::flags(index) & ~Qt::ItemIsEnabled;
}

bool Programma::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if(index.isValid() && role == Qt::EditRole)
    {
        switch(index.column())
        {
            case 0: Passi[index.row()].asse1 = &assi->at(value.toString()[0].toLatin1());
                break;
            case 1: Passi[index.row()].target1 = value.toString().replace(Editor::MENO, "-").toFloat();
                break;
            case 2: Passi[index.row()].asse2 = &assi->at(value.toString()[0].toLatin1());
                break;
            case 3: Passi[index.row()].target2 = value.toString().replace(Editor::MENO, "-").toFloat();
                break;
            case 4: Passi[index.row()].asse3 = &assi->at(value.toString()[0].toLatin1());
                break;
            case 5: Passi[index.row()].target3 = value.toString().replace(Editor::MENO, "-").toFloat();
                break;
        }
        salvato = false;
        emit dataChanged(index, index);
        return true;
    }
    else return false;
}

bool Programma::isIndexValid(const QModelIndex& index) const
{
    return index.row() >= 0 && index.row() < Passi.size() && index.isValid();
}

QStringList Programma::mimeTypes() const
{
    return QStringList() << "application/text";
}

如您所见,我已覆盖(然后在Programma.cpp中实现)用于移动,删除和创建行的所有基本事件。 我有一个删除当前行的按钮和一个插入新行的按钮;此外,我希望通过拖放来重新排序行 前面提到的两个按钮确实有效,但拖放功能不起作用:表格的行确实会移动,但{J},moveRowsmimeData方法都没有调用。因此,行标题号遵循相应的数据;相反,标题应该在每次拖拽后自动进行再生。

为什么会这样? 我缝合了Qt5文档中提供的示例。

也许我的问题是微不足道的,但我在Linux下开发C ++ 11是两者中的新手。 (只有我老板知道原因)。

0 个答案:

没有答案