diff --git a/src/console/CMakeLists.txt b/src/console/CMakeLists.txt index 34da20b..ff4298a 100644 --- a/src/console/CMakeLists.txt +++ b/src/console/CMakeLists.txt @@ -1,86 +1,87 @@ ecm_create_qm_loader(userfeedback_console_QM_LOADER userfeedbackconsole5_qt) set(console_lib_srcs core/aggregation.cpp core/aggregationelement.cpp core/product.cpp core/sample.cpp core/schemaentry.cpp core/schemaentryelement.cpp core/schemaentrytemplates.cpp core/survey.cpp rest/restapi.cpp rest/restclient.cpp rest/serverinfo.cpp jobs/job.cpp jobs/handshakejob.cpp jobs/productexportjob.cpp jobs/productimportjob.cpp jobs/securityscanjob.cpp model/aggregateddatamodel.cpp model/aggregationeditormodel.cpp model/aggregationelementmodel.cpp + model/aggregationelementeditmodel.cpp model/categoryaggregationmodel.cpp model/datamodel.cpp model/extrarowsproxymodel.cpp model/numericaggregationmodel.cpp model/productmodel.cpp model/ratiosetaggregationmodel.cpp model/rolemappingproxymodel.cpp model/schemamodel.cpp model/singlerowfilterproxymodel.cpp model/surveymodel.cpp model/timeaggregationmodel.cpp schematemplates/schematemplates.qrc ) add_library(KUserFeedbackConsole STATIC ${console_lib_srcs}) target_link_libraries(KUserFeedbackConsole Qt5::Network) target_include_directories(KUserFeedbackConsole PUBLIC "$") target_compile_features(KUserFeedbackConsole PRIVATE cxx_generic_lambdas) target_compile_definitions(KUserFeedbackConsole PUBLIC QT_DISABLE_DEPRECATED_BEFORE=0x050600 QT_DEPRECATED_WARNINGS) if(NOT TARGET KUserFeedbackWidgets) return() endif() set(console_srcs connectdialog.cpp main.cpp mainwindow.cpp helpcontroller.cpp analytics/aggregator.cpp analytics/analyticsview.cpp analytics/categoryaggregator.cpp analytics/chartutil.cpp analytics/numericaggregator.cpp analytics/ratiosetaggregator.cpp analytics/totalaggregator.cpp schemaeditor/aggregationeditwidget.cpp schemaeditor/schemaeditor.cpp schemaeditor/schemaeditwidget.cpp schemaeditor/schemaentryitemeditorfactory.cpp surveyeditor/surveydialog.cpp surveyeditor/surveyeditor.cpp widgets/metaenumcombobox.cpp ${userfeedback_console_QM_LOADER} ) add_executable(KUserFeedbackConsoleApplication ${console_srcs}) target_compile_features(KUserFeedbackConsoleApplication PRIVATE cxx_generic_lambdas) set_target_properties(KUserFeedbackConsoleApplication PROPERTIES OUTPUT_NAME UserFeedbackConsole) target_link_libraries(KUserFeedbackConsoleApplication Qt5::Widgets Qt5::Network Qt5::Charts KUserFeedbackWidgets KUserFeedbackConsole KUserFeedbackCommon) install(TARGETS KUserFeedbackConsoleApplication ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) if(UNIX AND NOT APPLE) install(FILES UserFeedbackConsole.desktop DESTINATION ${KDE_INSTALL_APPDIR}) endif() diff --git a/src/console/model/aggregationeditormodel.cpp b/src/console/model/aggregationeditormodel.cpp index c098842..0494de9 100644 --- a/src/console/model/aggregationeditormodel.cpp +++ b/src/console/model/aggregationeditormodel.cpp @@ -1,134 +1,144 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "aggregationeditormodel.h" #include #include using namespace KUserFeedback::Console; AggregationEditorModel::AggregationEditorModel(QObject* parent) : QAbstractTableModel(parent) { } AggregationEditorModel::~AggregationEditorModel() = default; Product AggregationEditorModel::product() const { return m_product; } void AggregationEditorModel::setProduct(const Product& product) { beginResetModel(); m_product = product; endResetModel(); } int AggregationEditorModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return 3; } int AggregationEditorModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; return m_product.aggregations().size(); } QVariant AggregationEditorModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || !m_product.isValid()) return {}; if (role == Qt::DisplayRole) { const auto aggr = m_product.aggregations().at(index.row()); switch (index.column()) { case 0: return aggr.name(); case 1: return Util::enumToString(aggr.type()); case 2: + { if (aggr.elements().isEmpty()) return tr(""); - return aggr.elements().at(0).displayString(); + QStringList l; + l.reserve(aggr.elements().size()); + foreach (const auto &elem, aggr.elements()) + l.push_back(elem.displayString()); + return l.join(QStringLiteral(", ")); + } } } else if (role == Qt::EditRole) { const auto aggr = m_product.aggregations().at(index.row()); switch (index.column()) { case 0: return aggr.name(); case 1: return QVariant::fromValue(aggr.type()); case 2: if (aggr.elements().isEmpty()) return QVariant::fromValue(AggregationElement()); return QVariant::fromValue(aggr.elements().at(0)); } } return {}; } QVariant AggregationEditorModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { switch (section) { case 0: return tr("Name"); case 1: return tr("Type"); case 2: return tr("Element"); } } return QAbstractTableModel::headerData(section, orientation, role); } Qt::ItemFlags AggregationEditorModel::flags(const QModelIndex& index) const { const auto baseFlags = QAbstractTableModel::flags(index); - if (index.isValid()) - return baseFlags | Qt::ItemIsEditable; + if (!index.isValid()) + return baseFlags; - return baseFlags; + const auto aggr = m_product.aggregations().at(index.row()); + if (aggr.type() == Aggregation::Category && index.column() == 2) + return baseFlags; + + return baseFlags | Qt::ItemIsEditable; } bool AggregationEditorModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid() || role != Qt::EditRole) return false; auto aggrs = m_product.aggregations(); auto &aggr = aggrs[index.row()]; switch (index.column()) { case 0: aggr.setName(value.toString()); break; case 1: aggr.setType(value.value()); break; case 2: aggr.setElements({ value.value() }); break; } m_product.setAggregations(aggrs); emit dataChanged(index, index); return true; } diff --git a/src/console/model/aggregationelementeditmodel.cpp b/src/console/model/aggregationelementeditmodel.cpp new file mode 100644 index 0000000..d8b625b --- /dev/null +++ b/src/console/model/aggregationelementeditmodel.cpp @@ -0,0 +1,91 @@ +/* + Copyright (C) 2017 Volker Krause + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "aggregationelementeditmodel.h" + +using namespace KUserFeedback::Console; + +AggregationElementEditModel::AggregationElementEditModel(QObject* parent) + : QAbstractListModel(parent) +{ +} + +AggregationElementEditModel::~AggregationElementEditModel() = default; + +Aggregation AggregationElementEditModel::aggregation() const +{ + return m_aggr; +} + +void AggregationElementEditModel::setAggregation(const Aggregation& aggregation) +{ + beginResetModel(); + m_aggr = aggregation; + endResetModel(); +} + +int AggregationElementEditModel::rowCount(const QModelIndex& parent) const +{ + if (parent.isValid()) + return 0; + return m_aggr.elements().size(); +} + +QVariant AggregationElementEditModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return {}; + + switch (role) { + case Qt::DisplayRole: + { + const auto elem = m_aggr.elements().at(index.row()); + if (elem.isValid()) + return elem.displayString(); + return tr(""); + } + case Qt::EditRole: + return QVariant::fromValue(m_aggr.elements().at(index.row())); + } + + return {}; +} + +QVariant AggregationElementEditModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole && section == 0) + return tr("Element"); + return QAbstractListModel::headerData(section, orientation, role); +} + +Qt::ItemFlags AggregationElementEditModel::flags(const QModelIndex& index) const +{ + const auto baseFlags = QAbstractListModel::flags(index); + return baseFlags | Qt::ItemIsEditable; +} + +bool AggregationElementEditModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid() || role != Qt::EditRole || value.isNull()) + return false; + + auto elems = m_aggr.elements(); + elems[index.row()] = value.value(); + m_aggr.setElements(elems); + emit dataChanged(index, index); + return true; +} diff --git a/src/console/model/aggregationelementeditmodel.h b/src/console/model/aggregationelementeditmodel.h new file mode 100644 index 0000000..a95a932 --- /dev/null +++ b/src/console/model/aggregationelementeditmodel.h @@ -0,0 +1,50 @@ +/* + Copyright (C) 2017 Volker Krause + + This program is free software; you can redistribute it and/or modify it + under the terms of the GNU Library General Public License as published by + the Free Software Foundation; either version 2 of the License, or (at your + option) any later version. + + This program is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef KUSERFEEDBACK_CONSOLE_AGGREGATIONELEMENTEDITMODEL_H +#define KUSERFEEDBACK_CONSOLE_AGGREGATIONELEMENTEDITMODEL_H + +#include + +#include + +namespace KUserFeedback { +namespace Console { + +class AggregationElementEditModel : public QAbstractListModel +{ + Q_OBJECT +public: + explicit AggregationElementEditModel(QObject *parent = nullptr); + ~AggregationElementEditModel(); + + Aggregation aggregation() const; + void setAggregation(const Aggregation &aggregation); + + int rowCount(const QModelIndex & parent) const override; + QVariant data(const QModelIndex & index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + Qt::ItemFlags flags(const QModelIndex & index) const override; + bool setData(const QModelIndex & index, const QVariant & value, int role) override; + +private: + Aggregation m_aggr; +}; + +}} + +#endif // KUSERFEEDBACK_CONSOLE_AGGREGATIONELEMENTEDITMODEL_H diff --git a/src/console/model/categoryaggregationmodel.cpp b/src/console/model/categoryaggregationmodel.cpp index 4562f4c..9ecc859 100644 --- a/src/console/model/categoryaggregationmodel.cpp +++ b/src/console/model/categoryaggregationmodel.cpp @@ -1,213 +1,213 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "categoryaggregationmodel.h" #include #include #include #include #include #include using namespace KUserFeedback::Console; CategoryAggregationModel::CategoryAggregationModel(QObject *parent) : QAbstractTableModel(parent) { } CategoryAggregationModel::~CategoryAggregationModel() { delete[] m_data; } void CategoryAggregationModel::setSourceModel(QAbstractItemModel* model) { Q_ASSERT(model); m_sourceModel = model; connect(model, &QAbstractItemModel::modelReset, this, &CategoryAggregationModel::recompute); recompute(); } void CategoryAggregationModel::setAggregation(const Aggregation& aggr) { m_aggr = aggr; m_depth = m_aggr.elements().size(); recompute(); } void CategoryAggregationModel::setDepth(int depth) { if (depth == m_depth) return; m_depth = std::min(depth, m_aggr.elements().size()); recompute(); } int CategoryAggregationModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent); return m_categories.size() + 1; } int CategoryAggregationModel::rowCount(const QModelIndex& parent) const { if (parent.isValid() || !m_sourceModel) return 0; return m_sourceModel->rowCount(); } QVariant CategoryAggregationModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || !m_sourceModel) return {}; if (role == TimeAggregationModel::MaximumValueRole) return m_maxValue; if (index.column() == 0) { const auto srcIdx = m_sourceModel->index(index.row(), 0); return m_sourceModel->data(srcIdx, role); } const auto idx = index.row() * m_categories.size() + index.column() - 1; switch (role) { case TimeAggregationModel::AccumulatedDisplayRole: return m_data[idx]; case Qt::DisplayRole: case TimeAggregationModel::DataDisplayRole: if (index.column() == 1) return m_data[idx]; return m_data[idx] - m_data[idx - 1]; } return {}; } QVariant CategoryAggregationModel::headerData(int section, Qt::Orientation orientation, int role) const { if (section < 0 || section >= columnCount()) return {}; if (orientation == Qt::Horizontal && m_sourceModel) { if (section == 0) return m_sourceModel->headerData(section, orientation, role); if (role == Qt::DisplayRole) { const auto cat = m_categories.at(section - 1); if (cat.isEmpty()) return tr("[empty]"); return cat; } } return QAbstractTableModel::headerData(section, orientation, role); } void CategoryAggregationModel::recompute() { - if (!m_sourceModel) + if (!m_sourceModel || m_depth > 2) return; const auto rowCount = m_sourceModel->rowCount(); beginResetModel(); m_categories.clear(); delete[] m_data; m_data = nullptr; m_maxValue = 0; if (rowCount <= 0 || !m_aggr.isValid() || m_depth <= 0) { endResetModel(); return; } // scan all samples to find all categories const auto allSamples = m_sourceModel->index(0, 0).data(TimeAggregationModel::AllSamplesRole).value>(); QVector>> depthSamples{{allSamples}}; // depth -> parent category index -> samples depthSamples.resize(m_depth + 1); QVector>> depthCategories{{{{}}}}; // depth -> parent category index -> category values depthCategories.resize(m_depth + 1); QVector> depthOffsets{{0}}; // depth -> parent category index -> column offset depthOffsets.resize(m_depth + 1); for (int i = 0; i < m_depth; ++i) { // for each depth layer... depthOffsets[i + 1] = { 0 }; for (int j = 0; j < depthCategories.at(i).size(); ++j) { // ... and for each parent category ... int prevSize = 0; for (int k = 0; k < depthCategories.at(i).at(j).size(); ++k) { // ... and for each category value... const auto sampleSubSet = depthSamples.at(i).at(j + k); QHash> catHash; for (const auto &s : sampleSubSet) // ... and for each sample catHash[sampleValue(s, i).toString()].push_back(s); QVector cats; cats.reserve(catHash.size()); for (auto it = catHash.cbegin(); it != catHash.cend(); ++it) cats.push_back(it.key()); std::sort(cats.begin(), cats.end()); depthCategories[i + 1].push_back(cats); for (const auto &cat : cats) depthSamples[i + 1].push_back(catHash.value(cat)); if (k > 0) depthOffsets[i + 1].push_back(depthOffsets.at(i + 1).constLast() + prevSize); prevSize = cats.size(); } } } for (const auto &cats : depthCategories.at(m_depth)) m_categories += cats; const auto colCount = m_categories.size(); // compute the counts per cell, we could do that on demand, but we need the maximum for QtCharts... m_data = new int[colCount * rowCount]; memset(m_data, 0, sizeof(int) * colCount * rowCount); for (int row = 0; row < rowCount; ++row) { const auto samples = m_sourceModel->index(row, 0).data(TimeAggregationModel::SamplesRole).value>(); for (const auto &sample : samples) { int parentIdx = 0; for (int i = 1; i <= m_depth; ++i) { const auto cats = depthCategories.at(i).at(parentIdx); const auto catIt = std::lower_bound(cats.constBegin(), cats.constEnd(), sampleValue(sample, i - 1).toString()); Q_ASSERT(catIt != cats.constEnd()); parentIdx = std::distance(cats.constBegin(), catIt) + depthOffsets.at(i).at(parentIdx); } m_data[colCount * row + parentIdx]++; } // accumulate per row for stacked plots for (int col = 1; col < colCount; ++col) { const auto idx = colCount * row + col; m_data[idx] += m_data[idx - 1]; } m_maxValue = std::max(m_maxValue, m_data[row * colCount + colCount - 1]); } endResetModel(); } QVariant CategoryAggregationModel::sampleValue(const Sample& s, int depth) const { const auto elem = m_aggr.elements().at(depth); switch (elem.type()) { case AggregationElement::Value: return s.value(elem.schemaEntry().name() + QLatin1String(".") + elem.schemaEntryElement().name()); case AggregationElement::Size: const auto l = s.value(elem.schemaEntry().name()); return l.value().size(); break; } return {}; } diff --git a/src/console/schemaeditor/aggregationeditwidget.cpp b/src/console/schemaeditor/aggregationeditwidget.cpp index dd0e1e7..e00340e 100644 --- a/src/console/schemaeditor/aggregationeditwidget.cpp +++ b/src/console/schemaeditor/aggregationeditwidget.cpp @@ -1,109 +1,173 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "aggregationeditwidget.h" #include "ui_aggregationeditwidget.h" #include "schemaentryitemeditorfactory.h" #include #include +#include #include #include #include using namespace KUserFeedback::Console; AggregationEditWidget::AggregationEditWidget(QWidget* parent) : QWidget(parent), ui(new Ui::AggregationEditWidget), m_model(new AggregationEditorModel(this)), - m_editorFactory(new SchemaEntryItemEditorFactory) + m_editorFactory(new SchemaEntryItemEditorFactory), + m_elementModel(new AggregationElementEditModel(this)) { ui->setupUi(this); ui->aggregationView->setModel(m_model); ui->aggregationView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); qobject_cast(ui->aggregationView->itemDelegate())->setItemEditorFactory(m_editorFactory.get()); connect(ui->aggregationView, &QWidget::customContextMenuRequested, this, &AggregationEditWidget::contextMenu); - connect(ui->aggregationView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AggregationEditWidget::updateState); + connect(ui->aggregationView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AggregationEditWidget::selectionChanged); connect(m_model, &QAbstractItemModel::dataChanged, this, &AggregationEditWidget::productChanged); + connect(m_model, &QAbstractItemModel::dataChanged, this, &AggregationEditWidget::updateState); connect(ui->actionAddAggregation, &QAction::triggered, this, &AggregationEditWidget::addAggregation); connect(ui->actionDeleteAggregation, &QAction::triggered, this, &AggregationEditWidget::deleteAggregation); + ui->elementView->setModel(m_elementModel); + ui->elementView->header()->setSectionResizeMode(0, QHeaderView::Stretch); + qobject_cast(ui->elementView->itemDelegate())->setItemEditorFactory(m_editorFactory.get()); + connect(ui->elementView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AggregationEditWidget::updateState); + connect(m_elementModel, &QAbstractItemModel::dataChanged, this, [this]() { + auto p = product(); + auto aggrs = p.aggregations(); + aggrs[ui->aggregationView->selectionModel()->selectedRows().at(0).row()] = m_elementModel->aggregation(); + p.setAggregations(aggrs); + m_model->setProduct(p); + emit productChanged(); + }); + connect(ui->addButton, &QPushButton::clicked, this, &AggregationEditWidget::addElement); + connect(ui->deleteButton, &QPushButton::clicked, this, &AggregationEditWidget::deleteElement); + addActions({ ui->actionAddAggregation, ui->actionDeleteAggregation }); updateState(); } AggregationEditWidget::~AggregationEditWidget() = default; Product AggregationEditWidget::product() const { return m_model->product(); } void AggregationEditWidget::setProduct(const Product& product) { m_model->setProduct(product); m_editorFactory->setProduct(product); updateState(); } +Aggregation AggregationEditWidget::currentAggregation() const +{ + const auto rows = ui->aggregationView->selectionModel()->selectedRows(); + if (rows.isEmpty()) + return {}; + + return product().aggregations().at(rows.at(0).row()); +} + void AggregationEditWidget::addAggregation() { auto p = product(); auto aggrs = p.aggregations(); aggrs += Aggregation(); p.setAggregations(aggrs); setProduct(p); emit productChanged(); } void AggregationEditWidget::deleteAggregation() { const auto rows = ui->aggregationView->selectionModel()->selectedRows(); if (rows.isEmpty()) return; const auto r = QMessageBox::critical(this, tr("Delete Aggregation"), tr("Do you want to delete the currently selected aggregation?"), QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Discard); if (r != QMessageBox::Discard) return; const auto idx = rows.at(0); auto p = product(); auto aggrs = p.aggregations(); aggrs.remove(idx.row()); p.setAggregations(aggrs); setProduct(p); emit productChanged(); } +void AggregationEditWidget::addElement() +{ + auto aggr = m_elementModel->aggregation(); + auto elems = aggr.elements(); + elems.push_back({}); + aggr.setElements(elems); + m_elementModel->setAggregation(aggr); +} + +void AggregationEditWidget::deleteElement() +{ + auto aggr = m_elementModel->aggregation(); + auto elems = aggr.elements(); + elems.removeAt(ui->elementView->selectionModel()->selectedRows().at(0).row()); + aggr.setElements(elems); + m_elementModel->setAggregation(aggr); + + auto p = product(); + auto aggrs = p.aggregations(); + aggrs[ui->aggregationView->selectionModel()->selectedRows().at(0).row()] = aggr; + p.setAggregations(aggrs); + m_model->setProduct(p); + emit productChanged(); +} + void AggregationEditWidget::updateState() { ui->actionAddAggregation->setEnabled(product().isValid()); ui->actionDeleteAggregation->setEnabled(ui->aggregationView->selectionModel()->hasSelection()); + + const auto aggr = currentAggregation(); + const auto isMultiElement = aggr.type() == Aggregation::Category; + ui->elementView->setEnabled(isMultiElement); + ui->addButton->setEnabled(isMultiElement); + ui->deleteButton->setEnabled(isMultiElement && ui->elementView->selectionModel()->hasSelection()); } void AggregationEditWidget::contextMenu(QPoint pos) { QMenu menu; menu.addActions({ ui->actionAddAggregation, ui->actionDeleteAggregation }); menu.exec(ui->aggregationView->viewport()->mapToGlobal(pos)); } + +void AggregationEditWidget::selectionChanged() +{ + m_elementModel->setAggregation(currentAggregation()); + updateState(); +} diff --git a/src/console/schemaeditor/aggregationeditwidget.h b/src/console/schemaeditor/aggregationeditwidget.h index 08bc6a3..36792a6 100644 --- a/src/console/schemaeditor/aggregationeditwidget.h +++ b/src/console/schemaeditor/aggregationeditwidget.h @@ -1,64 +1,71 @@ /* Copyright (C) 2016 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KUSERFEEDBACK_CONSOLE_AGGREGATIONEDITWIDGET_H #define KUSERFEEDBACK_CONSOLE_AGGREGATIONEDITWIDGET_H #include #include namespace KUserFeedback { namespace Console { +class Aggregation; class AggregationEditorModel; +class AggregationElementEditModel; class Product; class SchemaEntryItemEditorFactory; namespace Ui { class AggregationEditWidget; } class AggregationEditWidget : public QWidget { Q_OBJECT public: explicit AggregationEditWidget(QWidget *parent = nullptr); ~AggregationEditWidget(); Product product() const; void setProduct(const Product &product); signals: void productChanged(); private: + Aggregation currentAggregation() const; void addAggregation(); void deleteAggregation(); + void addElement(); + void deleteElement(); void updateState(); void contextMenu(QPoint pos); + void selectionChanged(); std::unique_ptr ui; AggregationEditorModel *m_model; std::unique_ptr m_editorFactory; + AggregationElementEditModel *m_elementModel; }; } } #endif // KUSERFEEDBACK_CONSOLE_AGGREGATIONEDITWIDGET_H diff --git a/src/console/schemaeditor/aggregationeditwidget.ui b/src/console/schemaeditor/aggregationeditwidget.ui index e1a40d0..409c3a5 100644 --- a/src/console/schemaeditor/aggregationeditwidget.ui +++ b/src/console/schemaeditor/aggregationeditwidget.ui @@ -1,53 +1,103 @@ KUserFeedback::Console::AggregationEditWidget 0 0 - 400 + 542 300 - + - - - Qt::CustomContextMenu - - - false - - - true + + + Qt::Horizontal + + + Qt::CustomContextMenu + + + false + + + true + + + + + + + + false + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Add + + + + + + + &Delete + + + + + + + Add &Aggregation Add a new aggregation. &Delete Aggregation Delete the currently selected aggregation.