fix(color): Limit label lengths, improve lbl display, prevent inv. char (#2287)
* (cpn) Add bullets & length warning * (fw) Show labels with bullet separators * (fw) Check all running twice. Remove from here * (fw) Correct buffer overflow when labels too long * (fw) Rem bullets in model label string * (fw, cpn) Labels length cpn,fw Limit label length cpn Check correct model on rename length cpn Focus issues cpn Add rename, move up/down.. still has an issue with selection. TODO... cpn Switch to utf8 encoding cpn Rename button ;) * (cpn) Move up down working re-Fix Checkbox, partially isn't what I thought (not greyed) * (fw) Add CSV string funcs Limit length of model edit shorter in portrait mode * (cpn) Correct Label option showing on B&W * (fw) Radio filename set but no file exists don't create * (cpn) Simplify wording * (fw) Correct debug values * (fw) Reduce truncation length * (both) Don't allow special chars in label name (cpn) Prevent move when at top and bottom (fw) Remove multiple label length defines (fw) Shorten truncate length even more * (fw) Close model_select on new model
This commit is contained in:
@@ -46,6 +46,7 @@ class AbstractStaticItemModel;
|
||||
constexpr char AIM_MODELDATA_TRAINERMODE[] {"modeldata.trainermode"};
|
||||
constexpr char AIM_MODELDATA_FUNCSWITCHCONFIG[] {"modeldata.funcswitchconfig"};
|
||||
constexpr char AIM_MODELDATA_FUNCSWITCHSTART[] {"modeldata.funcswitchstart"};
|
||||
constexpr int LABEL_LENGTH=16;
|
||||
|
||||
#define CHAR_FOR_NAMES " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-."
|
||||
#define CHAR_FOR_NAMES_REGEX "[ A-Za-z0-9_.-,]*"
|
||||
|
||||
@@ -106,33 +106,43 @@ void RadioData::convert(RadioDataConversionState & cstate)
|
||||
|
||||
void RadioData::addLabel(QString label)
|
||||
{
|
||||
label.replace("/c",",");
|
||||
label.replace("//","/");
|
||||
if(labels.indexOf(label) == -1)
|
||||
label = unEscapeCSV(label);
|
||||
// Truncate possible UTF-8 to 16char maximum
|
||||
QByteArray output = label.toUtf8();
|
||||
if (output.size() > LABEL_LENGTH) {
|
||||
int truncateAt = 0;
|
||||
for (int i = LABEL_LENGTH; i > 0; i--) {
|
||||
if ((output[i] & 0xC0) != 0x80) {
|
||||
truncateAt = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
output.truncate(truncateAt);
|
||||
}
|
||||
label = QString(output);
|
||||
if (labels.indexOf(label) == -1)
|
||||
labels.append(label);
|
||||
}
|
||||
|
||||
bool RadioData::deleteLabel(QString label)
|
||||
{
|
||||
bool deleted = false;
|
||||
QString csvLabel = label;
|
||||
csvLabel.replace("/","//");
|
||||
csvLabel.replace(",","/c");
|
||||
|
||||
// Remove labels in the models
|
||||
for(auto& model : models) {
|
||||
QStringList modelLabels = QString(model.labels).split(',',QString::SkipEmptyParts);
|
||||
if(modelLabels.indexOf(csvLabel) >= 0)
|
||||
QStringList modelLabels = fromCSV(QString::fromUtf8(model.labels));
|
||||
if (modelLabels.indexOf(label) >= 0) {
|
||||
deleted = true;
|
||||
modelLabels.removeAll(csvLabel);
|
||||
strcpy(model.labels, QString(modelLabels.join(',')).toLocal8Bit().data());
|
||||
modelLabels.removeAll(label);
|
||||
}
|
||||
strcpy(model.labels, toCSV(modelLabels).toUtf8().data());
|
||||
}
|
||||
|
||||
// Remove the label from the global list
|
||||
labels.removeAll(label);
|
||||
|
||||
// If no labels remain, add a Favorites one
|
||||
if(!labels.size()) {
|
||||
if (!labels.size()) {
|
||||
addLabel(tr("Favorites"));
|
||||
}
|
||||
return deleted;
|
||||
@@ -140,42 +150,46 @@ bool RadioData::deleteLabel(QString label)
|
||||
|
||||
bool RadioData::deleteLabel(int index)
|
||||
{
|
||||
if(index >= labels.size()) return false;
|
||||
if (index >= labels.size()) return false;
|
||||
QString modelLabel = labels.at(index);
|
||||
return deleteLabel(modelLabel);
|
||||
}
|
||||
|
||||
bool RadioData::renameLabel(QString from, QString to)
|
||||
{
|
||||
bool success = true;
|
||||
int lengthdiff = to.size() - from.size();
|
||||
QString csvFrom = from;
|
||||
QString csvTo = to;
|
||||
csvFrom.replace("/","//");
|
||||
csvFrom.replace(",","/c");
|
||||
csvTo.replace("/","//");
|
||||
csvTo.replace(",","/c");
|
||||
bool success = true;
|
||||
QString csvFrom = escapeCSV(from);
|
||||
QString csvTo = escapeCSV(to);
|
||||
int lengthdiff = csvTo.size() - csvFrom.size();
|
||||
|
||||
|
||||
// Check that rename is possible, not too long
|
||||
// Check that rename is possible (Rename won't cause too long of a string)
|
||||
for(auto& model : models) {
|
||||
if((int)strlen(model.labels) + lengthdiff > (int)sizeof(model.labels) - 1) {
|
||||
success = false;
|
||||
if (RadioData::fromCSV(model.labels).indexOf(from) != -1) {
|
||||
if ((int)strlen(model.labels) + lengthdiff > (int)sizeof(model.labels) - 1) {
|
||||
success = false;
|
||||
throw std::length_error(model.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(success) {
|
||||
if (success) {
|
||||
for(auto& model : models) {
|
||||
QStringList modelLabels = QString(model.labels).split(',',QString::SkipEmptyParts);
|
||||
int ind = modelLabels.indexOf(csvFrom);
|
||||
if(ind != -1) {
|
||||
if (ind != -1) {
|
||||
modelLabels.replace(ind, csvTo);
|
||||
QString outputcsv = QString(modelLabels.join(','));
|
||||
if(outputcsv.toLocal8Bit().size() < (int)sizeof(model.labels));
|
||||
strcpy(model.labels, outputcsv.toLocal8Bit().data());
|
||||
if (outputcsv.toUtf8().size() < (int)sizeof(model.labels)) {
|
||||
strcpy(model.labels, outputcsv.toUtf8().data());
|
||||
} else { // Shouldn't ever get here, from check above
|
||||
success = false;
|
||||
throw std::length_error(model.name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int ind = labels.indexOf(from);
|
||||
if(ind != -1) {
|
||||
if (ind != -1) {
|
||||
labels.replace(ind, to);
|
||||
}
|
||||
}
|
||||
@@ -184,42 +198,51 @@ bool RadioData::renameLabel(QString from, QString to)
|
||||
|
||||
bool RadioData::renameLabel(int index, QString to)
|
||||
{
|
||||
if(index >= labels.size()) return false;
|
||||
if (index >= labels.size()) return false;
|
||||
QString from = labels.at(index);
|
||||
return renameLabel(from, to);
|
||||
}
|
||||
|
||||
void RadioData::swapLabel(int indFrom, int indTo)
|
||||
{
|
||||
if(abs(indFrom - indTo) > 1 ||
|
||||
indFrom >= labels.size() ||
|
||||
indTo >= labels.size() ||
|
||||
indFrom < 0 ||
|
||||
indTo < 0)
|
||||
return;
|
||||
QString tmplbl = labels.at(indFrom);
|
||||
labels.replace(indFrom, labels.at(indTo));
|
||||
labels.replace(indTo, tmplbl);
|
||||
}
|
||||
|
||||
bool RadioData::addLabelToModel(int index, QString label)
|
||||
{
|
||||
if(index >= models.size()) return false;
|
||||
|
||||
label.replace("/","//");
|
||||
label.replace(",","/c");
|
||||
if (index >= models.size()) return false;
|
||||
label = escapeCSV(label);
|
||||
|
||||
char *modelLabelCsv = models[index].labels;
|
||||
// Make sure it will fit
|
||||
if(strlen(modelLabelCsv) + label.size() + 1 < sizeof(models[index].labels)-1) {
|
||||
QStringList modelLabels = QString(modelLabelCsv).split(',',QString::SkipEmptyParts);
|
||||
if(modelLabels.indexOf(label) == -1) {
|
||||
if (strlen(modelLabelCsv) + label.size() + 1 < sizeof(models[index].labels)-1) {
|
||||
QStringList modelLabels = QString::fromUtf8(modelLabelCsv).split(',',QString::SkipEmptyParts);
|
||||
if (modelLabels.indexOf(label) == -1) {
|
||||
modelLabels.append(label);
|
||||
strcpy(models[index].labels, QString(modelLabels.join(',')).toLocal8Bit().data());
|
||||
strcpy(models[index].labels, QString(modelLabels.join(',')).toUtf8().data());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
throw std::length_error(models[index].name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RadioData::removeLabelFromModel(int index, QString label)
|
||||
{
|
||||
if(index >= models.size()) return false;
|
||||
label.replace("/","//");
|
||||
label.replace(",","/c");
|
||||
if (index >= models.size()) return false;
|
||||
|
||||
char *modelLabelCsv = models[index].labels;
|
||||
QStringList modelLabels = QString(modelLabelCsv).split(',',QString::SkipEmptyParts);
|
||||
if(modelLabels.indexOf(label) >= 0) {
|
||||
modelLabels.removeAll(label);
|
||||
strcpy(models[index].labels, QString(modelLabels.join(',')).toLocal8Bit().data());
|
||||
QStringList lbls = fromCSV(QString::fromUtf8(models[index].labels));
|
||||
if (lbls.indexOf(label) >= 0) {
|
||||
lbls.removeAll(label);
|
||||
strcpy(models[index].labels, toCSV(lbls).toUtf8().data());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -234,3 +257,34 @@ void RadioData::addLabelsFromModels()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QStringList RadioData::fromCSV(const QString &csv)
|
||||
{
|
||||
QStringList lbls = QString(csv).split(',',QString::SkipEmptyParts);
|
||||
for(QString &label: lbls) {
|
||||
label = unEscapeCSV(label);
|
||||
}
|
||||
return lbls;
|
||||
}
|
||||
|
||||
QString RadioData::toCSV(QStringList lbls)
|
||||
{
|
||||
for(QString &label: lbls) {
|
||||
label = escapeCSV(label);
|
||||
}
|
||||
return lbls.join(',');
|
||||
}
|
||||
|
||||
QString RadioData::escapeCSV(QString str)
|
||||
{
|
||||
str.replace("/","//");
|
||||
str.replace(",","/c");
|
||||
return str;
|
||||
}
|
||||
|
||||
QString RadioData::unEscapeCSV(QString str)
|
||||
{
|
||||
str.replace("/c",",");
|
||||
str.replace("//","/");
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -27,10 +27,16 @@ class RadioData {
|
||||
bool deleteLabel(int index);
|
||||
bool renameLabel(QString from, QString to);
|
||||
bool renameLabel(int index, QString to);
|
||||
void swapLabel(int indFrom, int indTo);
|
||||
bool addLabelToModel(int index, QString label);
|
||||
bool removeLabelFromModel(int index, QString label);
|
||||
void addLabelsFromModels();
|
||||
|
||||
static QStringList fromCSV(const QString &csv);
|
||||
static QString toCSV(QStringList lbls);
|
||||
static QString escapeCSV(QString str);
|
||||
static QString unEscapeCSV(QString str);
|
||||
|
||||
void setCurrentModel(unsigned int index);
|
||||
void fixModelFilenames();
|
||||
QString getNextModelFilename();
|
||||
@@ -41,4 +47,5 @@ class RadioData {
|
||||
|
||||
protected:
|
||||
void fixModelFilename(unsigned int index);
|
||||
|
||||
};
|
||||
|
||||
+83
-112
@@ -22,12 +22,12 @@ Qt::ItemFlags LabelsModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
Qt::ItemFlags flags = Qt::ItemIsEnabled;
|
||||
|
||||
if(index.column() == 0) {
|
||||
if (index.column() == 0) {
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
flags |= Qt::ItemIsSelectable;
|
||||
flags |= Qt::ItemIsDropEnabled;
|
||||
flags |= Qt::ItemIsEditable;
|
||||
if(index.isValid())
|
||||
if (index.isValid())
|
||||
flags |= Qt::ItemIsDragEnabled;
|
||||
}
|
||||
return flags;
|
||||
@@ -35,33 +35,48 @@ Qt::ItemFlags LabelsModel::flags(const QModelIndex &index) const
|
||||
|
||||
bool LabelsModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if(!index.isValid() || index.column() != 0 ||
|
||||
if (!index.isValid() || index.column() != 0 ||
|
||||
index.row() >= radioData->labels.size())
|
||||
return false;
|
||||
|
||||
if(role == Qt::CheckStateRole) {
|
||||
if(value==Qt::Unchecked && selectedModel != -1) {
|
||||
if(radioData->removeLabelFromModel(selectedModel, radioData->labels.at(index.row())))
|
||||
if (role == Qt::CheckStateRole) {
|
||||
if (value==Qt::Unchecked && selectedModel != -1) {
|
||||
if (radioData->removeLabelFromModel(selectedModel, radioData->labels.at(index.row())))
|
||||
emit modelChanged(selectedModel);
|
||||
} else {
|
||||
if(radioData->addLabelToModel(selectedModel, radioData->labels.at(index.row())))
|
||||
try{
|
||||
if (radioData->addLabelToModel(selectedModel, radioData->labels.at(index.row())))
|
||||
emit modelChanged(selectedModel);
|
||||
} catch(const std::length_error& le) {
|
||||
emit labelsFault(tr("Unable to add label \"%1\" to model \"%2\" not enough room")\
|
||||
.arg(radioData->labels.at(index.row())).arg(le.what()));
|
||||
}
|
||||
}
|
||||
emit dataChanged(this->index(index.row(), 0),
|
||||
this->index(index.row(), 0));
|
||||
return true;
|
||||
} else if(role == Qt::EditRole) {
|
||||
} else if (role == Qt::EditRole) {
|
||||
QString replFrom = labels[index.row()].label;
|
||||
QString replTo = value.toString();
|
||||
if(radioData->labels.indexOf(replTo) == -1) { // Don't allow duplicates
|
||||
bool modelsChanged = radioData->renameLabel(replFrom,replTo);
|
||||
if (replFrom == replTo) // User exits edit without changing
|
||||
return true;
|
||||
if (radioData->labels.indexOf(replTo) == -1) { // Don't allow duplicates
|
||||
bool modelsChanged = false;
|
||||
try{
|
||||
modelsChanged = radioData->renameLabel(replFrom,replTo);
|
||||
} catch(const std::length_error& le) {
|
||||
emit labelsFault(tr("Unable to rename \"%1\" to \"%2\" not enough room in model %3")\
|
||||
.arg(replFrom).arg(replTo).arg(le.what()));
|
||||
return false;
|
||||
}
|
||||
labels[index.row()].label = replTo;
|
||||
emit dataChanged(this->index(index.row(), 0),
|
||||
this->index(index.row(), 0));
|
||||
if(selectedModel != -1 && modelsChanged)
|
||||
if (selectedModel != -1 && modelsChanged)
|
||||
emit modelChanged(selectedModel);
|
||||
} else {
|
||||
emit renameFault(tr("Unable to rename \"%1\" to \"%2\" the label already exists").arg(replFrom).arg(replTo));
|
||||
emit labelsFault(tr("Unable to rename \"%1\" to \"%2\" the label already exists")\
|
||||
.arg(replFrom).arg(replTo));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -70,22 +85,21 @@ bool LabelsModel::setData(const QModelIndex &index, const QVariant &value, int r
|
||||
|
||||
QVariant LabelsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if(index.row() >= labels.size() || !index.isValid() )
|
||||
if (index.row() >= labels.size() || !index.isValid() )
|
||||
return QVariant();
|
||||
|
||||
QString label = radioData->labels.at(index.row());
|
||||
|
||||
if(role == Qt::DisplayRole || role == Qt::EditRole) {
|
||||
if(index.column() == 0) {
|
||||
if (role == Qt::DisplayRole || role == Qt::EditRole) {
|
||||
if (index.column() == 0) {
|
||||
return labels.at(index.row()).label;
|
||||
//return label;
|
||||
}
|
||||
} else if (role == Qt::CheckStateRole) {
|
||||
if(index.column() == 0 && selectedModel >= 0 &&
|
||||
if (index.column() == 0 && selectedModel >= 0 &&
|
||||
selectedModel < (int)radioData->models.size()) {
|
||||
QStringList modelLabels = QString(radioData->models.at(selectedModel).labels).split(',',QString::SkipEmptyParts);
|
||||
label.replace("/","//");
|
||||
label.replace(",","/c");
|
||||
label = RadioData::escapeCSV(label);
|
||||
return modelLabels.indexOf(label)==-1?Qt::Unchecked:Qt::Checked;
|
||||
} else if (index.column() == 0 && selectedModel == -1) {
|
||||
return Qt::Unchecked;
|
||||
@@ -98,7 +112,7 @@ QModelIndex LabelsModel::index(int row, int column, const QModelIndex &parent) c
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
if(row < labels.size())
|
||||
if (row < labels.size())
|
||||
return createIndex(row,0);
|
||||
|
||||
return QModelIndex();
|
||||
@@ -123,94 +137,13 @@ int LabelsModel::columnCount(const QModelIndex &parent) const
|
||||
|
||||
QVariant LabelsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(role != Qt::DisplayRole ||
|
||||
if (role != Qt::DisplayRole ||
|
||||
orientation != Qt::Horizontal ||
|
||||
section != 0)
|
||||
return QVariant();
|
||||
return tr("Labels");
|
||||
}
|
||||
|
||||
Qt::DropActions LabelsModel::supportedDropActions() const
|
||||
{
|
||||
return Qt::MoveAction;
|
||||
}
|
||||
|
||||
QStringList LabelsModel::mimeTypes() const
|
||||
{
|
||||
QStringList types;
|
||||
types << "application/edglbl.text.list";
|
||||
return types;
|
||||
}
|
||||
|
||||
QMimeData *LabelsModel::mimeData(const QModelIndexList &indexes) const
|
||||
{
|
||||
QMimeData *mimeData = new QMimeData;
|
||||
QByteArray encodedData;
|
||||
|
||||
QDataStream stream(&encodedData, QIODevice::WriteOnly);
|
||||
|
||||
for (const QModelIndex &index : indexes) {
|
||||
if (index.isValid()) {
|
||||
QString text = data(index, Qt::DisplayRole).toString();
|
||||
stream << text;
|
||||
}
|
||||
}
|
||||
|
||||
mimeData->setData("application/edglbl.text.list", encodedData);
|
||||
return mimeData;
|
||||
}
|
||||
|
||||
bool LabelsModel::canDropMimeData(const QMimeData *data, Qt::DropAction action,
|
||||
int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(action);
|
||||
Q_UNUSED(row);
|
||||
Q_UNUSED(parent);
|
||||
|
||||
if (!data->hasFormat("application/edglbl.text.list"))
|
||||
return false;
|
||||
|
||||
if (column > 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LabelsModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
|
||||
{
|
||||
// TODO: This needs to be completed
|
||||
if (!canDropMimeData(data, action, row, column, parent))
|
||||
return false;
|
||||
|
||||
if(row < 0) return false;
|
||||
|
||||
if (action == Qt::IgnoreAction)
|
||||
return true;
|
||||
else if (action != Qt::MoveAction)
|
||||
return false;
|
||||
|
||||
QByteArray encodedData = data->data("application/edglbl.text.list");
|
||||
QDataStream stream(&encodedData, QIODevice::ReadOnly);
|
||||
QStringList newItems;
|
||||
int rows = 0;
|
||||
|
||||
while (!stream.atEnd()) {
|
||||
QString text;
|
||||
stream >> text;
|
||||
newItems << text;
|
||||
++rows;
|
||||
}
|
||||
|
||||
/*insertRows(row, rows, QModelIndex());
|
||||
for (const QString &text : qAsConst(newItems))
|
||||
{
|
||||
QModelIndex idx = index(row, 0, QModelIndex());
|
||||
row++;
|
||||
}*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LabelsModel::insertRows(int row, int count, const QModelIndex &parent)
|
||||
{
|
||||
if (parent.isValid())
|
||||
@@ -218,16 +151,16 @@ bool LabelsModel::insertRows(int row, int count, const QModelIndex &parent)
|
||||
|
||||
// Find a New Number
|
||||
for (int i = 0; i < count; i++) {
|
||||
int newno=0;
|
||||
QString newstr;
|
||||
int newNo=0;
|
||||
QString newStr;
|
||||
do {
|
||||
newstr = QString(tr("New%1").arg(newno));
|
||||
if(newno == 0)
|
||||
newstr = QString(tr("New"));
|
||||
newno++;
|
||||
} while(radioData->labels.indexOf(newstr) >= 0);
|
||||
newStr = QString(tr("New%1").arg(newNo));
|
||||
if (newNo == 0)
|
||||
newStr = QString(tr("New"));
|
||||
newNo++;
|
||||
} while(radioData->labels.indexOf(newStr) >= 0);
|
||||
// Add it to radioData
|
||||
radioData->labels.insert(row+i, newstr);
|
||||
radioData->labels.insert(row+i, newStr);
|
||||
}
|
||||
buildLabelsList();
|
||||
return true;
|
||||
@@ -240,12 +173,12 @@ bool LabelsModel::removeRows(int row, int count, const QModelIndex &parent)
|
||||
bool deleted=false;
|
||||
beginRemoveRows(parent, row, row + count - 1);
|
||||
for (int i = 0; i != count; ++i)
|
||||
if(radioData->deleteLabel(row+i)) {
|
||||
if (radioData->deleteLabel(row+i)) {
|
||||
deleted = true;
|
||||
}
|
||||
endRemoveRows();
|
||||
// Refresh all
|
||||
if(deleted)
|
||||
if (deleted)
|
||||
emit modelChanged(-1);
|
||||
buildLabelsList();
|
||||
return true;
|
||||
@@ -273,7 +206,7 @@ void LabelsModel::modelsSelectionChanged()
|
||||
QModelIndex index = modelsSelection->currentIndex();
|
||||
if (index.isValid()) {
|
||||
int mi = modelsSelection->currentIndex().row();
|
||||
if(mi < (int)radioData->models.size()) {
|
||||
if (mi < (int)radioData->models.size()) {
|
||||
selectedModel = mi;
|
||||
buildLabelsList();
|
||||
}
|
||||
@@ -281,3 +214,41 @@ void LabelsModel::modelsSelectionChanged()
|
||||
else
|
||||
selectedModel = -1;
|
||||
}
|
||||
|
||||
QValidator::State LabelValidator::validate(QString &label, int &pos) const
|
||||
{
|
||||
Q_UNUSED(pos)
|
||||
QString lbl = RadioData::escapeCSV(label);
|
||||
if (lbl.toUtf8().size() > LABEL_LENGTH)
|
||||
return QValidator::Invalid;
|
||||
if(lbl.contains('\\') || // TODO: Fix me to allow all, requires FW changes
|
||||
lbl.contains('\"') ||
|
||||
lbl.contains(':') ||
|
||||
lbl.contains('-') ||
|
||||
lbl.contains('\''))
|
||||
return QValidator::Invalid;
|
||||
if(lbl.size() == 0)
|
||||
return QValidator::Intermediate;
|
||||
return QValidator::Acceptable;
|
||||
}
|
||||
|
||||
void LabelValidator::fixup(QString &input) const
|
||||
{
|
||||
QByteArray output = input.toUtf8();
|
||||
if (output.size() > LABEL_LENGTH) {
|
||||
int truncateAt = 0;
|
||||
for (int i = LABEL_LENGTH; i > 0; i--) {
|
||||
if ((output[i] & 0xC0) != 0x80) {
|
||||
truncateAt = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
output.truncate(truncateAt);
|
||||
}
|
||||
input = QString(output);
|
||||
input.remove('\\'); // TODO: Fix me to allow all, requires FW changes
|
||||
input.remove('\"');
|
||||
input.remove(':');
|
||||
input.remove('-');
|
||||
input.remove('\'');
|
||||
}
|
||||
|
||||
+28
-6
@@ -4,6 +4,9 @@
|
||||
#include <QList>
|
||||
#include <QAbstractItemModel>
|
||||
#include <QItemSelectionModel>
|
||||
#include <QValidator>
|
||||
#include <QLineEdit>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
#include "radiodata.h"
|
||||
|
||||
@@ -29,11 +32,6 @@ public:
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
Qt::DropActions supportedDropActions() const override;
|
||||
QStringList mimeTypes() const override;
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||
bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const override;
|
||||
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
|
||||
bool insertRows(int row, int count, const QModelIndex &parent) override;
|
||||
bool removeRows(int row, int count, const QModelIndex &parent) override;
|
||||
|
||||
@@ -45,7 +43,7 @@ private slots:
|
||||
|
||||
signals:
|
||||
void modelChanged(int index);
|
||||
void renameFault(QString msg);
|
||||
void labelsFault(QString msg);
|
||||
|
||||
private:
|
||||
QItemSelectionModel *modelsSelection;
|
||||
@@ -54,3 +52,27 @@ private:
|
||||
QList<QModelIndex> modelIndices;
|
||||
QList<LabelItem> labels;
|
||||
};
|
||||
|
||||
class LabelValidator : public QValidator
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QValidator::State validate(QString &label, int &pos) const;
|
||||
void fixup(QString &input) const;
|
||||
};
|
||||
|
||||
class LabelEditTextDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LabelEditTextDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
|
||||
|
||||
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QLineEdit *editor = new QLineEdit(parent);
|
||||
editor->setValidator(new LabelValidator);
|
||||
return editor;
|
||||
}
|
||||
};
|
||||
|
||||
+59
-12
@@ -221,7 +221,10 @@ void MdiChild::setupNavigation()
|
||||
addAct(ACT_MDL_WIZ, "wizard.png", SLOT(wizardEdit()), tr("Alt+W"));
|
||||
|
||||
addAct(ACT_LBL_ADD, "add.png", SLOT(labelAdd()), tr("Alt-L"));
|
||||
addAct(ACT_LBL_DEL, "clear.png", SLOT(labelDelete()), tr("Alt-L"));
|
||||
addAct(ACT_LBL_DEL, "clear.png", SLOT(labelDelete()), tr("Alt-L"));
|
||||
addAct(ACT_LBL_REN, "edit.png", SLOT(labelRename()), tr("Alt-R"));
|
||||
addAct(ACT_LBL_MVU, "moveup.png", SLOT(labelMoveUp()), tr("Alt-+"));
|
||||
addAct(ACT_LBL_MVD, "movedown.png", SLOT(labelMoveDown()), tr("Alt--"));
|
||||
|
||||
addAct(ACT_MDL_DFT, "currentmodel.png", SLOT(setDefault()), tr("Alt+U"));
|
||||
addAct(ACT_MDL_PRT, "print.png", SLOT(print()), QKeySequence::Print);
|
||||
@@ -247,7 +250,7 @@ void MdiChild::setupNavigation()
|
||||
// Add labels Label to bottom layout
|
||||
if(lblLabels)
|
||||
lblLabels->deleteLater();
|
||||
lblLabels = new QLabel(tr("Labels"));
|
||||
lblLabels = new QLabel(tr("Labels Management"));
|
||||
lblLabels->setStyleSheet("font-weight: bold");
|
||||
ui->bottomLayout->addWidget(lblLabels);
|
||||
|
||||
@@ -261,6 +264,9 @@ void MdiChild::setupNavigation()
|
||||
labelsToolbar->setStyleSheet(tbCss);
|
||||
labelsToolbar->addAction(getAction(ACT_LBL_ADD));
|
||||
labelsToolbar->addAction(getAction(ACT_LBL_DEL));
|
||||
labelsToolbar->addAction(getAction(ACT_LBL_REN));
|
||||
labelsToolbar->addAction(getAction(ACT_LBL_MVU));
|
||||
labelsToolbar->addAction(getAction(ACT_LBL_MVD));
|
||||
ui->bottomLayout->addWidget(labelsToolbar);
|
||||
|
||||
if (radioToolbar)
|
||||
@@ -353,8 +359,11 @@ void MdiChild::retranslateUi()
|
||||
action[ACT_ITM_EDT]->setText(tr("Edit Model"));
|
||||
action[ACT_ITM_DEL]->setText(tr("Delete"));
|
||||
|
||||
action[ACT_LBL_ADD]->setText(tr("Add Label"));
|
||||
action[ACT_LBL_DEL]->setText(tr("Delete Label"));
|
||||
action[ACT_LBL_ADD]->setText(tr("Add"));
|
||||
action[ACT_LBL_DEL]->setText(tr("Delete"));
|
||||
action[ACT_LBL_REN]->setText(tr("Rename"));
|
||||
action[ACT_LBL_MVU]->setText(tr("Move Up"));
|
||||
action[ACT_LBL_MVD]->setText(tr("Move Down"));
|
||||
|
||||
action[ACT_MDL_ADD]->setText(tr("Add Model"));
|
||||
action[ACT_MDL_ADD]->setIconText(tr("Model"));
|
||||
@@ -369,6 +378,7 @@ void MdiChild::retranslateUi()
|
||||
|
||||
radioToolbar->setWindowTitle(tr("Show Radio Actions Toolbar"));
|
||||
modelsToolbar->setWindowTitle(tr("Show Model Actions Toolbar"));
|
||||
labelsToolbar->setWindowTitle(tr("Show Labels Actions Toolbar"));
|
||||
}
|
||||
|
||||
QList<QAction *> MdiChild::getGeneralActions()
|
||||
@@ -416,6 +426,9 @@ QList<QAction *> MdiChild::getLabelsActions()
|
||||
QList<QAction *> actGrp;
|
||||
actGrp.append(getAction(ACT_LBL_ADD));
|
||||
actGrp.append(getAction(ACT_LBL_DEL));
|
||||
actGrp.append(getAction(ACT_LBL_REN));
|
||||
actGrp.append(getAction(ACT_LBL_MVU));
|
||||
actGrp.append(getAction(ACT_LBL_MVD));
|
||||
return actGrp;
|
||||
}
|
||||
|
||||
@@ -457,6 +470,9 @@ void MdiChild::showLabelsContextMenu(const QPoint &pos)
|
||||
|
||||
contextMenu.addAction(action[ACT_LBL_ADD]);
|
||||
contextMenu.addAction(action[ACT_LBL_DEL]);
|
||||
contextMenu.addAction(action[ACT_LBL_REN]);
|
||||
contextMenu.addAction(action[ACT_LBL_MVU]);
|
||||
contextMenu.addAction(action[ACT_LBL_MVD]);
|
||||
|
||||
if (!contextMenu.isEmpty())
|
||||
contextMenu.exec(ui->lstLabels->mapToGlobal(pos));
|
||||
@@ -467,6 +483,8 @@ void MdiChild::showContextMenu(const QPoint & pos)
|
||||
QMenu contextMenu;
|
||||
contextMenu.addAction(modelsToolbar->toggleViewAction());
|
||||
contextMenu.addAction(radioToolbar->toggleViewAction());
|
||||
if(firmware->getCapability(Capability::HasModelLabels))
|
||||
contextMenu.addAction(labelsToolbar->toggleViewAction());
|
||||
if (!contextMenu.isEmpty())
|
||||
contextMenu.exec(mapToGlobal(pos));
|
||||
}
|
||||
@@ -509,15 +527,11 @@ void MdiChild::initModelsList()
|
||||
labelsListModel = new LabelsModel(ui->modelsList->selectionModel(),
|
||||
&radioData, this);
|
||||
connect(labelsListModel, &LabelsModel::modelChanged, this, &MdiChild::modelLabelsChanged);
|
||||
connect(labelsListModel, &LabelsModel::renameFault, this, &MdiChild::labelRenameFault);
|
||||
connect(labelsListModel, &LabelsModel::labelsFault, this, &MdiChild::labelsFault);
|
||||
ui->lstLabels->setModel(labelsListModel);
|
||||
ui->lstLabels->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
ui->lstLabels->setDragEnabled(true);
|
||||
ui->lstLabels->setAcceptDrops(true);
|
||||
ui->lstLabels->setDropIndicatorShown(true);
|
||||
ui->lstLabels->setDragDropOverwriteMode(false);
|
||||
ui->lstLabels->setDragDropMode(QAbstractItemView::InternalMove);
|
||||
ui->lstLabels->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
|
||||
ui->lstLabels->setItemDelegate(new LabelEditTextDelegate);
|
||||
|
||||
ui->modelsList->setIndentation(0);
|
||||
|
||||
@@ -1634,23 +1648,56 @@ void MdiChild::modelExport()
|
||||
void MdiChild::labelAdd()
|
||||
{
|
||||
labelsListModel->insertRow(0);
|
||||
QModelIndex newind = labelsListModel->index(0,0);
|
||||
ui->lstLabels->setCurrentIndex(newind);
|
||||
ui->lstLabels->edit(newind);
|
||||
setWindowModified(true);
|
||||
}
|
||||
|
||||
void MdiChild::labelDelete()
|
||||
{
|
||||
int row = ui->lstLabels->selectionModel()->currentIndex().row();
|
||||
if(row < 0) return;
|
||||
labelsListModel->removeRow(row);
|
||||
setWindowModified(true);
|
||||
}
|
||||
|
||||
void MdiChild::modelLabelsChanged()
|
||||
void MdiChild::labelRename()
|
||||
{
|
||||
int row = ui->lstLabels->selectionModel()->currentIndex().row();
|
||||
if(row < 0) return;
|
||||
QModelIndex newind = labelsListModel->index(row,0);
|
||||
ui->lstLabels->setCurrentIndex(newind);
|
||||
ui->lstLabels->edit(newind);
|
||||
}
|
||||
|
||||
void MdiChild::labelMoveUp()
|
||||
{
|
||||
int row = ui->lstLabels->selectionModel()->currentIndex().row();
|
||||
if(row == 0) return;
|
||||
radioData.swapLabel(row, row-1);
|
||||
labelsListModel->buildLabelsList();
|
||||
ui->lstLabels->selectionModel()->setCurrentIndex(labelsListModel->index(row-1,0), QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
|
||||
void MdiChild::labelMoveDown()
|
||||
{
|
||||
int row = ui->lstLabels->selectionModel()->currentIndex().row();
|
||||
if(row == labelsListModel->rowCount() -1) return;
|
||||
radioData.swapLabel(row, row+1);
|
||||
labelsListModel->buildLabelsList();
|
||||
ui->lstLabels->selectionModel()->setCurrentIndex(labelsListModel->index(row+1,0), QItemSelectionModel::ClearAndSelect);
|
||||
}
|
||||
|
||||
void MdiChild::modelLabelsChanged(int row)
|
||||
{
|
||||
setWindowModified(true);
|
||||
refresh();
|
||||
ui->modelsList->selectionModel()->select(modelsListModel->index(row,0), QItemSelectionModel::ClearAndSelect |
|
||||
QItemSelectionModel::Rows);
|
||||
}
|
||||
|
||||
void MdiChild::labelRenameFault(QString msg)
|
||||
void MdiChild::labelsFault(QString msg)
|
||||
{
|
||||
QMessageBox::warning(this, CPN_STR_TTL_WARNING, msg);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ class MdiChild : public QWidget
|
||||
ACT_ITM_DEL, // delete model or cat
|
||||
ACT_LBL_ADD,
|
||||
ACT_LBL_DEL,
|
||||
ACT_LBL_MVU, // Move up
|
||||
ACT_LBL_MVD, // Move down
|
||||
ACT_LBL_REN, // Move down
|
||||
ACT_MDL_ADD, // model actions...
|
||||
ACT_MDL_CPY,
|
||||
ACT_MDL_CUT,
|
||||
@@ -140,8 +143,11 @@ class MdiChild : public QWidget
|
||||
void modelExport();
|
||||
void labelAdd();
|
||||
void labelDelete();
|
||||
void modelLabelsChanged();
|
||||
void labelRenameFault(QString msg);
|
||||
void labelRename();
|
||||
void labelMoveUp();
|
||||
void labelMoveDown();
|
||||
void modelLabelsChanged(int row);
|
||||
void labelsFault(QString msg);
|
||||
void wizardEdit();
|
||||
void modelDuplicate();
|
||||
|
||||
|
||||
@@ -682,7 +682,8 @@ void ModelsListModel::refresh()
|
||||
current->setData(currentColumn++, rxs);
|
||||
}
|
||||
if (hasLabels) {
|
||||
current->setData(currentColumn++, QString(model.labels));
|
||||
QStringList labels = RadioData::fromCSV(QString::fromUtf8(model.labels));
|
||||
current->setData(currentColumn++, labels.join(QChar(0x2022)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
#endif
|
||||
|
||||
#define LABELS_LENGTH 100 // Maximum length of the label string
|
||||
#define LABEL_LENGTH 20 // Maximum length of a single label
|
||||
#define LABEL_LENGTH 16
|
||||
|
||||
#if defined(PCBHORUS) || defined(PCBNV14)
|
||||
#define MAX_MODELS 60
|
||||
|
||||
@@ -383,7 +383,6 @@ void ModelsPageBody::selectModel(ModelCell *model)
|
||||
storageCheck(true);
|
||||
|
||||
modelslist.setCurrentModel(model);
|
||||
checkAll();
|
||||
|
||||
// Exit to main view
|
||||
auto w = Layer::back();
|
||||
@@ -496,8 +495,8 @@ class LabelDialog : public Dialog
|
||||
Dialog(parent, STR_ENTER_LABEL, rect_t{}),
|
||||
saveHandler(std::move(_saveHandler))
|
||||
{
|
||||
strncpy(this->label, label, MAX_LABEL_SIZE);
|
||||
this->label[MAX_LABEL_SIZE] = '\0';
|
||||
strncpy(this->label, label, LABEL_LENGTH);
|
||||
this->label[LABEL_LENGTH] = '\0';
|
||||
|
||||
auto form = &content->form;
|
||||
form->padRow(lv_dpx(8));
|
||||
@@ -505,7 +504,7 @@ class LabelDialog : public Dialog
|
||||
auto form_obj = form->getLvObj();
|
||||
lv_obj_set_style_flex_cross_place(form_obj, LV_FLEX_ALIGN_CENTER, 0);
|
||||
|
||||
new TextEdit(form, rect_t{}, label, MAX_LABEL_SIZE);
|
||||
new TextEdit(form, rect_t{}, label, LABEL_LENGTH);
|
||||
|
||||
auto box = new FormGroup(form, rect_t{});
|
||||
box->setFlexLayout(LV_FLEX_FLOW_ROW);
|
||||
@@ -538,7 +537,7 @@ class LabelDialog : public Dialog
|
||||
|
||||
protected:
|
||||
std::function<void(std::string label)> saveHandler;
|
||||
char label[MAX_LABEL_SIZE + 1];
|
||||
char label[LABEL_LENGTH + 1];
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
@@ -625,11 +624,9 @@ void ModelLabelsWindow::newModel()
|
||||
// On complete update the current cell's data
|
||||
modelslist.updateCurrentModelCell();
|
||||
|
||||
auto labels = getLabels();
|
||||
lblselector->setNames(labels);
|
||||
lblselector->setSelected(modelslabels.getLabels().size());
|
||||
mdlselector->update(0);
|
||||
setTitle();
|
||||
// Close Window
|
||||
auto w = Layer::back();
|
||||
if (w) w->onCancel();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -778,8 +775,8 @@ void ModelLabelsWindow::buildBody(FormWindow *window)
|
||||
menu->setTitle(selectedLabel);
|
||||
menu->addLine(STR_RENAME_LABEL, [=]() {
|
||||
auto oldLabel = labels[selected];
|
||||
strncpy(tmpLabel, oldLabel.c_str(), MAX_LABEL_SIZE);
|
||||
tmpLabel[MAX_LABEL_SIZE] = '\0';
|
||||
strncpy(tmpLabel, oldLabel.c_str(), LABEL_LENGTH);
|
||||
tmpLabel[LABEL_LENGTH] = '\0';
|
||||
new LabelDialog(this, tmpLabel, [=](std::string newLabel) {
|
||||
auto rndialog =
|
||||
new ProgressDialog(this, STR_RENAME_LABEL, [=]() {});
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
#include "storage/modelslist.h"
|
||||
#include "tabsgroup.h"
|
||||
|
||||
constexpr int MAX_LABEL_SIZE = 20;
|
||||
|
||||
class ModelButton;
|
||||
|
||||
class ModelsPageBody : public FormWindow
|
||||
@@ -78,7 +76,7 @@ class ModelLabelsWindow : public Page
|
||||
|
||||
protected:
|
||||
ModelsSortBy sort = DEFAULT_MODEL_SORT;
|
||||
char tmpLabel[MAX_LABEL_SIZE + 1] = "\0";
|
||||
char tmpLabel[LABEL_LENGTH + 1] = "\0";
|
||||
ListBox *lblselector;
|
||||
ModelsPageBody *mdlselector;
|
||||
std::string currentLabel;
|
||||
|
||||
@@ -127,7 +127,7 @@ void SubScreenButton::event_cb(lv_event_t* e)
|
||||
auto btn = (SubScreenButton*)lv_obj_get_user_data(obj);
|
||||
if (!btn) return;
|
||||
|
||||
lv_event_code_t code = lv_event_get_code(e);
|
||||
lv_event_code_t code = lv_event_get_code(e);
|
||||
if (code == LV_EVENT_DRAW_PART_BEGIN) {
|
||||
|
||||
lv_obj_draw_part_dsc_t* dsc = lv_event_get_draw_part_dsc(e);
|
||||
@@ -142,7 +142,7 @@ void SubScreenButton::event_cb(lv_event_t* e)
|
||||
} else if (code == LV_EVENT_DRAW_PART_END) {
|
||||
|
||||
if (btn->text.empty()) return;
|
||||
|
||||
|
||||
lv_obj_draw_part_dsc_t* dsc = lv_event_get_draw_part_dsc(e);
|
||||
if (dsc->part != LV_PART_MAIN) return;
|
||||
|
||||
@@ -243,7 +243,7 @@ void ModelSetupPage::build(FormWindow * window)
|
||||
new StaticText(line, rect_t{}, STR_LABELS, 0, COLOR_THEME_PRIMARY1);
|
||||
auto curmod = modelslist.getCurrentModel();
|
||||
labelTextButton =
|
||||
new TextButton(line, rect_t{}, modelslabels.getLabelString(curmod ,STR_UNLABELEDMODEL), [=] () {
|
||||
new TextButton(line, rect_t{}, modelslabels.getBulletLabelString(curmod ,STR_UNLABELEDMODEL), [=] () {
|
||||
Menu *menu = new Menu(window, true);
|
||||
menu->setTitle(STR_LABELS);
|
||||
for (auto &label: modelslabels.getLabels()) {
|
||||
@@ -253,8 +253,9 @@ void ModelSetupPage::build(FormWindow * window)
|
||||
modelslabels.addLabelToModel(label, curmod);
|
||||
else
|
||||
modelslabels.removeLabelFromModel(label, curmod);
|
||||
labelTextButton->setText(modelslabels.getLabelString(curmod,STR_UNLABELEDMODEL));
|
||||
strcpy(g_model.header.labels, modelslabels.getLabelString(curmod,STR_UNLABELEDMODEL).c_str());
|
||||
labelTextButton->setText(modelslabels.getBulletLabelString(curmod,STR_UNLABELEDMODEL));
|
||||
strncpy(g_model.header.labels, ModelMap::toCSV(modelslabels.getLabelsByModel(curmod)).c_str(),sizeof(g_model.header.labels));
|
||||
g_model.header.labels[sizeof(g_model.header.labels)-1] = '\0';
|
||||
SET_DIRTY();
|
||||
}, [=] () {
|
||||
return modelslabels.isLabelSelected(label, curmod);
|
||||
@@ -285,7 +286,7 @@ void ModelSetupPage::build(FormWindow * window)
|
||||
form->setFlexLayout(LV_FLEX_FLOW_ROW_WRAP, lv_dpx(8));
|
||||
lv_obj_set_style_flex_main_place(form->getLvObj(), LV_FLEX_ALIGN_SPACE_EVENLY, 0);
|
||||
form->padAll(lv_dpx(8));
|
||||
|
||||
|
||||
Window* btn = new IntmoduleButton(form);
|
||||
lv_obj_set_style_min_width(btn->getLvObj(), LV_DPI_DEF, 0);
|
||||
|
||||
@@ -342,11 +343,11 @@ TimerBtnMatrix::TimerBtnMatrix(Window* parent, const rect_t& r) :
|
||||
setText(1, TR_TIMER "2");
|
||||
setText(2, TR_TIMER "3");
|
||||
update();
|
||||
|
||||
|
||||
lv_btnmatrix_set_btn_width(lvobj, 3, 2);
|
||||
lv_obj_set_width(lvobj, lv_pct(100));
|
||||
lv_obj_set_height(lvobj, LV_DPI_DEF / 2);
|
||||
|
||||
|
||||
lv_obj_set_style_bg_opa(lvobj, LV_OPA_0, 0);
|
||||
lv_obj_set_style_pad_all(lvobj, lv_dpx(8), 0);
|
||||
|
||||
|
||||
@@ -330,10 +330,14 @@ LabelsVector ModelMap::getLabels()
|
||||
|
||||
int ModelMap::addLabel(std::string lbl)
|
||||
{
|
||||
if (lbl.size() == 0) return -1;
|
||||
if (lbl == STR_UNLABELEDMODEL) return -1;
|
||||
|
||||
// Add a new label if if doesn't already exist in the list
|
||||
// Limit maximum label length, TODO... Truncate UTF8 Properly
|
||||
lbl = lbl.substr(0, LABEL_LENGTH);
|
||||
removeYAMLChars(lbl);
|
||||
if (lbl.size() == 0) return -1;
|
||||
|
||||
// Add a new label if it doesn't already exist in the list
|
||||
// Returns the index to the label
|
||||
int ind = getIndexByLabel(lbl);
|
||||
if (ind < 0) {
|
||||
@@ -419,8 +423,7 @@ std::string ModelMap::toCSV(const LabelsVector &labels)
|
||||
csv += ',';
|
||||
}
|
||||
// Escape out commas
|
||||
replace_all(lbl, "/", "//");
|
||||
replace_all(lbl, ",", "/c");
|
||||
escapeCSV(lbl);
|
||||
csv += lbl;
|
||||
comma = true;
|
||||
}
|
||||
@@ -440,14 +443,36 @@ LabelsVector ModelMap::fromCSV(const char *str)
|
||||
std::istringstream f(str);
|
||||
std::string lbl;
|
||||
while (std::getline(f, lbl, ',')) {
|
||||
// Un-escape commas
|
||||
replace_all(lbl, "/c", ",");
|
||||
replace_all(lbl, "//", "/");
|
||||
unEscapeCSV(lbl);
|
||||
lbls.push_back(lbl);
|
||||
}
|
||||
return lbls;
|
||||
}
|
||||
|
||||
// TODO - Fix me, ideally there should be no limitations
|
||||
void ModelMap::escapeCSV(std::string &str)
|
||||
{
|
||||
replace_all(str, "/", "//");
|
||||
replace_all(str, ",", "/c");
|
||||
}
|
||||
|
||||
// TODO - Fix me, ideally there should be no limitations
|
||||
void ModelMap::unEscapeCSV(std::string &str)
|
||||
{
|
||||
replace_all(str, "//", "/");
|
||||
replace_all(str, "/c", ",");
|
||||
}
|
||||
|
||||
// TODO - Fix me, ideally there should be no limitations
|
||||
void ModelMap::removeYAMLChars(std::string &str)
|
||||
{
|
||||
replace_all(str, "\\", "");
|
||||
replace_all(str, "\"", "");
|
||||
replace_all(str, ":", "");
|
||||
replace_all(str, "\'", "");
|
||||
replace_all(str, "-", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Replace all occurances of <from> to <to> in <str>
|
||||
*
|
||||
@@ -585,13 +610,17 @@ bool ModelMap::moveLabelTo(unsigned curind, unsigned newind)
|
||||
* @return false success
|
||||
*/
|
||||
|
||||
bool ModelMap::renameLabel(
|
||||
const std::string &from, const std::string &to,
|
||||
bool ModelMap::renameLabel(const std::string &from, std::string to,
|
||||
std::function<void(const char *file, int progress)> progress)
|
||||
{
|
||||
if (from == "") return true;
|
||||
DEBUG_TIMER_START(debugTimerYamlScan);
|
||||
|
||||
if (from == "") return true;
|
||||
// Limit max label name. TODO: Warn user they entered too long of a string
|
||||
to = to.substr(0, LABEL_LENGTH);
|
||||
removeYAMLChars(to);
|
||||
if(to.size() == 0) return true;
|
||||
|
||||
|
||||
ModelData *modeldata = (ModelData *)malloc(sizeof(ModelData));
|
||||
if (!modeldata) {
|
||||
@@ -604,6 +633,23 @@ bool ModelMap::renameLabel(
|
||||
|
||||
bool fault = false;
|
||||
ModelsVector mods = getModelsByLabel(from); // Find all models to be renamed
|
||||
|
||||
// Scan all these models first, recombine their labels to a csv,
|
||||
// make sure re-size is going to fit before starting. Otherwise new partial
|
||||
// labels would be created on next scan.
|
||||
for(const auto &model: mods) {
|
||||
int curlen = toCSV(getLabelsByModel(model)).size();
|
||||
std::string csvto = to;
|
||||
escapeCSV(csvto);
|
||||
std::string csvfrom = from;
|
||||
escapeCSV(csvfrom);
|
||||
if(curlen + csvto.size() - csvfrom.size() > LABELS_LENGTH - 1) {
|
||||
TRACE("Labels: Rename Error! Labels too long on %s", model->modelName);
|
||||
if (progress != nullptr) progress("", 100); // Kill progress dialog
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
for (const auto &modcell : mods) {
|
||||
if (progress != nullptr) {
|
||||
@@ -613,25 +659,9 @@ bool ModelMap::renameLabel(
|
||||
readModelYaml(modcell->modelFilename, (uint8_t *)modeldata,
|
||||
sizeof(ModelData));
|
||||
|
||||
// Make sure there is room to rename, use toCSV to capture escape chars
|
||||
LabelsVector tmpvect;
|
||||
tmpvect.push_back(to);
|
||||
int newlen = ModelMap::toCSV(tmpvect).size();
|
||||
tmpvect.clear();
|
||||
tmpvect.push_back(from);
|
||||
int oldlen = ModelMap::toCSV(tmpvect).size();
|
||||
|
||||
// Separate Curent CSV
|
||||
LabelsVector lbls = ModelMap::fromCSV(modeldata->header.labels);
|
||||
|
||||
int nlen = lbls.size() + newlen - oldlen;
|
||||
if (nlen > LABELS_LENGTH - 1) {
|
||||
fault = true;
|
||||
TRACE("Labels: Rename Error! Labels too long on %s - %s",
|
||||
modeldata->header.name, modcell->modelFilename);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace from->to strings
|
||||
for (auto &lbl : lbls) {
|
||||
if (lbl == from) lbl = to;
|
||||
@@ -691,19 +721,26 @@ bool ModelMap::renameLabel(
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns a comma separated list of the labels, used in model_setup
|
||||
* @brief Returns a bullet separated list of the labels, used in model_setup
|
||||
*
|
||||
* @param curmod Model Cell
|
||||
* @param noresults String to return when no labels found
|
||||
* @return std::string of all Labels, if no results return
|
||||
*/
|
||||
|
||||
std::string ModelMap::getLabelString(ModelCell *curmod, const char *noresults)
|
||||
std::string ModelMap::getBulletLabelString(ModelCell *curmod, const char *noresults)
|
||||
{
|
||||
std::string lbls = ModelMap::toCSV(getLabelsByModel(curmod));
|
||||
replace_all(lbls, ",", "\u2022");
|
||||
unEscapeCSV(lbls);
|
||||
|
||||
if(lbls.size() == 0) {
|
||||
return noresults;
|
||||
}
|
||||
if(lbls.size() > LABEL_TRUNCATE_LENGTH) {
|
||||
lbls = lbls.substr(0, LABEL_TRUNCATE_LENGTH);
|
||||
lbls += "...";
|
||||
}
|
||||
return lbls;
|
||||
}
|
||||
|
||||
@@ -745,7 +782,7 @@ bool ModelMap::updateModelFile(ModelCell *cell)
|
||||
{
|
||||
// Update memory copy if on current model
|
||||
if (cell == modelslist.getCurrentModel()) {
|
||||
strncpy(g_model.header.labels, getLabelString(cell).c_str(),
|
||||
strncpy(g_model.header.labels, ModelMap::toCSV(getLabelsByModel(cell)).c_str(),
|
||||
LABELS_LENGTH - 1);
|
||||
g_model.header.labels[LABELS_LENGTH - 1] = '\0';
|
||||
storageDirty(EE_MODEL);
|
||||
@@ -761,7 +798,7 @@ bool ModelMap::updateModelFile(ModelCell *cell)
|
||||
bool fault = false;
|
||||
readModelYaml(cell->modelFilename, (uint8_t *)modeldata, sizeof(ModelData));
|
||||
|
||||
strncpy(modeldata->header.labels, getLabelString(cell).c_str(),
|
||||
strncpy(modeldata->header.labels, ModelMap::toCSV(getLabelsByModel(cell)).c_str(),
|
||||
LABELS_LENGTH - 1);
|
||||
modeldata->header.labels[LABELS_LENGTH - 1] = '\0';
|
||||
|
||||
@@ -1087,6 +1124,9 @@ bool ModelsList::load(Format fmt)
|
||||
TRACE("ERROR no Current Model Found");
|
||||
if (modelslist.size()) {
|
||||
modelslist.setCurrentModel(modelslist.at(0));
|
||||
strncpy(g_eeGeneral.currModelFilename, modelslist.at(0)->modelFilename,
|
||||
sizeof(g_eeGeneral.currModelFilename));
|
||||
g_eeGeneral.currModelFilename[sizeof(g_eeGeneral.currModelFilename) - 1] = '\0';
|
||||
TRACE(" - Set current model to first available");
|
||||
} else {
|
||||
TRACE(" - No Models Found, making a new one");
|
||||
@@ -1127,7 +1167,7 @@ const char *ModelsList::save(LabelsVector newOrder)
|
||||
if(newOrder.empty())
|
||||
newOrder = modelslabels.getLabels();
|
||||
for (auto &lbl : newOrder) {
|
||||
f_printf(&file, " %s:\r\n", lbl.c_str());
|
||||
f_printf(&file, " \"%s\":\r\n", lbl.c_str());
|
||||
if (modelslabels.isLabelFiltered(lbl))
|
||||
f_printf(&file, " selected: true\r\n", lbl.c_str());
|
||||
}
|
||||
|
||||
@@ -48,6 +48,12 @@
|
||||
|
||||
#define DEFAULT_MODEL_SORT NAME_ASC
|
||||
|
||||
#if LCD_W > LCD_H // Landscape
|
||||
#define LABEL_TRUNCATE_LENGTH 21
|
||||
#else
|
||||
#define LABEL_TRUNCATE_LENGTH 16
|
||||
#endif
|
||||
|
||||
struct ModelData;
|
||||
struct ModuleData;
|
||||
|
||||
@@ -134,8 +140,7 @@ class ModelMap : protected std::multimap<uint16_t, ModelCell *>
|
||||
const std::string &,
|
||||
std::function<void(const char *file, int progress)> progress = nullptr);
|
||||
bool moveLabelTo(unsigned current, unsigned newind);
|
||||
bool renameLabel(
|
||||
const std::string &from, const std::string &to,
|
||||
bool renameLabel(const std::string &from, std::string to,
|
||||
std::function<void(const char *file, int progress)> progress = nullptr);
|
||||
std::string getCurrentLabel() { return currentlabel; };
|
||||
void setCurrentLabel(const std::string &lbl)
|
||||
@@ -143,7 +148,7 @@ class ModelMap : protected std::multimap<uint16_t, ModelCell *>
|
||||
currentlabel = lbl;
|
||||
setDirty();
|
||||
}
|
||||
std::string getLabelString(ModelCell *, const char *noresults = "");
|
||||
std::string getBulletLabelString(ModelCell *, const char *noresults = "");
|
||||
void setDirty(bool save = false);
|
||||
bool isDirty() { return _isDirty; }
|
||||
|
||||
@@ -161,6 +166,9 @@ class ModelMap : protected std::multimap<uint16_t, ModelCell *>
|
||||
|
||||
static std::string toCSV(const LabelsVector &labels);
|
||||
static LabelsVector fromCSV(const char *str);
|
||||
static void escapeCSV(std::string &str);
|
||||
static void unEscapeCSV(std::string &str);
|
||||
static void removeYAMLChars(std::string &str);
|
||||
static void replace_all(std::string &str,
|
||||
const std::string &from,
|
||||
const std::string &to);
|
||||
|
||||
@@ -49,8 +49,8 @@ struct labelslist_iter
|
||||
bool modeldatavalid; // Used to determine if reading yaml values is necessary
|
||||
uint8_t level;
|
||||
uint8_t section;
|
||||
char current_attr[LABEL_LENGTH+1]; // set after find_node()
|
||||
char current_label[LABEL_LENGTH+1]; // set after find_node()
|
||||
char current_attr[LABELS_LENGTH+1]; // set after find_node()
|
||||
char current_label[LABELS_LENGTH+1]; // set after find_node()
|
||||
};
|
||||
|
||||
static labelslist_iter __labelslist_iter_inst;
|
||||
@@ -130,9 +130,9 @@ static bool find_node(void* ctx, char* buf, uint8_t len)
|
||||
bool found=false;
|
||||
for(auto &filehash : modelslist.fileHashInfo) {
|
||||
if(filehash.name == mi->current_attr) {
|
||||
TRACE_LABELS_YAML(" Model %s has a real file, creating a modelcell", filehash.name);
|
||||
TRACE_LABELS_YAML(" Model %s has a real file, creating a modelcell", mi->current_attr);
|
||||
if(filehash.celladded) {
|
||||
TRACE_LABELS_YAML(" Duplicate found labels.yml model cell %s already added", filehash.name);
|
||||
TRACE_LABELS_YAML(" Duplicate found labels.yml model cell %s already added", mi->current_attr);
|
||||
break;
|
||||
}
|
||||
ModelCell *model = new ModelCell(mi->current_attr);
|
||||
@@ -158,8 +158,8 @@ static bool find_node(void* ctx, char* buf, uint8_t len)
|
||||
if(mi->level == 1 && mi->section == labelslist_iter::SEC_Labels) {
|
||||
TRACE_LABELS_YAML("Label Found -- %s", mi->current_attr);
|
||||
modelslabels.addLabel(mi->current_attr);
|
||||
strncpy(mi->current_label,mi->current_attr, LABEL_LENGTH);
|
||||
mi->current_label[LABEL_LENGTH] = '\0';
|
||||
strncpy(mi->current_label,mi->current_attr, LABELS_LENGTH);
|
||||
mi->current_label[LABELS_LENGTH] = '\0';
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -167,7 +167,11 @@ static bool find_node(void* ctx, char* buf, uint8_t len)
|
||||
|
||||
static void set_attr(void* ctx, char* buf, uint8_t len)
|
||||
{
|
||||
char value[40];
|
||||
char value[LABELS_LENGTH + 1];
|
||||
if(len > LABELS_LENGTH) {
|
||||
TRACE("ERROR: YAML too long for buffer");
|
||||
return;
|
||||
}
|
||||
memcpy(value, buf, len);
|
||||
value[len] = '\0';
|
||||
|
||||
@@ -256,7 +260,7 @@ static void set_attr(void* ctx, char* buf, uint8_t len)
|
||||
|
||||
// Sort Order
|
||||
} else if (mi->level == 0 && mi->section == labelslist_iter::SEC_Sort) {
|
||||
TRACE_LABELS_YAML(" Sort Order Found -- %s", mi->current_attr);
|
||||
TRACE_LABELS_YAML(" Sort Order Found -- %s", value);
|
||||
modelslabels.setSortOrder((ModelsSortBy)strtol(value,NULL,10));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define MAX_STR 40
|
||||
#define MAX_STR 100
|
||||
#define MAX_DEPTH 16 // 12 real + 4 virtual
|
||||
|
||||
struct YamlParserCalls
|
||||
@@ -78,7 +78,7 @@ class YamlParser
|
||||
// tree iterator state
|
||||
const YamlParserCalls* calls;
|
||||
void* ctx;
|
||||
|
||||
|
||||
// Reset parser state for next line
|
||||
void reset();
|
||||
|
||||
@@ -97,7 +97,7 @@ public:
|
||||
YamlParser();
|
||||
|
||||
void init(const YamlParserCalls* parser_calls, void* parser_ctx);
|
||||
|
||||
|
||||
YamlResult parse(const char* buffer, unsigned int size);
|
||||
|
||||
void set_eof() { eof = true; }
|
||||
|
||||
Reference in New Issue
Block a user