##// END OF EJS Templates
@@ -1,128 +1,128
1 1 /*------------------------------------------------------------------------------
2 2 -- This file is a part of the SciQLop Software
3 3 -- Copyright (C) 2017, Plasma Physics Laboratory - CNRS
4 4 --
5 5 -- This program is free software; you can redistribute it and/or modify
6 6 -- it under the terms of the GNU General Public License as published by
7 7 -- the Free Software Foundation; either version 2 of the License, or
8 8 -- (at your option) any later version.
9 9 --
10 10 -- This program is distributed in the hope that it will be useful,
11 11 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 -- GNU General Public License for more details.
14 14 --
15 15 -- You should have received a copy of the GNU General Public License
16 16 -- along with this program; if not, write to the Free Software
17 17 -- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 18 -------------------------------------------------------------------------------*/
19 19 /*-- Author : Alexis Jeandet
20 20 -- Mail : alexis.jeandet@member.fsf.org
21 21 ----------------------------------------------------------------------------*/
22 22 #include "MainWindow.h"
23 23 #include "ui_MainWindow.h"
24 24
25 25 #include <DataSource/DataSourceController.h>
26 26 #include <DataSource/DataSourceWidget.h>
27 27 #include <SqpApplication.h>
28 28
29 29 #include <QAction>
30 30 #include <QDate>
31 31 #include <QDateTime>
32 32 #include <QDir>
33 33 #include <QFileDialog>
34 34 //#include <omp.h>
35 35 //#include <network/filedownloader.h>
36 36 //#include <qlopdatabase.h>
37 37 //#include <qlopsettings.h>
38 38 //#include <qlopgui.h>
39 39 //#include <spacedata.h>
40 40 //#include "qlopcore.h"
41 41 //#include "qlopcodecmanager.h"
42 42 //#include "cdfcodec.h"
43 43 //#include "amdatxtcodec.h"
44 44 //#include <qlopplotmanager.h>
45 45 #include <QAction>
46 46 #include <QToolBar>
47 47 #include <memory.h>
48 48 MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent}, m_Ui{new Ui::MainWindow}
49 49 {
50 50 m_Ui->setupUi(this);
51 51
52 52 auto leftSidePane = m_Ui->leftInspectorSidePane->sidePane();
53 53 leftSidePane->addAction("ACTION L1");
54 54 leftSidePane->addAction("ACTION L2");
55 55 leftSidePane->addAction("ACTION L3");
56 56
57 57 auto rightSidePane = m_Ui->rightInspectorSidePane->sidePane();
58 58 rightSidePane->addAction("ACTION R1");
59 59 rightSidePane->addAction("ACTION R2");
60 60 rightSidePane->addAction("ACTION R3");
61 61
62 62 this->menuBar()->addAction("File");
63 63 auto mainToolBar = this->addToolBar("MainToolBar");
64 64 mainToolBar->addAction("A1");
65 65
66 66 // Widgets / controllers connections
67 connect(&sqpApp->dataSourceController(), SIGNAL(dataSourceItemSet(DataSourceItem &)),
68 m_Ui->dataSourceWidget, SLOT(addDataSource(DataSourceItem &)));
67 connect(&sqpApp->dataSourceController(), SIGNAL(dataSourceItemSet(DataSourceItem *)),
68 m_Ui->dataSourceWidget, SLOT(addDataSource(DataSourceItem *)));
69 69
70 70 /* QLopGUI::registerMenuBar(menuBar());
71 71 this->setWindowIcon(QIcon(":/sciqlopLOGO.svg"));
72 72 this->m_progressWidget = new QWidget();
73 73 this->m_progressLayout = new QVBoxLayout(this->m_progressWidget);
74 74 this->m_progressWidget->setLayout(this->m_progressLayout);
75 75 this->m_progressWidget->setWindowModality(Qt::WindowModal);
76 76 m_progressThreadIds = (int*) malloc(OMP_THREADS*sizeof(int));
77 77 for(int i=0;i<OMP_THREADS;i++)
78 78 {
79 79 this->m_progress.append(new QProgressBar(this->m_progressWidget));
80 80 this->m_progress.last()->setMinimum(0);
81 81 this->m_progress.last()->setMaximum(100);
82 82 this->m_progressLayout->addWidget(this->m_progress.last());
83 83 this->m_progressWidget->hide();
84 84 this->m_progressThreadIds[i] = -1;
85 85 }
86 86 this->m_progressWidget->setWindowTitle("Loading File");
87 87 const QList<QLopService*>ServicesToLoad=QList<QLopService*>()
88 88 << QLopCore::self()
89 89 << QLopPlotManager::self()
90 90 << QLopCodecManager::self()
91 91 << FileDownloader::self()
92 92 << QLopDataBase::self()
93 93 << SpaceData::self();
94 94
95 95 CDFCodec::registerToManager();
96 96 AMDATXTCodec::registerToManager();
97 97
98 98
99 99 for(int i=0;i<ServicesToLoad.count();i++)
100 100 {
101 101 qDebug()<<ServicesToLoad.at(i)->serviceName();
102 102 ServicesToLoad.at(i)->initialize(); //must be called before getGUI
103 103 QDockWidget* wdgt=ServicesToLoad.at(i)->getGUI();
104 104 if(wdgt)
105 105 {
106 106 wdgt->setAllowedAreas(Qt::AllDockWidgetAreas);
107 107 this->addDockWidget(Qt::TopDockWidgetArea,wdgt);
108 108 }
109 109 PythonQt::self()->getMainModule().addObject(ServicesToLoad.at(i)->serviceName(),(QObject*)ServicesToLoad.at(i));
110 110 }*/
111 111 }
112 112
113 113 MainWindow::~MainWindow()
114 114 {
115 115 }
116 116
117 117
118 118 void MainWindow::changeEvent(QEvent *e)
119 119 {
120 120 QMainWindow::changeEvent(e);
121 121 switch (e->type()) {
122 122 case QEvent::LanguageChange:
123 123 m_Ui->retranslateUi(this);
124 124 break;
125 125 default:
126 126 break;
127 127 }
128 128 }
@@ -1,61 +1,61
1 1 #ifndef SCIQLOP_DATASOURCECONTROLLER_H
2 2 #define SCIQLOP_DATASOURCECONTROLLER_H
3 3
4 4 #include <QLoggingCategory>
5 5 #include <QObject>
6 6 #include <QUuid>
7 7
8 8 #include <Common/spimpl.h>
9 9
10 10 Q_DECLARE_LOGGING_CATEGORY(LOG_DataSourceController)
11 11
12 12 class DataSourceItem;
13 13
14 14 /**
15 15 * @brief The DataSourceController class aims to make the link between SciQlop and its plugins. This
16 16 * is the intermediate class that SciQlop has to use in the way to connect a data source. Please
17 17 * first use register method to initialize a plugin specified by its metadata name (JSON plugin
18 18 * source) then others specifics method will be able to access it. You can load a data source driver
19 19 * plugin then create a data source.
20 20 */
21 21 class DataSourceController : public QObject {
22 22 Q_OBJECT
23 23 public:
24 24 explicit DataSourceController(QObject *parent = 0);
25 25 virtual ~DataSourceController();
26 26
27 27 /**
28 28 * Registers a data source. The method delivers a unique id that can be used afterwards to
29 29 * access to the data source properties (structure, connection parameters, data provider, etc.)
30 30 * @param dataSourceName the name of the data source
31 31 * @return the unique id with which the data source has been registered
32 32 */
33 33 QUuid registerDataSource(const QString &dataSourceName) noexcept;
34 34
35 35 /**
36 36 * Sets the structure of a data source. The controller takes ownership of the structure.
37 37 * @param dataSourceUid the unique id with which the data source has been registered into the
38 38 * controller. If it is invalid, the method has no effect.
39 39 * @param dataSourceItem the structure of the data source
40 40 * @sa registerDataSource()
41 41 */
42 42 void setDataSourceItem(const QUuid &dataSourceUid,
43 43 std::unique_ptr<DataSourceItem> dataSourceItem) noexcept;
44 44
45 45 public slots:
46 46 /// Manage init/end of the controller
47 47 void initialize();
48 48 void finalize();
49 49
50 50 signals:
51 51 /// Signal emitted when a structure has been set for a data source
52 void dataSourceItemSet(DataSourceItem &dataSourceItem);
52 void dataSourceItemSet(DataSourceItem *dataSourceItem);
53 53
54 54 private:
55 55 void waitForFinish();
56 56
57 57 class DataSourceControllerPrivate;
58 58 spimpl::unique_impl_ptr<DataSourceControllerPrivate> impl;
59 59 };
60 60
61 61 #endif // SCIQLOP_DATASOURCECONTROLLER_H
@@ -1,78 +1,78
1 1 #include <DataSource/DataSourceController.h>
2 2 #include <DataSource/DataSourceItem.h>
3 3
4 4 #include <QMutex>
5 5 #include <QThread>
6 6
7 7 #include <QDir>
8 8 #include <QStandardPaths>
9 9
10 10 Q_LOGGING_CATEGORY(LOG_DataSourceController, "DataSourceController")
11 11
12 12 class DataSourceController::DataSourceControllerPrivate {
13 13 public:
14 14 QMutex m_WorkingMutex;
15 15 /// Data sources registered
16 16 QHash<QUuid, QString> m_DataSources;
17 17 /// Data sources structures
18 18 std::map<QUuid, std::unique_ptr<DataSourceItem> > m_DataSourceItems;
19 19 };
20 20
21 21 DataSourceController::DataSourceController(QObject *parent)
22 22 : impl{spimpl::make_unique_impl<DataSourceControllerPrivate>()}
23 23 {
24 24 qCDebug(LOG_DataSourceController()) << tr("DataSourceController construction")
25 25 << QThread::currentThread();
26 26 }
27 27
28 28 DataSourceController::~DataSourceController()
29 29 {
30 30 qCDebug(LOG_DataSourceController()) << tr("DataSourceController destruction")
31 31 << QThread::currentThread();
32 32 this->waitForFinish();
33 33 }
34 34
35 35 QUuid DataSourceController::registerDataSource(const QString &dataSourceName) noexcept
36 36 {
37 37 auto dataSourceUid = QUuid::createUuid();
38 38 impl->m_DataSources.insert(dataSourceUid, dataSourceName);
39 39
40 40 return dataSourceUid;
41 41 }
42 42
43 43 void DataSourceController::setDataSourceItem(
44 44 const QUuid &dataSourceUid, std::unique_ptr<DataSourceItem> dataSourceItem) noexcept
45 45 {
46 46 if (impl->m_DataSources.contains(dataSourceUid)) {
47 47 impl->m_DataSourceItems.insert(std::make_pair(dataSourceUid, std::move(dataSourceItem)));
48 48
49 49 // Retrieves the data source item to emit the signal with it
50 50 auto it = impl->m_DataSourceItems.find(dataSourceUid);
51 51 if (it != impl->m_DataSourceItems.end()) {
52 emit dataSourceItemSet(*it->second);
52 emit dataSourceItemSet(it->second.get());
53 53 }
54 54 }
55 55 else {
56 56 qCWarning(LOG_DataSourceController()) << tr("Can't set data source item for uid %1 : no "
57 57 "data source has been registered with the uid")
58 58 .arg(dataSourceUid.toString());
59 59 }
60 60 }
61 61
62 62 void DataSourceController::initialize()
63 63 {
64 64 qCDebug(LOG_DataSourceController()) << tr("DataSourceController init")
65 65 << QThread::currentThread();
66 66 impl->m_WorkingMutex.lock();
67 67 qCDebug(LOG_DataSourceController()) << tr("DataSourceController init END");
68 68 }
69 69
70 70 void DataSourceController::finalize()
71 71 {
72 72 impl->m_WorkingMutex.unlock();
73 73 }
74 74
75 75 void DataSourceController::waitForFinish()
76 76 {
77 77 QMutexLocker locker{&impl->m_WorkingMutex};
78 78 }
@@ -1,51 +1,51
1 1 #include <DataSource/DataSourceController.h>
2 2 #include <DataSource/DataSourceItem.h>
3 3
4 4 #include <QObject>
5 5 #include <QtTest>
6 6
7 7 #include <memory>
8 8
9 9 class TestDataSourceController : public QObject {
10 10 Q_OBJECT
11 11 private slots:
12 12 void testRegisterDataSource();
13 13 void testSetDataSourceItem();
14 14 };
15 15
16 16 void TestDataSourceController::testRegisterDataSource()
17 17 {
18 18 DataSourceController dataSourceController{};
19 19
20 20 auto uid = dataSourceController.registerDataSource(QStringLiteral("Source1"));
21 21 QVERIFY(!uid.isNull());
22 22 }
23 23
24 24 void TestDataSourceController::testSetDataSourceItem()
25 25 {
26 26 DataSourceController dataSourceController{};
27 27
28 28 // Spy to test controllers' signals
29 QSignalSpy signalSpy{&dataSourceController, SIGNAL(dataSourceItemSet(const DataSourceItem &))};
29 QSignalSpy signalSpy{&dataSourceController, SIGNAL(dataSourceItemSet(DataSourceItem *))};
30 30
31 31 // Create a data source item
32 32 auto source1Name = QStringLiteral("Source1");
33 33 auto source1Values = QVector<QVariant>{source1Name};
34 34 auto source1Item
35 35 = std::make_unique<DataSourceItem>(DataSourceItemType::PRODUCT, std::move(source1Values));
36 36
37 37 // Add data source item to the controller and check that a signal has been emitted after setting
38 38 // data source item in the controller
39 39 auto source1Uid = dataSourceController.registerDataSource(source1Name);
40 40 dataSourceController.setDataSourceItem(source1Uid, std::move(source1Item));
41 41 QCOMPARE(signalSpy.count(), 1);
42 42
43 43 // Try to a data source item with an unregistered uid and check that no signal has been emitted
44 44 auto unregisteredUid = QUuid::createUuid();
45 45 dataSourceController.setDataSourceItem(
46 46 unregisteredUid, std::make_unique<DataSourceItem>(DataSourceItemType::PRODUCT));
47 47 QCOMPARE(signalSpy.count(), 1);
48 48 }
49 49
50 50 QTEST_MAIN(TestDataSourceController)
51 51 #include "TestDataSourceController.moc"
@@ -1,33 +1,33
1 1 #ifndef SCIQLOP_DATASOURCEWIDGET_H
2 2 #define SCIQLOP_DATASOURCEWIDGET_H
3 3
4 4 #include <Common/spimpl.h>
5 5
6 6 #include <QWidget>
7 7
8 8 class DataSourceItem;
9 9
10 10 /**
11 11 * @brief The DataSourceWidget handles the graphical representation (as a tree) of the data sources
12 12 * attached to SciQlop.
13 13 */
14 14 class DataSourceWidget : public QWidget {
15 15 Q_OBJECT
16 16
17 17 public:
18 18 explicit DataSourceWidget(QWidget *parent = 0);
19 19
20 20 public slots:
21 21 /**
22 22 * Adds a data source. An item associated to the data source is created and then added to the
23 23 * representation tree
24 * @param dataSource the data source to add
24 * @param dataSource the data source to add. The pointer has to be not null
25 25 */
26 void addDataSource(DataSourceItem &dataSource) noexcept;
26 void addDataSource(DataSourceItem *dataSource) noexcept;
27 27
28 28 private:
29 29 class DataSourceWidgetPrivate;
30 30 spimpl::unique_impl_ptr<DataSourceWidgetPrivate> impl;
31 31 };
32 32
33 33 #endif // SCIQLOP_DATASOURCEWIDGET_H
@@ -1,68 +1,75
1 1 #include <DataSource/DataSourceItem.h>
2 2 #include <DataSource/DataSourceTreeWidgetItem.h>
3 3
4 4 #include <SqpApplication.h>
5 5
6 6 Q_LOGGING_CATEGORY(LOG_DataSourceTreeWidgetItem, "DataSourceTreeWidgetItem")
7 7
8 8 namespace {
9 9
10 10 QIcon itemIcon(const DataSourceItem *dataSource)
11 11 {
12 12 if (dataSource) {
13 13 auto dataSourceType = dataSource->type();
14 14 switch (dataSourceType) {
15 15 case DataSourceItemType::NODE:
16 16 return sqpApp->style()->standardIcon(QStyle::SP_DirIcon);
17 17 case DataSourceItemType::PRODUCT:
18 18 return sqpApp->style()->standardIcon(QStyle::SP_FileIcon);
19 19 default:
20 20 // No action
21 21 break;
22 22 }
23
24 qCWarning(LOG_DataSourceTreeWidgetItem())
25 << QObject::tr("Can't set data source icon : unknown data source type");
26 }
27 else {
28 qCWarning(LOG_DataSourceTreeWidgetItem())
29 << QObject::tr("Can't set data source icon : the data source is null");
23 30 }
24 31
25 32 // Default cases
26 33 return QIcon{};
27 34 }
28 35
29 36 } // namespace
30 37
31 38 struct DataSourceTreeWidgetItem::DataSourceTreeWidgetItemPrivate {
32 39 explicit DataSourceTreeWidgetItemPrivate(const DataSourceItem *data) : m_Data{data} {}
33 40
34 41 /// Model used to retrieve data source information
35 42 const DataSourceItem *m_Data;
36 43 };
37 44
38 45 DataSourceTreeWidgetItem::DataSourceTreeWidgetItem(const DataSourceItem *data, int type)
39 46 : DataSourceTreeWidgetItem{nullptr, data, type}
40 47 {
41 48 }
42 49
43 50 DataSourceTreeWidgetItem::DataSourceTreeWidgetItem(QTreeWidget *parent, const DataSourceItem *data,
44 51 int type)
45 52 : QTreeWidgetItem{parent, type},
46 53 impl{spimpl::make_unique_impl<DataSourceTreeWidgetItemPrivate>(data)}
47 54 {
48 55 // Sets the icon depending on the data source
49 56 setIcon(0, itemIcon(impl->m_Data));
50 57 }
51 58
52 59 QVariant DataSourceTreeWidgetItem::data(int column, int role) const
53 60 {
54 61 if (role == Qt::DisplayRole) {
55 62 return (impl->m_Data) ? impl->m_Data->data(column) : QVariant{};
56 63 }
57 64 else {
58 65 return QTreeWidgetItem::data(column, role);
59 66 }
60 67 }
61 68
62 69 void DataSourceTreeWidgetItem::setData(int column, int role, const QVariant &value)
63 70 {
64 71 // Data can't be changed by edition
65 72 if (role != Qt::EditRole) {
66 73 QTreeWidgetItem::setData(column, role, value);
67 74 }
68 75 }
@@ -1,61 +1,63
1 1 #include <DataSource/DataSourceWidget.h>
2 2
3 3 #include <ui_DataSourceWidget.h>
4 4
5 5 #include <DataSource/DataSourceItem.h>
6 6 #include <DataSource/DataSourceTreeWidgetItem.h>
7 7
8 8 namespace {
9 9
10 10 /// Number of columns displayed in the tree
11 11 const auto TREE_NB_COLUMNS = 1;
12 12
13 13 /// Header labels for the tree
14 14 const auto TREE_HEADER_LABELS = QStringList{QObject::tr("Name")};
15 15
16 16 /**
17 17 * Creates the item associated to a data source
18 18 * @param dataSource the data source for which to create the item
19 19 * @return the new item
20 20 */
21 21 DataSourceTreeWidgetItem *createTreeWidgetItem(DataSourceItem *dataSource)
22 22 {
23 23 // Creates item for the data source
24 24 auto item = new DataSourceTreeWidgetItem{dataSource};
25 25
26 26 // Generates items for the children of the data source
27 27 for (auto i = 0; i < dataSource->childCount(); ++i) {
28 28 item->addChild(createTreeWidgetItem(dataSource->child(i)));
29 29 }
30 30
31 31 return item;
32 32 }
33 33
34 34 } // namespace
35 35
36 36 class DataSourceWidget::DataSourceWidgetPrivate {
37 37 public:
38 38 explicit DataSourceWidgetPrivate(DataSourceWidget &widget)
39 39 : m_Ui{std::make_unique<Ui::DataSourceWidget>()}
40 40 {
41 41 m_Ui->setupUi(&widget);
42 42
43 43 // Set tree properties
44 44 m_Ui->treeWidget->setColumnCount(TREE_NB_COLUMNS);
45 45 m_Ui->treeWidget->setHeaderLabels(TREE_HEADER_LABELS);
46 46 }
47 47
48 48 std::unique_ptr<Ui::DataSourceWidget> m_Ui;
49 49 };
50 50
51 51 DataSourceWidget::DataSourceWidget(QWidget *parent)
52 52 : QWidget{parent}, impl{spimpl::make_unique_impl<DataSourceWidgetPrivate>(*this)}
53 53 {
54 54 }
55 55
56 void DataSourceWidget::addDataSource(DataSourceItem &dataSource) noexcept
56 void DataSourceWidget::addDataSource(DataSourceItem *dataSource) noexcept
57 57 {
58 58 // Creates the item associated to the source and adds it to the tree widget. The tree widget
59 59 // takes the ownership of the item
60 impl->m_Ui->treeWidget->addTopLevelItem(createTreeWidgetItem(&dataSource));
60 if (dataSource) {
61 impl->m_Ui->treeWidget->addTopLevelItem(createTreeWidgetItem(dataSource));
62 }
61 63 }
General Comments 3
Under Review
author

Pull request updated. Auto status change to "Under Review"

Changed commits:
  * 6 added
  * 3 removed

Changed files:
  * A gui/src/Visualization/VisualizationWidget.cpp
  * A gui/include/SidePane/SqpSidePane.h
  * A gui/include/Visualization/VisualizationGraphWidget.h
  * A gui/include/Visualization/VisualizationTabWidget.h
  * A gui/include/Visualization/VisualizationWidget.h
  * A gui/include/Visualization/VisualizationZoneWidget.h
  * A gui/include/Visualization/qcustomplot.h
  * A gui/src/SidePane/SqpSidePane.cpp
  * A gui/src/Visualization/VisualizationGraphWidget.cpp
  * A gui/src/Visualization/VisualizationTabWidget.cpp
  * A gui/src/Visualization/VisualizationZoneWidget.cpp
  * A gui/src/Visualization/qcustomplot.cpp
  * M gui/include/DataSource/DataSourceWidget.h
  * M gui/src/DataSource/DataSourceTreeWidgetItem.cpp
  * M gui/src/DataSource/DataSourceWidget.cpp
  * M app/src/Main.cpp
  * M app/src/MainWindow.cpp
  * M app/ui/MainWindow.ui
  * M core/include/DataSource/DataSourceController.h
  * M core/src/DataSource/DataSourceController.cpp
  * M core/tests/DataSource/TestDataSourceController.cpp
  * M gui/ui/visualization/VisualizationWidget.ui
  * M gui/vera-exclusions/exclusions.txt
  * M gui/src/visualization/VisualizationWidget.cpp
  * R cmake/sciqlop_package_qt.cmake
  * R core/include/Plugin/PluginManager.h
  * R core/include/Visualization/VisualizationController.h
  * R core/src/Plugin/PluginManager.cpp
  * R core/src/Visualization/VisualizationController.cpp
  * R gui/include/sidepane/SqpSidePane.h
  * R gui/include/visualization/VisualizationGraphWidget.h
  * R gui/include/visualization/VisualizationTabWidget.h
  * R gui/include/visualization/VisualizationWidget.h
  * R gui/include/visualization/VisualizationZoneWidget.h
  * R gui/include/visualization/qcustomplot.h
  * R gui/src/sidepane/SqpSidePane.cpp
  * R gui/src/visualization/VisualizationGraphWidget.cpp
  * R gui/src/visualization/VisualizationTabWidget.cpp
  * R gui/src/visualization/VisualizationZoneWidget.cpp
  * R gui/src/visualization/qcustomplot.cpp
  * R gui/ui/sidepane/SqpSidePane.ui
  * R gui/ui/visualization/VisualizationGraphWidget.ui
  * R gui/ui/visualization/VisualizationTabWidget.ui
  * R gui/ui/visualization/VisualizationZoneWidget.ui
  * R plugin/CMakeLists.txt
  * R plugin/cmake/Findsciqlop-plugin.cmake
  * R plugin/include/Plugin/IPlugin.h
  * R app/CMakeLists.txt
  * R app/include/MainWindow.h
  * R app/vera-exclusions/exclusions.txt
  * R cmake/sciqlop_applications.cmake
  * R core/CMakeLists.txt
  * R core/include/Common/spimpl.h
  * R gui/CMakeLists.txt
  * R gui/include/SqpApplication.h
  * R gui/src/SqpApplication.cpp
  * R app/src/mainwindow.ui
Approved
author

Status change > Approved

You need to be logged in to leave comments. Login now