diff --git a/code/nel/tools/3d/mesh_editor/icons.qrc b/code/nel/tools/3d/mesh_editor/icons.qrc
new file mode 100644
index 000000000..31a9f782d
--- /dev/null
+++ b/code/nel/tools/3d/mesh_editor/icons.qrc
@@ -0,0 +1,7 @@
+
+
+ icons/cross-circle.png
+ icons/exclamation.png
+ icons/information-white.png
+
+
diff --git a/code/nel/tools/3d/mesh_editor/icons/cross-circle.png b/code/nel/tools/3d/mesh_editor/icons/cross-circle.png
new file mode 100644
index 000000000..d3b37afb8
Binary files /dev/null and b/code/nel/tools/3d/mesh_editor/icons/cross-circle.png differ
diff --git a/code/nel/tools/3d/mesh_editor/icons/exclamation.png b/code/nel/tools/3d/mesh_editor/icons/exclamation.png
new file mode 100644
index 000000000..21fd8f78a
Binary files /dev/null and b/code/nel/tools/3d/mesh_editor/icons/exclamation.png differ
diff --git a/code/nel/tools/3d/mesh_editor/icons/information-white.png b/code/nel/tools/3d/mesh_editor/icons/information-white.png
new file mode 100644
index 000000000..70052384a
Binary files /dev/null and b/code/nel/tools/3d/mesh_editor/icons/information-white.png differ
diff --git a/code/nel/tools/3d/mesh_editor/main_window.cpp b/code/nel/tools/3d/mesh_editor/main_window.cpp
index f6d6260d4..f8d3bb701 100644
--- a/code/nel/tools/3d/mesh_editor/main_window.cpp
+++ b/code/nel/tools/3d/mesh_editor/main_window.cpp
@@ -40,6 +40,7 @@
// Project includes
#include "../shared_widgets/command_log.h"
+#include "../shared_widgets/error_list.h"
#include "graphics_viewport.h"
#include "graphics_config.h"
@@ -54,6 +55,7 @@ CMainWindow::CMainWindow(QWidget *parent, Qt::WindowFlags flags)
m_IsSoundInitialized(false), m_IsSoundEnabled(false),
m_Timer(NULL), m_GraphicsViewport(NULL),
m_CommandLog(NULL), m_CommandLogDock(NULL),
+ m_ErrorList(NULL), m_ErrorListDock(NULL),
m_GraphicsConfig(NULL), m_GraphicsConfigScroll(NULL), m_GraphicsConfigDock(NULL),
m_FileMenu(NULL), m_EditMenu(NULL), m_ViewportMenu(NULL), m_WidgetsMenu(NULL), m_HelpMenu(NULL),
m_FileToolBar(NULL), m_EditToolBar(NULL),
@@ -341,6 +343,17 @@ void CMainWindow::createDockWindows()
m_WidgetsMenu->addAction(m_CommandLogDock->toggleViewAction());
}
+ // ErrorList (Error List)
+ {
+ m_ErrorListDock = new QDockWidget(this);
+ m_ErrorListDock->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
+ m_ErrorList = new CErrorList(m_ErrorListDock);
+ m_ErrorListDock->setWidget(m_ErrorList);
+ addDockWidget(Qt::BottomDockWidgetArea, m_ErrorListDock);
+ m_WidgetsMenu->addAction(m_ErrorListDock->toggleViewAction());
+ tabifyDockWidget(m_CommandLogDock, m_ErrorListDock);
+ }
+
// GraphicsConfig (Graphics Configuration)
{
m_GraphicsConfigDock = new QDockWidget(this);
@@ -381,6 +394,7 @@ void CMainWindow::createDockWindows()
void CMainWindow::translateDockWindows()
{
m_CommandLogDock->setWindowTitle(tr("Console"));
+ m_ErrorListDock->setWindowTitle(tr("Error List"));
m_GraphicsConfigDock->setWindowTitle(tr("Graphics Configuration"));
m_AssetTreeDock->setWindowTitle(tr("Asset Database"));
}
diff --git a/code/nel/tools/3d/mesh_editor/main_window.h b/code/nel/tools/3d/mesh_editor/main_window.h
index 74ecae5a1..36ba41eb1 100644
--- a/code/nel/tools/3d/mesh_editor/main_window.h
+++ b/code/nel/tools/3d/mesh_editor/main_window.h
@@ -63,6 +63,7 @@ namespace NLSOUND {
namespace NLQT {
class CCommandLogDisplayer;
+ class CErrorList;
}
class CGraphicsViewport;
@@ -136,6 +137,9 @@ private:
NLQT::CCommandLogDisplayer *m_CommandLog;
QDockWidget *m_CommandLogDock;
+ NLQT::CErrorList *m_ErrorList;
+ QDockWidget *m_ErrorListDock;
+
CGraphicsConfig *m_GraphicsConfig;
QScrollArea *m_GraphicsConfigScroll;
QDockWidget *m_GraphicsConfigDock;
diff --git a/code/nel/tools/3d/shared_widgets/error_list.cpp b/code/nel/tools/3d/shared_widgets/error_list.cpp
new file mode 100644
index 000000000..caf270992
--- /dev/null
+++ b/code/nel/tools/3d/shared_widgets/error_list.cpp
@@ -0,0 +1,477 @@
+/*
+
+Copyright (C) 2015 by authors
+Author: Jan Boon
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+// Source: https://github.com/kaetemi/errorlist
+
+#include "error_list.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace NLQT {
+
+#define DATAROLE_TYPE (Qt::UserRole + 0)
+#define DATAROLE_TIMESTAMP (Qt::UserRole + 1)
+#define DATAROLE_GROUP (Qt::UserRole + 2)
+#define DATAROLE_MESSAGE (Qt::UserRole + 3)
+#define DATAROLE_LINE (Qt::UserRole + 4)
+#define DATAROLE_USERDATA (Qt::UserRole + 5)
+#define DATAROLE_COLLAPSED (Qt::UserRole + 6)
+
+class CErrorListItem : public QListWidgetItem
+{
+public:
+ CErrorListItem(const QIcon &icon, const QString &message) : QListWidgetItem(icon, message)
+ {
+
+ }
+
+ virtual bool operator<(const QListWidgetItem &other) const Q_DECL_OVERRIDE
+ {
+ time_t this_timestamp = data(DATAROLE_TIMESTAMP).toInt();
+ time_t other_timestamp = other.data(DATAROLE_TIMESTAMP).toInt();
+ return this_timestamp < other_timestamp;
+ }
+};
+
+const char *iconNames[] = {
+ ":/icons/cross-circle.png",
+ ":/icons/exclamation.png",
+ ":/icons/information-white.png"
+};
+
+const char *filterText[] = {
+ "Errors",
+ "Warnings",
+ "Messages"
+};
+
+CErrorList::CErrorList(QWidget *parent) : QWidget(parent), m_Collapse(true), m_CollapseBtn(NULL), m_Message(NULL)
+{
+ m_Filter[0] = true;
+ m_Filter[1] = true;
+ m_Filter[2] = true;
+ m_FilterBtn[0] = NULL;
+ m_FilterBtn[1] = NULL;
+ m_FilterBtn[2] = NULL;
+ m_FilterCounts[0] = 0;
+ m_FilterCounts[1] = 0;
+ m_FilterCounts[2] = 0;
+
+ QVBoxLayout *layout = new QVBoxLayout(this);
+
+ QHBoxLayout *buttons = new QHBoxLayout(this);
+
+ QPushButton *clearBtn = new QPushButton(this);
+ clearBtn->setText(tr("Clear"));
+ connect(clearBtn, SIGNAL(clicked()), this, SLOT(clear()));
+ buttons->addWidget(clearBtn);
+
+ QPushButton *collapseBtn = new QPushButton(this);
+ m_CollapseBtn = collapseBtn;
+ collapseBtn->setText(tr("Collapse"));
+ collapseBtn->setCheckable(true);
+ collapseBtn->setChecked(m_Collapse);
+ connect(collapseBtn, SIGNAL(toggled(bool)), this, SLOT(collapse(bool)));
+ buttons->addWidget(collapseBtn);
+
+ buttons->addStretch();
+
+ QPushButton *errorBtn = new QPushButton(this);
+ m_FilterBtn[0] = errorBtn;
+ errorBtn->setIcon(QIcon(iconNames[0]));
+ updateFilterCount(0);
+ errorBtn->setCheckable(true);
+ errorBtn->setChecked(m_Filter[0]);
+ connect(errorBtn, SIGNAL(toggled(bool)), this, SLOT(filterError(bool)));
+ buttons->addWidget(errorBtn);
+
+ QPushButton *warningBtn = new QPushButton(this);
+ m_FilterBtn[1] = warningBtn;
+ warningBtn->setIcon(QIcon(iconNames[1]));
+ updateFilterCount(1);
+ warningBtn->setCheckable(true);
+ warningBtn->setChecked(m_Filter[1]);
+ connect(warningBtn, SIGNAL(toggled(bool)), this, SLOT(filterWarning(bool)));
+ buttons->addWidget(warningBtn);
+
+ QPushButton *messageBtn = new QPushButton(this);
+ m_FilterBtn[2] = messageBtn;
+ messageBtn->setIcon(QIcon(iconNames[2]));
+ updateFilterCount(2);
+ messageBtn->setCheckable(true);
+ messageBtn->setChecked(m_Filter[2]);
+ connect(messageBtn, SIGNAL(toggled(bool)), this, SLOT(filterMessage(bool)));
+ buttons->addWidget(messageBtn);
+
+ layout->addLayout(buttons);
+
+ QSplitter *splitter = new QSplitter(Qt::Vertical, this);
+
+ m_List = new QListWidget(this);
+ m_List->setTextElideMode(Qt::ElideRight);
+ m_List->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ // m_List->setSortingEnabled(true);
+ connect(m_List, &QListWidget::itemClicked, this, &CErrorList::listItemClicked);
+ connect(m_List, &QListWidget::itemDoubleClicked, this, &CErrorList::listItemDoubleClicked);
+ splitter->addWidget(m_List);
+
+ QTextEdit *messageText = new QTextEdit(this);
+ m_Message = messageText;
+ messageText->setReadOnly(true);
+ messageText->setVisible(false);
+ messageText->setAcceptRichText(true);
+ splitter->addWidget(messageText);
+
+ layout->addWidget(splitter);
+
+ setLayout(layout);
+}
+
+CErrorList::~CErrorList()
+{
+
+}
+
+void CErrorList::clear()
+{
+ m_CurrentMessage = NULL;
+ m_Message->setVisible(false);
+ m_CollapseItems.clear();
+ m_List->clear();
+ m_FilterCounts[0] = 0;
+ m_FilterCounts[1] = 0;
+ m_FilterCounts[2] = 0;
+ updateFilterCount(0);
+ updateFilterCount(1);
+ updateFilterCount(2);
+}
+
+void CErrorList::clear(const QString &group)
+{
+ for (int i = 0; i < m_List->count(); ++i)
+ {
+ QListWidgetItem *item = m_List->item(i);
+ if (item->data(DATAROLE_GROUP).toString() == group)
+ {
+ if (m_CurrentMessage == item)
+ {
+ m_CurrentMessage = NULL;
+ m_Message->setVisible(false);
+ }
+ if (m_Collapse)
+ {
+ QString cs = getCollapseString(item);
+ std::map::iterator it
+ = m_CollapseItems.find(cs);
+ if (it != m_CollapseItems.end())
+ m_CollapseItems.erase(it);
+ }
+ --m_FilterCounts[(int)item->data(DATAROLE_TYPE).toInt()];
+ delete item;
+ --i;
+ }
+ }
+ updateFilterCount(0);
+ updateFilterCount(1);
+ updateFilterCount(2);
+}
+
+void CErrorList::collapse(bool c)
+{
+ if (c != m_Collapse)
+ {
+ m_Collapse = c;
+ m_CollapseBtn->setChecked(c);
+
+ if (c)
+ {
+ for (int i = 0; i < m_List->count(); ++i)
+ {
+ QListWidgetItem *item = m_List->item(i);
+ QString cs = getCollapseString(item);
+ QListWidgetItem *ref = m_CollapseItems[cs];
+
+ if (ref != NULL)
+ {
+ ref->setData(DATAROLE_COLLAPSED, ref->data(DATAROLE_COLLAPSED).toInt() + 1);
+ updateCollapseText(ref);
+ item->setData(DATAROLE_COLLAPSED, 0);
+ item->setHidden(true);
+ if (m_CurrentMessage == item)
+ {
+ m_CurrentMessage = NULL;
+ m_Message->setHidden(true);
+ }
+ }
+ else
+ {
+ m_CollapseItems[cs] = item;
+ }
+ }
+ }
+ else
+ {
+ m_CollapseItems.clear();
+ for (int i = 0; i < m_List->count(); ++i)
+ {
+ int cc = m_List->item(i)->data(DATAROLE_COLLAPSED).toInt();
+ if (cc > 1)
+ {
+ m_List->item(i)->setText(m_List->item(i)->data(DATAROLE_LINE).toString());
+ }
+ if (cc == 0)
+ {
+ bool hide = !m_Filter[m_List->item(i)->data(DATAROLE_TYPE).toInt()];
+ m_List->item(i)->setHidden(hide);
+ }
+ if (cc != 1)
+ {
+ m_List->item(i)->setData(DATAROLE_COLLAPSED, 1);
+ }
+ }
+ }
+ }
+}
+
+void CErrorList::filter(ErrorType type, bool f)
+{
+ if (f != m_Filter[(int)type])
+ {
+ m_Filter[(int)type] = f;
+ m_FilterBtn[(int)type]->setChecked(f);
+
+ for (int i = 0; i < m_List->count(); ++i)
+ {
+ if (m_List->item(i)->data(DATAROLE_TYPE).toInt() == (int)type)
+ {
+ bool hide =
+ m_List->item(i)->data(DATAROLE_COLLAPSED).toInt() == 0
+ || !f;
+ m_List->item(i)->setHidden(hide);
+ if (hide && m_CurrentMessage == m_List->item(i))
+ {
+ m_CurrentMessage = NULL;
+ m_Message->setHidden(true);
+ }
+ }
+ }
+ }
+}
+
+void CErrorList::updateFilterCount(int filter)
+{
+ m_FilterBtn[filter]->setText(QString(" ") + QString::number(m_FilterCounts[filter]) + " " + filterText[filter]);
+}
+
+void CErrorList::filterError(bool f)
+{
+ filter(Error, f);
+}
+
+void CErrorList::filterWarning(bool f)
+{
+ filter(Warning, f);
+}
+
+void CErrorList::filterMessage(bool f)
+{
+ filter(Message, f);
+}
+
+void CErrorList::markClear(const QString &group)
+{
+ for (int i = 0; i < m_List->count(); ++i)
+ {
+ QListWidgetItem *item = m_List->item(i);
+ if (item->data(DATAROLE_GROUP).toString() == group)
+ {
+ QString cs = getCollapseString(item);
+ m_MarkedClear.insert(cs);
+ }
+ }
+}
+
+void CErrorList::clearMarked()
+{
+ for (int i = 0; i < m_List->count(); ++i)
+ {
+ QListWidgetItem *item = m_List->item(i);
+ QString cs = getCollapseString(item);
+ if (m_MarkedClear.find(cs) != m_MarkedClear.end())
+ {
+ if (m_CurrentMessage == item)
+ {
+ m_CurrentMessage = NULL;
+ m_Message->setVisible(false);
+ }
+ if (m_Collapse)
+ {
+ QString cs = getCollapseString(item);
+ std::map::iterator it
+ = m_CollapseItems.find(cs);
+ if (it != m_CollapseItems.end())
+ m_CollapseItems.erase(it);
+ }
+ --m_FilterCounts[(int)item->data(DATAROLE_TYPE).toInt()];
+ delete item;
+ --i;
+ }
+ }
+}
+
+void CErrorList::listItemClicked(QListWidgetItem *item)
+{
+ if (item != m_CurrentMessage)
+ {
+ if (item->isSelected())
+ {
+ QString text = item->data(DATAROLE_MESSAGE).toString();
+ m_Message->setHtml(text);
+ m_Message->setVisible(true);
+ }
+ else
+ {
+ m_CurrentMessage = NULL;
+ m_Message->setVisible(false);
+ }
+ }
+}
+
+void CErrorList::listItemDoubleClicked(QListWidgetItem *item)
+{
+ emit request(item->data(DATAROLE_GROUP).toString(), item->data(DATAROLE_USERDATA).toMap());
+}
+
+QString CErrorList::getCollapseString(QListWidgetItem *item)
+{
+ return item->data(DATAROLE_GROUP).toString() + " / " + item->data(DATAROLE_LINE).toString();
+}
+
+void CErrorList::updateCollapseText(QListWidgetItem *item)
+{
+ item->setText(QString("(" + QString::number(item->data(DATAROLE_COLLAPSED).toInt()) + "x) " + item->data(DATAROLE_LINE).toString()));// .replace('\n', ' ').replace('\r', ' ')));
+}
+
+void CErrorList::add(ErrorType type, const QString &group, time_t timestamp, const QString &message, const QMap &userData)
+{
+ QTextDocument doc;
+ doc.setHtml(message);
+ QString line = doc.toPlainText().replace('\n', ' ').replace('\r', ' ');
+
+ CErrorListItem *item = new CErrorListItem(QIcon(iconNames[(int)type]), line);
+ item->setData(DATAROLE_TYPE, (int)type);
+ item->setData(DATAROLE_TIMESTAMP, timestamp);
+ item->setData(DATAROLE_GROUP, group);
+ item->setData(DATAROLE_MESSAGE, message);
+ item->setData(DATAROLE_LINE, line);
+ item->setData(DATAROLE_USERDATA, userData);
+
+ QString cs = getCollapseString(item);
+ if (m_MarkedClear.find(cs) != m_MarkedClear.end())
+ {
+ m_MarkedClear.erase(cs);
+ return;
+ }
+
+ bool hide = m_Collapse;
+ if (hide)
+ {
+ QListWidgetItem *ref = m_CollapseItems[cs];
+ hide = ref != NULL;
+ if (hide)
+ {
+ ref->setData(DATAROLE_COLLAPSED, ref->data(DATAROLE_COLLAPSED).toInt() + 1);
+ updateCollapseText(ref);
+ item->setData(DATAROLE_COLLAPSED, 0);
+ }
+ else
+ {
+ m_CollapseItems[cs] = item;
+ }
+ }
+ if (!hide)
+ {
+ item->setData(DATAROLE_COLLAPSED, 1);
+ hide = !m_Filter[(int)type];
+ }
+
+ m_List->addItem(item);
+ item->setHidden(hide);
+ ++m_FilterCounts[(int)type];
+ updateFilterCount((int)type);
+}
+
+void CErrorList::update(const QString &group, const QString &message)
+{
+ // NOTE: This does not play well with collapse, so you should only have one message present in the category
+ // Automatically adds a message if no message exists in the category
+ for (int i = 0; i < m_List->count(); ++i)
+ {
+ QListWidgetItem *item = m_List->item(i);
+ if (item->data(DATAROLE_GROUP).toString() == group)
+ {
+ QTextDocument doc;
+ doc.setHtml(message);
+ QString line = doc.toPlainText().replace('\n', ' ').replace('\r', ' ');
+ item->setData(DATAROLE_MESSAGE, message);
+ item->setData(DATAROLE_LINE, line);
+ item->setText(line);
+ return;
+ }
+ }
+ add(Message, group, message);
+}
+
+void CErrorList::add(ErrorType type, const QString &group, time_t timestamp, const QString &message)
+{
+ QMap nullMap;
+ add(type, group, timestamp, message, nullMap);
+}
+
+void CErrorList::add(ErrorType type, const QString &group, const QString &message, const QMap &userData)
+{
+ add(type, group, QDateTime::currentDateTime().toTime_t(), message, userData);
+}
+
+void CErrorList::add(ErrorType type, const QString &group, const QString &message)
+{
+ QMap nullMap;
+ add(type, group, message, nullMap);
+}
+
+}
+
+/* end of file */
\ No newline at end of file
diff --git a/code/nel/tools/3d/shared_widgets/error_list.h b/code/nel/tools/3d/shared_widgets/error_list.h
new file mode 100644
index 000000000..f90f52db5
--- /dev/null
+++ b/code/nel/tools/3d/shared_widgets/error_list.h
@@ -0,0 +1,118 @@
+/*
+
+Copyright (C) 2015 by authors
+Author: Jan Boon
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+// Source: https://github.com/kaetemi/errorlist
+
+#ifndef ERRORLIST_H
+#define ERRORLIST_H
+
+#define ERRORLIST_EXPORT
+
+#include
+#include