我讨厌以下基本上包含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},moveRows
和mimeData
方法都没有调用。因此,行标题号遵循相应的数据;相反,标题应该在每次拖拽后自动进行再生。
为什么会这样? 我缝合了Qt5文档中提供的示例。
也许我的问题是微不足道的,但我在Linux下开发C ++ 11是两者中的新手。 (只有我老板知道原因)。