VariableModel.cpp
396 lines
| 12.1 KiB
| text/x-c
|
CppLexer
Alexandre Leroux
|
r163 | #include <Variable/Variable.h> | ||
r849 | #include <Variable/VariableController.h> | |||
Alexandre Leroux
|
r112 | #include <Variable/VariableModel.h> | ||
Alexandre Leroux
|
r488 | #include <Common/DateUtils.h> | ||
r849 | #include <Common/MimeTypesDef.h> | |||
Alexandre Leroux
|
r709 | #include <Common/StringUtils.h> | ||
Alexandre Leroux
|
r488 | |||
Alexandre Leroux
|
r165 | #include <Data/IDataSeries.h> | ||
Alexandre Leroux
|
r112 | |||
r878 | #include <DataSource/DataSourceController.h> | |||
#include <Time/TimeController.h> | ||||
r849 | #include <QMimeData> | |||
Alexandre Leroux
|
r277 | #include <QSize> | ||
r933 | #include <QTimer> | |||
r401 | #include <unordered_map> | |||
Alexandre Leroux
|
r274 | |||
Alexandre Leroux
|
r112 | Q_LOGGING_CATEGORY(LOG_VariableModel, "VariableModel") | ||
Alexandre Leroux
|
r149 | namespace { | ||
Alexandre Leroux
|
r150 | // Column indexes | ||
const auto NAME_COLUMN = 0; | ||||
Alexandre Leroux
|
r274 | const auto TSTART_COLUMN = 1; | ||
const auto TEND_COLUMN = 2; | ||||
Alexandre Leroux
|
r719 | const auto NBPOINTS_COLUMN = 3; | ||
const auto UNIT_COLUMN = 4; | ||||
const auto MISSION_COLUMN = 5; | ||||
const auto PLUGIN_COLUMN = 6; | ||||
const auto NB_COLUMNS = 7; | ||||
Alexandre Leroux
|
r149 | |||
Alexandre Leroux
|
r275 | // Column properties | ||
Alexandre Leroux
|
r276 | const auto DEFAULT_HEIGHT = 25; | ||
const auto DEFAULT_WIDTH = 100; | ||||
Alexandre Leroux
|
r275 | |||
struct ColumnProperties { | ||||
Alexandre Leroux
|
r276 | ColumnProperties(const QString &name = {}, int width = DEFAULT_WIDTH, | ||
int height = DEFAULT_HEIGHT) | ||||
: m_Name{name}, m_Width{width}, m_Height{height} | ||||
{ | ||||
} | ||||
Alexandre Leroux
|
r275 | |||
QString m_Name; | ||||
Alexandre Leroux
|
r276 | int m_Width; | ||
int m_Height; | ||||
Alexandre Leroux
|
r275 | }; | ||
Alexandre Leroux
|
r550 | const auto COLUMN_PROPERTIES = QHash<int, ColumnProperties>{ | ||
Alexandre Leroux
|
r719 | {NAME_COLUMN, {QObject::tr("Name")}}, {TSTART_COLUMN, {QObject::tr("tStart"), 180}}, | ||
{TEND_COLUMN, {QObject::tr("tEnd"), 180}}, {NBPOINTS_COLUMN, {QObject::tr("Nb points")}}, | ||||
{UNIT_COLUMN, {QObject::tr("Unit")}}, {MISSION_COLUMN, {QObject::tr("Mission")}}, | ||||
{PLUGIN_COLUMN, {QObject::tr("Plugin")}}}; | ||||
Alexandre Leroux
|
r275 | |||
Alexandre Leroux
|
r274 | /// Format for datetimes | ||
const auto DATETIME_FORMAT = QStringLiteral("dd/MM/yyyy \nhh:mm:ss:zzz"); | ||||
Alexandre Leroux
|
r709 | QString uniqueName(const QString &defaultName, | ||
const std::vector<std::shared_ptr<Variable> > &variables) | ||||
{ | ||||
auto forbiddenNames = std::vector<QString>(variables.size()); | ||||
std::transform(variables.cbegin(), variables.cend(), forbiddenNames.begin(), | ||||
[](const auto &variable) { return variable->name(); }); | ||||
auto uniqueName = StringUtils::uniqueName(defaultName, forbiddenNames); | ||||
Q_ASSERT(!uniqueName.isEmpty()); | ||||
return uniqueName; | ||||
} | ||||
r401 | ||||
Alexandre Leroux
|
r149 | } // namespace | ||
Alexandre Leroux
|
r112 | struct VariableModel::VariableModelPrivate { | ||
/// Variables created in SciQlop | ||||
Alexandre Leroux
|
r165 | std::vector<std::shared_ptr<Variable> > m_Variables; | ||
Alexandre Leroux
|
r421 | std::unordered_map<std::shared_ptr<Variable>, double> m_VariableToProgress; | ||
r849 | VariableController *m_VariableController; | |||
r401 | ||||
r402 | /// Return the row index of the variable. -1 if it's not found | |||
r401 | int indexOfVariable(Variable *variable) const noexcept; | |||
Alexandre Leroux
|
r112 | }; | ||
r849 | VariableModel::VariableModel(VariableController *parent) | |||
Alexandre Leroux
|
r149 | : QAbstractTableModel{parent}, impl{spimpl::make_unique_impl<VariableModelPrivate>()} | ||
Alexandre Leroux
|
r112 | { | ||
r849 | impl->m_VariableController = parent; | |||
Alexandre Leroux
|
r112 | } | ||
Alexandre Leroux
|
r708 | void VariableModel::addVariable(std::shared_ptr<Variable> variable) noexcept | ||
Alexandre Leroux
|
r112 | { | ||
Alexandre Leroux
|
r153 | auto insertIndex = rowCount(); | ||
beginInsertRows({}, insertIndex, insertIndex); | ||||
Alexandre Leroux
|
r709 | // Generates unique name for the variable | ||
variable->setName(uniqueName(variable->name(), impl->m_Variables)); | ||||
Alexandre Leroux
|
r165 | impl->m_Variables.push_back(variable); | ||
Alexandre Leroux
|
r368 | connect(variable.get(), &Variable::updated, this, &VariableModel::onVariableUpdated); | ||
Alexandre Leroux
|
r112 | |||
Alexandre Leroux
|
r153 | endInsertRows(); | ||
Alexandre Leroux
|
r708 | } | ||
Alexandre Leroux
|
r710 | bool VariableModel::containsVariable(std::shared_ptr<Variable> variable) const noexcept | ||
{ | ||||
auto end = impl->m_Variables.cend(); | ||||
return std::find(impl->m_Variables.cbegin(), end, variable) != end; | ||||
} | ||||
Alexandre Leroux
|
r708 | std::shared_ptr<Variable> VariableModel::createVariable(const QString &name, | ||
const QVariantHash &metadata) noexcept | ||||
{ | ||||
r756 | auto variable = std::make_shared<Variable>(name, metadata); | |||
Alexandre Leroux
|
r708 | addVariable(variable); | ||
Alexandre Leroux
|
r153 | |||
Alexandre Leroux
|
r165 | return variable; | ||
Alexandre Leroux
|
r112 | } | ||
Alexandre Leroux
|
r149 | |||
Alexandre Leroux
|
r332 | void VariableModel::deleteVariable(std::shared_ptr<Variable> variable) noexcept | ||
{ | ||||
if (!variable) { | ||||
qCCritical(LOG_Variable()) << "Can't delete a null variable from the model"; | ||||
return; | ||||
} | ||||
// Finds variable in the model | ||||
auto begin = impl->m_Variables.cbegin(); | ||||
auto end = impl->m_Variables.cend(); | ||||
auto it = std::find(begin, end, variable); | ||||
if (it != end) { | ||||
auto removeIndex = std::distance(begin, it); | ||||
// Deletes variable | ||||
beginRemoveRows({}, removeIndex, removeIndex); | ||||
impl->m_Variables.erase(it); | ||||
endRemoveRows(); | ||||
} | ||||
else { | ||||
qCritical(LOG_VariableModel()) | ||||
<< tr("Can't delete variable %1 from the model: the variable is not in the model") | ||||
.arg(variable->name()); | ||||
} | ||||
Alexandre Leroux
|
r421 | |||
// Removes variable from progress map | ||||
impl->m_VariableToProgress.erase(variable); | ||||
Alexandre Leroux
|
r332 | } | ||
r401 | ||||
Alexandre Leroux
|
r246 | std::shared_ptr<Variable> VariableModel::variable(int index) const | ||
{ | ||||
Alexandre Leroux
|
r961 | return (index >= 0u && static_cast<size_t>(index) < impl->m_Variables.size()) | ||
? impl->m_Variables[index] | ||||
: nullptr; | ||||
Alexandre Leroux
|
r246 | } | ||
Alexandre Leroux
|
r684 | std::vector<std::shared_ptr<Variable> > VariableModel::variables() const | ||
{ | ||||
return impl->m_Variables; | ||||
} | ||||
r401 | void VariableModel::setDataProgress(std::shared_ptr<Variable> variable, double progress) | |||
{ | ||||
r432 | if (progress > 0.0) { | |||
impl->m_VariableToProgress[variable] = progress; | ||||
} | ||||
else { | ||||
impl->m_VariableToProgress.erase(variable); | ||||
} | ||||
r401 | auto modelIndex = createIndex(impl->indexOfVariable(variable.get()), NAME_COLUMN); | |||
emit dataChanged(modelIndex, modelIndex); | ||||
} | ||||
Alexandre Leroux
|
r149 | int VariableModel::columnCount(const QModelIndex &parent) const | ||
{ | ||||
Q_UNUSED(parent); | ||||
return NB_COLUMNS; | ||||
} | ||||
int VariableModel::rowCount(const QModelIndex &parent) const | ||||
{ | ||||
Q_UNUSED(parent); | ||||
return impl->m_Variables.size(); | ||||
} | ||||
QVariant VariableModel::data(const QModelIndex &index, int role) const | ||||
{ | ||||
Alexandre Leroux
|
r151 | if (!index.isValid()) { | ||
return QVariant{}; | ||||
} | ||||
if (index.row() < 0 || index.row() >= rowCount()) { | ||||
return QVariant{}; | ||||
} | ||||
if (role == Qt::DisplayRole) { | ||||
Alexandre Leroux
|
r157 | if (auto variable = impl->m_Variables.at(index.row()).get()) { | ||
Alexandre Leroux
|
r151 | switch (index.column()) { | ||
case NAME_COLUMN: | ||||
Alexandre Leroux
|
r163 | return variable->name(); | ||
Alexandre Leroux
|
r654 | case TSTART_COLUMN: { | ||
auto range = variable->realRange(); | ||||
return range != INVALID_RANGE | ||||
? DateUtils::dateTime(range.m_TStart).toString(DATETIME_FORMAT) | ||||
: QVariant{}; | ||||
} | ||||
case TEND_COLUMN: { | ||||
auto range = variable->realRange(); | ||||
return range != INVALID_RANGE | ||||
? DateUtils::dateTime(range.m_TEnd).toString(DATETIME_FORMAT) | ||||
: QVariant{}; | ||||
} | ||||
Alexandre Leroux
|
r719 | case NBPOINTS_COLUMN: | ||
return variable->nbPoints(); | ||||
Alexandre Leroux
|
r550 | case UNIT_COLUMN: | ||
return variable->metadata().value(QStringLiteral("units")); | ||||
case MISSION_COLUMN: | ||||
return variable->metadata().value(QStringLiteral("mission")); | ||||
Alexandre Leroux
|
r551 | case PLUGIN_COLUMN: | ||
return variable->metadata().value(QStringLiteral("plugin")); | ||||
Alexandre Leroux
|
r151 | default: | ||
// No action | ||||
break; | ||||
} | ||||
qWarning(LOG_VariableModel()) | ||||
<< tr("Can't get data (unknown column %1)").arg(index.column()); | ||||
} | ||||
else { | ||||
qWarning(LOG_VariableModel()) << tr("Can't get data (no variable)"); | ||||
} | ||||
} | ||||
r402 | else if (role == VariableRoles::ProgressRole) { | |||
r401 | if (auto variable = impl->m_Variables.at(index.row())) { | |||
Alexandre Leroux
|
r421 | auto it = impl->m_VariableToProgress.find(variable); | ||
r401 | if (it != impl->m_VariableToProgress.cend()) { | |||
return it->second; | ||||
} | ||||
} | ||||
} | ||||
Alexandre Leroux
|
r151 | |||
Alexandre Leroux
|
r149 | return QVariant{}; | ||
} | ||||
QVariant VariableModel::headerData(int section, Qt::Orientation orientation, int role) const | ||||
{ | ||||
Alexandre Leroux
|
r277 | if (role != Qt::DisplayRole && role != Qt::SizeHintRole) { | ||
Alexandre Leroux
|
r150 | return QVariant{}; | ||
} | ||||
if (orientation == Qt::Horizontal) { | ||||
Alexandre Leroux
|
r275 | auto propertiesIt = COLUMN_PROPERTIES.find(section); | ||
if (propertiesIt != COLUMN_PROPERTIES.cend()) { | ||||
// Role is either DisplayRole or SizeHintRole | ||||
Alexandre Leroux
|
r277 | return (role == Qt::DisplayRole) | ||
? QVariant{propertiesIt->m_Name} | ||||
: QVariant{QSize{propertiesIt->m_Width, propertiesIt->m_Height}}; | ||||
Alexandre Leroux
|
r275 | } | ||
else { | ||||
qWarning(LOG_VariableModel()) | ||||
<< tr("Can't get header data (unknown column %1)").arg(section); | ||||
Alexandre Leroux
|
r150 | } | ||
} | ||||
Alexandre Leroux
|
r149 | |||
return QVariant{}; | ||||
} | ||||
Alexandre Leroux
|
r368 | |||
r849 | Qt::ItemFlags VariableModel::flags(const QModelIndex &index) const | |||
{ | ||||
return QAbstractTableModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; | ||||
} | ||||
Qt::DropActions VariableModel::supportedDropActions() const | ||||
{ | ||||
r870 | return Qt::CopyAction | Qt::MoveAction; | |||
r849 | } | |||
Qt::DropActions VariableModel::supportedDragActions() const | ||||
{ | ||||
return Qt::CopyAction | Qt::MoveAction; | ||||
} | ||||
QStringList VariableModel::mimeTypes() const | ||||
{ | ||||
r878 | return {MIME_TYPE_VARIABLE_LIST, MIME_TYPE_TIME_RANGE}; | |||
r849 | } | |||
QMimeData *VariableModel::mimeData(const QModelIndexList &indexes) const | ||||
{ | ||||
auto mimeData = new QMimeData; | ||||
QList<std::shared_ptr<Variable> > variableList; | ||||
r878 | ||||
SqpRange firstTimeRange; | ||||
r849 | for (const auto &index : indexes) { | |||
r850 | if (index.column() == 0) { // only the first column | |||
auto variable = impl->m_Variables.at(index.row()); | ||||
if (variable.get() && index.isValid()) { | ||||
r878 | ||||
if (variableList.isEmpty()) { | ||||
// Gets the range of the first variable | ||||
firstTimeRange = std::move(variable->range()); | ||||
} | ||||
r850 | variableList << variable; | |||
} | ||||
r849 | } | |||
} | ||||
r878 | auto variablesEncodedData = impl->m_VariableController->mimeDataForVariables(variableList); | |||
mimeData->setData(MIME_TYPE_VARIABLE_LIST, variablesEncodedData); | ||||
if (variableList.count() == 1) { | ||||
// No time range MIME data if multiple variables are dragged | ||||
auto timeEncodedData = TimeController::mimeDataForTimeRange(firstTimeRange); | ||||
mimeData->setData(MIME_TYPE_TIME_RANGE, timeEncodedData); | ||||
} | ||||
r849 | ||||
return mimeData; | ||||
} | ||||
bool VariableModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, | ||||
int column, const QModelIndex &parent) const | ||||
{ | ||||
r870 | // drop of a product | |||
r933 | return data->hasFormat(MIME_TYPE_PRODUCT_LIST) | |||
|| (data->hasFormat(MIME_TYPE_TIME_RANGE) && parent.isValid() | ||||
&& !data->hasFormat(MIME_TYPE_VARIABLE_LIST)); | ||||
r849 | } | |||
bool VariableModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, | ||||
const QModelIndex &parent) | ||||
{ | ||||
r874 | auto dropDone = false; | |||
r870 | ||||
if (data->hasFormat(MIME_TYPE_PRODUCT_LIST)) { | ||||
r878 | auto productList | |||
= DataSourceController::productsDataForMimeData(data->data(MIME_TYPE_PRODUCT_LIST)); | ||||
r870 | ||||
for (auto metaData : productList) { | ||||
emit requestVariable(metaData.toHash()); | ||||
} | ||||
dropDone = true; | ||||
} | ||||
r933 | else if (data->hasFormat(MIME_TYPE_TIME_RANGE) && parent.isValid()) { | |||
auto variable = this->variable(parent.row()); | ||||
auto range = TimeController::timeRangeForMimeData(data->data(MIME_TYPE_TIME_RANGE)); | ||||
emit requestVariableRangeUpdate(variable, range); | ||||
dropDone = true; | ||||
} | ||||
r870 | ||||
return dropDone; | ||||
r849 | } | |||
r422 | void VariableModel::abortProgress(const QModelIndex &index) | |||
{ | ||||
if (auto variable = impl->m_Variables.at(index.row())) { | ||||
emit abortProgessRequested(variable); | ||||
} | ||||
} | ||||
Alexandre Leroux
|
r368 | void VariableModel::onVariableUpdated() noexcept | ||
{ | ||||
// Finds variable that has been updated in the model | ||||
if (auto updatedVariable = dynamic_cast<Variable *>(sender())) { | ||||
r401 | auto updatedVariableIndex = impl->indexOfVariable(updatedVariable); | |||
if (updatedVariableIndex > -1) { | ||||
emit dataChanged(createIndex(updatedVariableIndex, 0), | ||||
createIndex(updatedVariableIndex, columnCount() - 1)); | ||||
Alexandre Leroux
|
r368 | } | ||
} | ||||
} | ||||
r401 | ||||
int VariableModel::VariableModelPrivate::indexOfVariable(Variable *variable) const noexcept | ||||
{ | ||||
auto begin = std::cbegin(m_Variables); | ||||
auto end = std::cend(m_Variables); | ||||
auto it | ||||
= std::find_if(begin, end, [variable](const auto &var) { return var.get() == variable; }); | ||||
if (it != end) { | ||||
// Gets the index of the variable in the model: we assume here that views have the same | ||||
// order as the model | ||||
return std::distance(begin, it); | ||||
} | ||||
else { | ||||
return -1; | ||||
} | ||||
} | ||||