diff --git a/gui/include/Catalogue/CatalogueEventsModel.h b/gui/include/Catalogue/CatalogueEventsModel.h index bc0bd70..e51ee47 100644 --- a/gui/include/Catalogue/CatalogueEventsModel.h +++ b/gui/include/Catalogue/CatalogueEventsModel.h @@ -3,17 +3,29 @@ #include #include +#include +#include class DBEvent; class DBEventProduct; +Q_DECLARE_LOGGING_CATEGORY(LOG_CatalogueEventsModel) + class CatalogueEventsModel : public QAbstractItemModel { + Q_OBJECT + +signals: + void modelSorted(); + public: CatalogueEventsModel(QObject *parent = nullptr); + enum class Column { Name, TStart, TEnd, Tags, Product, Validation, NbColumn }; + void setEvents(const QVector > &events); void addEvent(const std::shared_ptr &event); void removeEvent(const std::shared_ptr &event); + QVector > events() const; enum class ItemType { Root, Event, EventProduct }; ItemType itemTypeOf(const QModelIndex &index) const; @@ -21,8 +33,18 @@ public: std::shared_ptr getParentEvent(const QModelIndex &index) const; std::shared_ptr getEventProduct(const QModelIndex &index) const; + /// Refresh the data for the specified event void refreshEvent(const std::shared_ptr &event); + /// Returns a QModelIndex which represent the specified event + QModelIndex indexOf(const std::shared_ptr &event) const; + + /// Marks a change flag on the specified event to allow sorting on the validation column + void setEventHasChanges(const std::shared_ptr &event, bool hasChanges); + + /// Returns true if the specified event has unsaved changes + bool eventsHasChanges(const std::shared_ptr &event) const; + // Model QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; QModelIndex parent(const QModelIndex &index) const; diff --git a/gui/include/Catalogue/CatalogueExplorerHelper.h b/gui/include/Catalogue/CatalogueExplorerHelper.h new file mode 100644 index 0000000..037901c --- /dev/null +++ b/gui/include/Catalogue/CatalogueExplorerHelper.h @@ -0,0 +1,13 @@ +#ifndef SCIQLOP_CATALOGUEEXPLORERHELPER_H +#define SCIQLOP_CATALOGUEEXPLORERHELPER_H + +#include + +#include + +struct CatalogueExplorerHelper { + static QWidget *buildValidationWidget(QWidget *parent, std::function save, + std::function discard); +}; + +#endif // SCIQLOP_CATALOGUEEXPLORERHELPER_H diff --git a/gui/include/Catalogue/CatalogueTreeWidgetItem.h b/gui/include/Catalogue/CatalogueTreeWidgetItem.h index a8b4f14..f9b0d65 100644 --- a/gui/include/Catalogue/CatalogueTreeWidgetItem.h +++ b/gui/include/Catalogue/CatalogueTreeWidgetItem.h @@ -22,6 +22,9 @@ public: /// changes void setHasChanges(bool value); + /// Returns true if the widget indicating the event has unsaved changes is displayed + bool hasChanges(); + /// Refreshes the data displayed by the item from the catalogue void refresh(); diff --git a/gui/meson.build b/gui/meson.build index 7899701..7f85566 100644 --- a/gui/meson.build +++ b/gui/meson.build @@ -25,7 +25,8 @@ gui_moc_headers = [ 'include/Catalogue/CatalogueExplorer.h', 'include/Catalogue/CatalogueEventsWidget.h', 'include/Catalogue/CatalogueSideBarWidget.h', - 'include/Catalogue/CatalogueInspectorWidget.h' + 'include/Catalogue/CatalogueInspectorWidget.h', + 'include/Catalogue/CatalogueEventsModel.h' ] gui_ui_files = [ @@ -116,7 +117,8 @@ gui_sources = [ 'src/Catalogue/CatalogueSideBarWidget.cpp', 'src/Catalogue/CatalogueInspectorWidget.cpp', 'src/Catalogue/CatalogueTreeWidgetItem.cpp', - 'src/Catalogue/CatalogueEventsModel.cpp' + 'src/Catalogue/CatalogueEventsModel.cpp', + 'src/Catalogue/CatalogueExplorerHelper.cpp' ] gui_inc = include_directories(['include']) diff --git a/gui/src/Catalogue/CatalogueEventsModel.cpp b/gui/src/Catalogue/CatalogueEventsModel.cpp index d82e3a5..de66396 100644 --- a/gui/src/Catalogue/CatalogueEventsModel.cpp +++ b/gui/src/Catalogue/CatalogueEventsModel.cpp @@ -15,33 +15,46 @@ #include #include +Q_LOGGING_CATEGORY(LOG_CatalogueEventsModel, "CatalogueEventsModel") + const auto EVENT_ITEM_TYPE = 1; const auto EVENT_PRODUCT_ITEM_TYPE = 2; struct CatalogueEventsModel::CatalogueEventsModelPrivate { QVector > m_Events; std::unordered_map > > m_EventProducts; + std::unordered_set > m_EventsWithChanges; - enum class Column { Name, TStart, TEnd, Tags, Product, NbColumn }; QStringList columnNames() { - return QStringList{tr("Event"), tr("TStart"), tr("TEnd"), tr("Tags"), tr("Product")}; + return QStringList{tr("Event"), tr("TStart"), tr("TEnd"), + tr("Tags"), tr("Product"), tr("")}; + } + + QVariant sortData(int col, const std::shared_ptr &event) const + { + if (col == (int)CatalogueEventsModel::Column::Validation) { + return m_EventsWithChanges.find(event) != m_EventsWithChanges.cend() ? true + : QVariant(); + } + + return eventData(col, event); } QVariant eventData(int col, const std::shared_ptr &event) const { switch (static_cast(col)) { - case Column::Name: + case CatalogueEventsModel::Column::Name: return event->getName(); - case Column::TStart: + case CatalogueEventsModel::Column::TStart: return nbEventProducts(event) > 0 ? DateUtils::dateTime(event->getTStart()) : QVariant{}; - case Column::TEnd: + case CatalogueEventsModel::Column::TEnd: return nbEventProducts(event) > 0 ? DateUtils::dateTime(event->getTEnd()) : QVariant{}; - case Column::Product: + case CatalogueEventsModel::Column::Product: return QString::number(nbEventProducts(event)) + " product(s)"; - case Column::Tags: { + case CatalogueEventsModel::Column::Tags: { QString tagList; auto tags = event->getTags(); for (auto tag : tags) { @@ -51,6 +64,8 @@ struct CatalogueEventsModel::CatalogueEventsModelPrivate { return tagList; } + case CatalogueEventsModel::Column::Validation: + return QVariant(); default: break; } @@ -80,17 +95,18 @@ struct CatalogueEventsModel::CatalogueEventsModelPrivate { QVariant eventProductData(int col, const std::shared_ptr &eventProduct) const { switch (static_cast(col)) { - case Column::Name: + case CatalogueEventsModel::Column::Name: return eventProduct->getProductId(); - case Column::TStart: + case CatalogueEventsModel::Column::TStart: return DateUtils::dateTime(eventProduct->getTStart()); - case Column::TEnd: + case CatalogueEventsModel::Column::TEnd: return DateUtils::dateTime(eventProduct->getTEnd()); - case Column::Product: + case CatalogueEventsModel::Column::Product: return eventProduct->getProductId(); - case Column::Tags: { + case CatalogueEventsModel::Column::Tags: return QString(); - } + case CatalogueEventsModel::Column::Validation: + return QVariant(); default: break; } @@ -111,6 +127,7 @@ void CatalogueEventsModel::setEvents(const QVector > &e impl->m_Events = events; impl->m_EventProducts.clear(); + impl->m_EventsWithChanges.clear(); for (auto event : events) { impl->parseEventProduct(event); } @@ -165,21 +182,58 @@ void CatalogueEventsModel::removeEvent(const std::shared_ptr &event) beginRemoveRows(QModelIndex(), index, index); impl->m_Events.removeAt(index); impl->m_EventProducts.erase(event.get()); + impl->m_EventsWithChanges.erase(event); endRemoveRows(); } } +QVector > CatalogueEventsModel::events() const +{ + return impl->m_Events; +} + void CatalogueEventsModel::refreshEvent(const std::shared_ptr &event) { - auto i = impl->m_Events.indexOf(event); - if (i >= 0) { - auto eventIndex = index(i, 0); + auto eventIndex = indexOf(event); + if (eventIndex.isValid()) { + + // Refreshes the event line auto colCount = columnCount(); - emit dataChanged(eventIndex, index(i, colCount)); + emit dataChanged(eventIndex, index(eventIndex.row(), colCount)); + // Also refreshes its children event products auto childCount = rowCount(eventIndex); emit dataChanged(index(0, 0, eventIndex), index(childCount, colCount, eventIndex)); } + else { + qCWarning(LOG_CatalogueEventsModel()) << "refreshEvent: event not found."; + } +} + +QModelIndex CatalogueEventsModel::indexOf(const std::shared_ptr &event) const +{ + auto row = impl->m_Events.indexOf(event); + if (row >= 0) { + return index(row, 0); + } + + return QModelIndex(); +} + +void CatalogueEventsModel::setEventHasChanges(const std::shared_ptr &event, + bool hasChanges) +{ + if (hasChanges) { + impl->m_EventsWithChanges.insert(event); + } + else { + impl->m_EventsWithChanges.erase(event); + } +} + +bool CatalogueEventsModel::eventsHasChanges(const std::shared_ptr &event) const +{ + return impl->m_EventsWithChanges.find(event) != impl->m_EventsWithChanges.cend(); } QModelIndex CatalogueEventsModel::index(int row, int column, const QModelIndex &parent) const @@ -255,7 +309,7 @@ int CatalogueEventsModel::rowCount(const QModelIndex &parent) const int CatalogueEventsModel::columnCount(const QModelIndex &parent) const { - return static_cast(CatalogueEventsModelPrivate::Column::NbColumn); + return static_cast(CatalogueEventsModel::Column::NbColumn); } Qt::ItemFlags CatalogueEventsModel::flags(const QModelIndex &index) const @@ -302,8 +356,8 @@ void CatalogueEventsModel::sort(int column, Qt::SortOrder order) { std::sort(impl->m_Events.begin(), impl->m_Events.end(), [this, column, order](auto e1, auto e2) { - auto data1 = impl->eventData(column, e1); - auto data2 = impl->eventData(column, e2); + auto data1 = impl->sortData(column, e1); + auto data2 = impl->sortData(column, e2); auto result = data1.toString() < data2.toString(); @@ -311,6 +365,7 @@ void CatalogueEventsModel::sort(int column, Qt::SortOrder order) }); emit dataChanged(QModelIndex(), QModelIndex()); + emit modelSorted(); } Qt::DropActions CatalogueEventsModel::supportedDragActions() const diff --git a/gui/src/Catalogue/CatalogueEventsWidget.cpp b/gui/src/Catalogue/CatalogueEventsWidget.cpp index a5bc191..de503c2 100644 --- a/gui/src/Catalogue/CatalogueEventsWidget.cpp +++ b/gui/src/Catalogue/CatalogueEventsWidget.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -16,8 +17,8 @@ Q_LOGGING_CATEGORY(LOG_CatalogueEventsWidget, "CatalogueEventsWidget") -/// Format of the dates appearing in the label of a cursor -const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss"); +/// Fixed size of the validation column +const auto VALIDATION_COLUMN_SIZE = 35; struct CatalogueEventsWidget::CatalogueEventsWidgetPrivate { @@ -277,8 +278,20 @@ CatalogueEventsWidget::CatalogueEventsWidget(QWidget *parent) }); ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents); - ui->treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::Name, + QHeaderView::Stretch); + ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::Validation, + QHeaderView::Fixed); + ui->treeView->header()->resizeSection((int)CatalogueEventsModel::Column::Validation, + VALIDATION_COLUMN_SIZE); ui->treeView->header()->setSortIndicatorShown(true); + + connect(impl->m_Model, &CatalogueEventsModel::modelSorted, [this]() { + auto allEvents = impl->m_Model->events(); + for (auto event : allEvents) { + setEventChanges(event, impl->m_Model->eventsHasChanges(event)); + } + }); } CatalogueEventsWidget::~CatalogueEventsWidget() @@ -294,6 +307,25 @@ void CatalogueEventsWidget::setVisualizationWidget(VisualizationWidget *visualiz void CatalogueEventsWidget::setEventChanges(const std::shared_ptr &event, bool hasChanges) { impl->m_Model->refreshEvent(event); + + auto eventIndex = impl->m_Model->indexOf(event); + auto validationIndex + = eventIndex.sibling(eventIndex.row(), (int)CatalogueEventsModel::Column::Validation); + + if (hasChanges) { + if (ui->treeView->indexWidget(validationIndex) == nullptr) { + auto widget = CatalogueExplorerHelper::buildValidationWidget( + ui->treeView, [this, event]() { setEventChanges(event, false); }, + [this, event]() { setEventChanges(event, false); }); + ui->treeView->setIndexWidget(validationIndex, widget); + } + } + else { + // Note: the widget is destroyed + ui->treeView->setIndexWidget(validationIndex, nullptr); + } + + impl->m_Model->setEventHasChanges(event, hasChanges); } void CatalogueEventsWidget::populateWithCatalogues( diff --git a/gui/src/Catalogue/CatalogueExplorerHelper.cpp b/gui/src/Catalogue/CatalogueExplorerHelper.cpp new file mode 100644 index 0000000..1fb7767 --- /dev/null +++ b/gui/src/Catalogue/CatalogueExplorerHelper.cpp @@ -0,0 +1,32 @@ +#include "Catalogue/CatalogueExplorerHelper.h" + +#include +#include + +const auto VALIDATION_BUTTON_ICON_SIZE = 12; + +QWidget *CatalogueExplorerHelper::buildValidationWidget(QWidget *parent, std::function save, + std::function discard) +{ + auto widget = new QWidget{parent}; + + auto layout = new QHBoxLayout{widget}; + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + auto btnValid = new QToolButton{widget}; + btnValid->setIcon(QIcon{":/icones/save"}); + btnValid->setIconSize(QSize{VALIDATION_BUTTON_ICON_SIZE, VALIDATION_BUTTON_ICON_SIZE}); + btnValid->setAutoRaise(true); + QObject::connect(btnValid, &QToolButton::clicked, save); + layout->addWidget(btnValid); + + auto btnDiscard = new QToolButton{widget}; + btnDiscard->setIcon(QIcon{":/icones/discard"}); + btnDiscard->setIconSize(QSize{VALIDATION_BUTTON_ICON_SIZE, VALIDATION_BUTTON_ICON_SIZE}); + btnDiscard->setAutoRaise(true); + QObject::connect(btnDiscard, &QToolButton::clicked, discard); + layout->addWidget(btnDiscard); + + return widget; +} diff --git a/gui/src/Catalogue/CatalogueTreeWidgetItem.cpp b/gui/src/Catalogue/CatalogueTreeWidgetItem.cpp index afa3cef..4ce4f2a 100644 --- a/gui/src/Catalogue/CatalogueTreeWidgetItem.cpp +++ b/gui/src/Catalogue/CatalogueTreeWidgetItem.cpp @@ -1,12 +1,9 @@ #include "Catalogue/CatalogueTreeWidgetItem.h" +#include #include #include -#include -#include - -const auto VALIDATION_BUTTON_ICON_SIZE = 12; /// Column in the tree widget where the apply and cancel buttons must appear const auto APPLY_CANCEL_BUTTONS_COLUMN = 1; @@ -66,29 +63,11 @@ std::shared_ptr CatalogueTreeWidgetItem::catalogue() const void CatalogueTreeWidgetItem::setHasChanges(bool value) { if (value) { - if (treeWidget()->itemWidget(this, APPLY_CANCEL_BUTTONS_COLUMN) == nullptr) { - auto widet = new QWidget{treeWidget()}; - - auto layout = new QHBoxLayout{widet}; - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); - - auto btnValid = new QToolButton{widet}; - btnValid->setIcon(QIcon{":/icones/save"}); - btnValid->setIconSize(QSize{VALIDATION_BUTTON_ICON_SIZE, VALIDATION_BUTTON_ICON_SIZE}); - btnValid->setAutoRaise(true); - QObject::connect(btnValid, &QToolButton::clicked, [this]() { setHasChanges(false); }); - layout->addWidget(btnValid); - - auto btnDiscard = new QToolButton{widet}; - btnDiscard->setIcon(QIcon{":/icones/discard"}); - btnDiscard->setIconSize( - QSize{VALIDATION_BUTTON_ICON_SIZE, VALIDATION_BUTTON_ICON_SIZE}); - btnDiscard->setAutoRaise(true); - QObject::connect(btnDiscard, &QToolButton::clicked, [this]() { setHasChanges(false); }); - layout->addWidget(btnDiscard); - - treeWidget()->setItemWidget(this, APPLY_CANCEL_BUTTONS_COLUMN, {widet}); + if (!hasChanges()) { + auto widget = CatalogueExplorerHelper::buildValidationWidget( + treeWidget(), [this]() { setHasChanges(false); }, + [this]() { setHasChanges(false); }); + treeWidget()->setItemWidget(this, APPLY_CANCEL_BUTTONS_COLUMN, widget); } } else { @@ -97,6 +76,11 @@ void CatalogueTreeWidgetItem::setHasChanges(bool value) } } +bool CatalogueTreeWidgetItem::hasChanges() +{ + return treeWidget()->itemWidget(this, APPLY_CANCEL_BUTTONS_COLUMN) != nullptr; +} + void CatalogueTreeWidgetItem::refresh() { emitDataChanged();