diff --git a/dust3d.pro b/dust3d.pro index 3ac8c55b..8c4bb5fb 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -2,10 +2,9 @@ QT += core widgets opengl CONFIG += debug RESOURCES += resources.qrc -INCLUDEPATH += src +include(thirdparty/QtAwesome/QtAwesome/QtAwesome.pri) -SOURCES += src/mainwindow.cpp -HEADERS += src/mainwindow.h +INCLUDEPATH += src SOURCES += src/modelshaderprogram.cpp HEADERS += src/modelshaderprogram.h @@ -13,27 +12,42 @@ HEADERS += src/modelshaderprogram.h SOURCES += src/modelmeshbinder.cpp HEADERS += src/modelmeshbinder.h +SOURCES += src/modelofflinerender.cpp +HEADERS += src/modelofflinerender.h + SOURCES += src/modelwidget.cpp HEADERS += src/modelwidget.h -SOURCES += src/skeletoneditgraphicsview.cpp -HEADERS += src/skeletoneditgraphicsview.h +SOURCES += src/skeletonnodepropertywidget.cpp +HEADERS += src/skeletonnodepropertywidget.h -SOURCES += src/skeletoneditnodeitem.cpp -HEADERS += src/skeletoneditnodeitem.h +SOURCES += src/skeletonedgepropertywidget.cpp +HEADERS += src/skeletonedgepropertywidget.h -SOURCES += src/skeletoneditedgeitem.cpp -HEADERS += src/skeletoneditedgeitem.h +SOURCES += src/skeletondocument.cpp +HEADERS += src/skeletondocument.h -SOURCES += src/skeletontomesh.cpp -HEADERS += src/skeletontomesh.h +SOURCES += src/skeletondocumentwindow.cpp +HEADERS += src/skeletondocumentwindow.h + +SOURCES += src/skeletongraphicswidget.cpp +HEADERS += src/skeletongraphicswidget.h + +SOURCES += src/skeletonhistorylistwidget.cpp +HEADERS += src/skeletonhistorylistwidget.h + +SOURCES += src/skeletonpartlistwidget.cpp +HEADERS += src/skeletonpartlistwidget.h + +SOURCES += src/meshgenerator.cpp +HEADERS += src/meshgenerator.h + +SOURCES += src/util.cpp +HEADERS += src/util.h SOURCES += src/turnaroundloader.cpp HEADERS += src/turnaroundloader.h -SOURCES += src/skeletonwidget.cpp -HEADERS += src/skeletonwidget.h - SOURCES += src/skeletonsnapshot.cpp HEADERS += src/skeletonsnapshot.h @@ -52,6 +66,12 @@ HEADERS += src/mesh.h SOURCES += src/unionmesh.cpp HEADERS += src/unionmesh.h +SOURCES += src/logbrowser.cpp +HEADERS += src/logbrowser.h + +SOURCES += src/logbrowserdialog.cpp +HEADERS += src/logbrowserdialog.h + SOURCES += src/main.cpp INCLUDEPATH += ../meshlite/include diff --git a/src/logbrowser.cpp b/src/logbrowser.cpp new file mode 100644 index 00000000..431d8ed5 --- /dev/null +++ b/src/logbrowser.cpp @@ -0,0 +1,24 @@ +#include "logbrowser.h" +// Modified from https://wiki.qt.io/Browser_for_QDebug_output +#include +#include "logbrowserdialog.h" + +LogBrowser::LogBrowser(QObject *parent) : + QObject(parent) +{ + qRegisterMetaType("QtMsgType"); + m_browserDialog = new LogBrowserDialog; + connect(this, SIGNAL(sendMessage(QtMsgType,QString)), m_browserDialog, SLOT(outputMessage(QtMsgType,QString)), Qt::QueuedConnection); + m_browserDialog->show(); +} + +LogBrowser::~LogBrowser() +{ + delete m_browserDialog; +} + +void LogBrowser::outputMessage(QtMsgType type, const QString &msg) +{ + printf("%s\n", msg.toUtf8().constData()); + emit sendMessage( type, msg ); +} diff --git a/src/logbrowser.h b/src/logbrowser.h new file mode 100644 index 00000000..bc8df370 --- /dev/null +++ b/src/logbrowser.h @@ -0,0 +1,25 @@ +#ifndef LOG_BROWSER_H +#define LOG_BROWSER_H +// Modified from https://wiki.qt.io/Browser_for_QDebug_output +#include + +class LogBrowserDialog; + +class LogBrowser : public QObject +{ + Q_OBJECT +public: + explicit LogBrowser(QObject *parent = 0); + ~LogBrowser(); + +public slots: + void outputMessage(QtMsgType type, const QString &msg); + +signals: + void sendMessage(QtMsgType type, const QString &msg); + +private: + LogBrowserDialog *m_browserDialog; +}; + +#endif // LOGBROWSER_H \ No newline at end of file diff --git a/src/logbrowserdialog.cpp b/src/logbrowserdialog.cpp new file mode 100644 index 00000000..d3958ff2 --- /dev/null +++ b/src/logbrowserdialog.cpp @@ -0,0 +1,120 @@ +#include "logbrowserdialog.h" +// Modified from https://wiki.qt.io/Browser_for_QDebug_output +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LogBrowserDialog::LogBrowserDialog(QWidget *parent) : + QDialog(parent) +{ + QVBoxLayout *layout = new QVBoxLayout; + setLayout(layout); + + m_browser = new QTextBrowser(this); + layout->addWidget(m_browser); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->setContentsMargins(0, 0, 0, 0); + layout->addLayout(buttonLayout); + + buttonLayout->addStretch(10); + + m_clearButton = new QPushButton(this); + m_clearButton->setText("Clear"); + buttonLayout->addWidget(m_clearButton); + connect(m_clearButton, SIGNAL(clicked()), m_browser, SLOT(clear())); + + m_saveButton = new QPushButton(this); + m_saveButton->setText("Save Output"); + buttonLayout->addWidget(m_saveButton); + connect(m_saveButton, SIGNAL(clicked()), this, SLOT(save())); + + resize(400, 300); +} + + +LogBrowserDialog::~LogBrowserDialog() +{ + +} + + +void LogBrowserDialog::outputMessage(QtMsgType type, const QString &msg) +{ + switch (type) { + case QtDebugMsg: + m_browser->append(tr("— DEBUG: %1").arg(msg)); + break; + + case QtWarningMsg: + m_browser->append(tr("— WARNING: %1").arg(msg)); + break; + + case QtCriticalMsg: + m_browser->append(tr("— CRITICAL: %1").arg(msg)); + break; + + case QtInfoMsg: + m_browser->append(tr("— INFO: %1").arg(msg)); + break; + + case QtFatalMsg: + m_browser->append(tr("— FATAL: %1").arg(msg)); + break; + } +} + + +void LogBrowserDialog::save() +{ + QString saveFileName = QFileDialog::getSaveFileName(this, + tr("Save Log Output"), + tr("%1/logfile.txt").arg(QDir::homePath()), + tr("Text Files (*.txt);;All Files (*)")); + + if (saveFileName.isEmpty()) + return; + + QFile file(saveFileName); + if (!file.open(QIODevice::WriteOnly)) { + QMessageBox::warning(this, + tr("Error"), + QString(tr("File '%1'
cannot be opened for writing.

" + "The log output could not be saved!
")) + .arg(saveFileName)); + return; + } + + QTextStream stream(&file); + stream << m_browser->toPlainText(); + file.close(); +} + +void LogBrowserDialog::closeEvent(QCloseEvent *e) +{ + QMessageBox::StandardButton answer = QMessageBox::question(this, + tr("Close Log Browser?"), + tr("Do you really want to close the log browser?"), + QMessageBox::Yes | QMessageBox::No); + + if (answer == QMessageBox::Yes) + e->accept(); + else + e->ignore(); +} + +void LogBrowserDialog::keyPressEvent(QKeyEvent *e) +{ + // ignore all keyboard events + // protects against accidentally closing of the dialog + // without asking the user + e->ignore(); +} diff --git a/src/logbrowserdialog.h b/src/logbrowserdialog.h new file mode 100644 index 00000000..0d0e6ccb --- /dev/null +++ b/src/logbrowserdialog.h @@ -0,0 +1,31 @@ +#ifndef LOG_BROWSER_DIALOG_H +#define LOG_BROWSER_DIALOG_H +// Modified from https://wiki.qt.io/Browser_for_QDebug_output +#include + +class QTextBrowser; +class QPushButton; + +class LogBrowserDialog : public QDialog +{ + Q_OBJECT +public: + LogBrowserDialog(QWidget *parent = 0); + ~LogBrowserDialog(); + +public slots: + void outputMessage(QtMsgType type, const QString &msg); + +protected slots: + void save(); + +protected: + virtual void keyPressEvent(QKeyEvent *e); + virtual void closeEvent(QCloseEvent *e); + + QTextBrowser *m_browser; + QPushButton *m_clearButton; + QPushButton *m_saveButton; +}; + +#endif // DIALOG_H \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 10d15643..5e04e35e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,8 +2,20 @@ #include #include #include -#include "mainwindow.h" -#include "meshlite.h" +#include +#include +#include +#include "logbrowser.h" +#include "skeletondocumentwindow.h" +#include "theme.h" + +QPointer g_logBrowser; + +void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + if (g_logBrowser) + g_logBrowser->outputMessage(type, msg); +} int main(int argc, char ** argv) { @@ -24,8 +36,8 @@ int main(int argc, char ** argv) darkPalette.setColor(QPalette::ButtonText, QColor(239,239,239)); darkPalette.setColor(QPalette::BrightText, Qt::red); darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); - darkPalette.setColor(QPalette::Highlight, QColor(252, 102, 33)); - darkPalette.setColor(QPalette::HighlightedText, Qt::black); + darkPalette.setColor(QPalette::Highlight, Theme::white); + darkPalette.setColor(QPalette::HighlightedText, Qt::white); qApp->setPalette(darkPalette); qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #fc6621; border: 1px solid white; }"); @@ -36,8 +48,11 @@ int main(int argc, char ** argv) font.setPixelSize(9); font.setBold(false); QApplication::setFont(font); + + g_logBrowser = new LogBrowser; + qInstallMessageHandler(&outputMessage); - MainWindow mainWindow; + SkeletonDocumentWindow mainWindow; mainWindow.showMaximized(); return app.exec(); } diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp new file mode 100644 index 00000000..07b337b9 --- /dev/null +++ b/src/meshgenerator.cpp @@ -0,0 +1,230 @@ +#include +#include +#include "meshgenerator.h" +#include "util.h" +#include "skeletondocument.h" +#include "meshlite.h" +#include "modelofflinerender.h" +#include "unionmesh.h" +#include "theme.h" + +MeshGenerator::MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread) : + m_snapshot(snapshot), + m_mesh(nullptr), + m_preview(nullptr), + m_requirePreview(false), + m_previewRender(nullptr), + m_thread(thread) +{ +} + +MeshGenerator::~MeshGenerator() +{ + delete m_snapshot; + delete m_mesh; + delete m_preview; + for (const auto &partPreviewIt: m_partPreviewMap) { + delete partPreviewIt.second; + } + for (const auto &render: m_partPreviewRenderMap) { + delete render.second; + } + delete m_previewRender; +} + +void MeshGenerator::addPreviewRequirement() +{ + m_requirePreview = true; + if (nullptr == m_previewRender) { + m_previewRender = new ModelOfflineRender; + m_previewRender->setRenderThread(m_thread); + } +} + +void MeshGenerator::addPartPreviewRequirement(const QString &partId) +{ + qDebug() << "addPartPreviewRequirement:" << partId; + m_requirePartPreviewMap.insert(partId); + if (m_partPreviewRenderMap.find(partId) == m_partPreviewRenderMap.end()) { + ModelOfflineRender *render = new ModelOfflineRender; + render->setRenderThread(m_thread); + m_partPreviewRenderMap[partId] = render; + } +} + +Mesh *MeshGenerator::takeResultMesh() +{ + Mesh *resultMesh = m_mesh; + m_mesh = nullptr; + return resultMesh; +} + +QImage *MeshGenerator::takePreview() +{ + QImage *resultPreview = m_preview; + m_preview = nullptr; + return resultPreview; +} + +QImage *MeshGenerator::takePartPreview(const QString &partId) +{ + QImage *resultImage = m_partPreviewMap[partId]; + m_partPreviewMap[partId] = nullptr; + return resultImage; +} + +void MeshGenerator::resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile) +{ + float left = -1; + float right = -1; + float top = -1; + float bottom = -1; + float zLeft = -1; + float zRight = -1; + for (const auto &nodeIt: m_snapshot->nodes) { + float radius = valueOfKeyInMapOrEmpty(nodeIt.second, "radius").toFloat(); + float x = valueOfKeyInMapOrEmpty(nodeIt.second, "x").toFloat(); + float y = valueOfKeyInMapOrEmpty(nodeIt.second, "y").toFloat(); + float z = valueOfKeyInMapOrEmpty(nodeIt.second, "z").toFloat(); + if (left < 0 || x - radius < left) { + left = x - radius; + } + if (top < 0 || y - radius < top) { + top = y - radius; + } + if (x + radius > right) { + right = x + radius; + } + if (y + radius > bottom) { + bottom = y + radius; + } + if (zLeft < 0 || z - radius < zLeft) { + zLeft = z - radius; + } + if (z + radius > zRight) { + zRight = z + radius; + } + } + *mainProfile = QRectF(left, top, right - left, bottom - top); + *sideProfile = QRectF(zLeft, top, zRight - zLeft, bottom - top); +} + +void MeshGenerator::process() +{ + if (nullptr == m_snapshot) + return; + + void *meshliteContext = meshlite_create_context(); + std::map partBmeshMap; + std::map bmeshNodeMap; + + QRectF mainProfile, sideProfile; + resolveBoundingBox(&mainProfile, &sideProfile); + float longHeight = mainProfile.height(); + if (mainProfile.width() > longHeight) + longHeight = mainProfile.width(); + if (sideProfile.width() > longHeight) + longHeight = sideProfile.width(); + float mainProfileMiddleX = mainProfile.x() + mainProfile.width() / 2; + float sideProfileMiddleX = sideProfile.x() + sideProfile.width() / 2; + float mainProfileMiddleY = mainProfile.y() + mainProfile.height() / 2; + + for (const auto &partIdIt: m_snapshot->partIdList) { + int bmeshId = meshlite_bmesh_create(meshliteContext); + partBmeshMap[partIdIt] = bmeshId; + } + + for (const auto &edgeIt: m_snapshot->edges) { + QString partId = valueOfKeyInMapOrEmpty(edgeIt.second, "partId"); + QString fromNodeId = valueOfKeyInMapOrEmpty(edgeIt.second, "from"); + QString toNodeId = valueOfKeyInMapOrEmpty(edgeIt.second, "to"); + qDebug() << "Processing edge " << fromNodeId << "<=>" << toNodeId; + const auto fromIt = m_snapshot->nodes.find(fromNodeId); + const auto toIt = m_snapshot->nodes.find(toNodeId); + if (fromIt == m_snapshot->nodes.end() || toIt == m_snapshot->nodes.end()) + continue; + const auto partBmeshIt = partBmeshMap.find(partId); + if (partBmeshIt == partBmeshMap.end()) + continue; + int bmeshId = partBmeshIt->second; + + int bmeshFromNodeId = 0; + const auto bmeshFromIt = bmeshNodeMap.find(fromNodeId); + if (bmeshFromIt == bmeshNodeMap.end()) { + float radius = valueOfKeyInMapOrEmpty(fromIt->second, "radius").toFloat() / longHeight; + float x = (valueOfKeyInMapOrEmpty(fromIt->second, "x").toFloat() - mainProfileMiddleX) / longHeight; + float y = (valueOfKeyInMapOrEmpty(fromIt->second, "y").toFloat() - mainProfileMiddleY) / longHeight; + float z = (valueOfKeyInMapOrEmpty(fromIt->second, "z").toFloat() - sideProfileMiddleX) / longHeight; + bmeshFromNodeId = meshlite_bmesh_add_node(meshliteContext, bmeshId, x, y, z, radius); + qDebug() << "bmeshId[" << bmeshId << "] add node[" << bmeshFromNodeId << "]" << radius << x << y << z; + bmeshNodeMap[fromNodeId] = bmeshFromNodeId; + } else { + bmeshFromNodeId = bmeshFromIt->second; + qDebug() << "bmeshId[" << bmeshId << "] use existed node[" << bmeshFromNodeId << "]"; + } + + int bmeshToNodeId = 0; + const auto bmeshToIt = bmeshNodeMap.find(toNodeId); + if (bmeshToIt == bmeshNodeMap.end()) { + float radius = valueOfKeyInMapOrEmpty(toIt->second, "radius").toFloat() / longHeight; + float x = (valueOfKeyInMapOrEmpty(toIt->second, "x").toFloat() - mainProfileMiddleX) / longHeight; + float y = (valueOfKeyInMapOrEmpty(toIt->second, "y").toFloat() - mainProfileMiddleY) / longHeight; + float z = (valueOfKeyInMapOrEmpty(toIt->second, "z").toFloat() - sideProfileMiddleX) / longHeight; + bmeshToNodeId = meshlite_bmesh_add_node(meshliteContext, bmeshId, x, y, z, radius); + qDebug() << "bmeshId[" << bmeshId << "] add node[" << bmeshToNodeId << "]" << radius << x << y << z; + bmeshNodeMap[toNodeId] = bmeshToNodeId; + } else { + bmeshToNodeId = bmeshToIt->second; + qDebug() << "bmeshId[" << bmeshId << "] use existed node[" << bmeshToNodeId << "]"; + } + + meshlite_bmesh_add_edge(meshliteContext, bmeshId, bmeshFromNodeId, bmeshToNodeId); + } + + std::map partMeshMap; + std::vector meshIds; + for (const auto &partIdIt: m_snapshot->partIdList) { + int bmeshId = partBmeshMap[partIdIt]; + int meshId = meshlite_bmesh_generate_mesh(meshliteContext, bmeshId, 0); + qDebug() << "m_requirePartPreviewMap.find:" << partIdIt; + if (m_requirePartPreviewMap.find(partIdIt) != m_requirePartPreviewMap.end()) { + ModelOfflineRender *render = m_partPreviewRenderMap[partIdIt]; + render->updateMesh(new Mesh(meshliteContext, meshId)); + QImage *image = new QImage(render->toImage(QSize(Theme::previewImageSize, Theme::previewImageSize))); + m_partPreviewMap[partIdIt] = image; + } + int triangulatedMeshId = meshlite_triangulate(meshliteContext, meshId); + meshIds.push_back(triangulatedMeshId); + } + + int mergedMeshId = 0; + if (meshIds.size() > 0) { + mergedMeshId = unionMeshs(meshliteContext, meshIds); + if (mergedMeshId > 0) { + mergedMeshId = meshlite_combine_coplanar_faces(meshliteContext, mergedMeshId); + } + } + + if (mergedMeshId > 0) { + if (m_requirePreview) { + m_previewRender->updateMesh(new Mesh(meshliteContext, mergedMeshId)); + QImage *image = new QImage(m_previewRender->toImage(QSize(Theme::previewImageSize, Theme::previewImageSize))); + m_preview = image; + } + m_mesh = new Mesh(meshliteContext, mergedMeshId); + } + + if (m_previewRender) { + m_previewRender->setRenderThread(QGuiApplication::instance()->thread()); + } + + for (auto &partPreviewRender: m_partPreviewRenderMap) { + partPreviewRender.second->setRenderThread(QGuiApplication::instance()->thread()); + } + + meshlite_destroy_context(meshliteContext); + + this->moveToThread(QGuiApplication::instance()->thread()); + + emit finished(); +} diff --git a/src/meshgenerator.h b/src/meshgenerator.h new file mode 100644 index 00000000..42f9a8e5 --- /dev/null +++ b/src/meshgenerator.h @@ -0,0 +1,42 @@ +#ifndef MESH_GENERATOR_H +#define MESH_GENERATOR_H +#include +#include +#include +#include +#include +#include +#include "skeletonsnapshot.h" +#include "mesh.h" +#include "modelofflinerender.h" + +class MeshGenerator : public QObject +{ + Q_OBJECT +public: + MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread); + ~MeshGenerator(); + void addPreviewRequirement(); + void addPartPreviewRequirement(const QString &partId); + Mesh *takeResultMesh(); + QImage *takePreview(); + QImage *takePartPreview(const QString &partId); +signals: + void finished(); +public slots: + void process(); +private: + SkeletonSnapshot *m_snapshot; + Mesh *m_mesh; + QImage *m_preview; + std::map m_partPreviewMap; + bool m_requirePreview; + std::set m_requirePartPreviewMap; + ModelOfflineRender *m_previewRender; + std::map m_partPreviewRenderMap; + QThread *m_thread; +private: + void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile); +}; + +#endif diff --git a/src/modelofflinerender.cpp b/src/modelofflinerender.cpp new file mode 100644 index 00000000..b1b74e15 --- /dev/null +++ b/src/modelofflinerender.cpp @@ -0,0 +1,107 @@ +#include +#include +#include +#include "modelofflinerender.h" + +ModelOfflineRender::ModelOfflineRender(QScreen *targetScreen) : + QOffscreenSurface(targetScreen), + m_context(nullptr), + m_mesh(nullptr) +{ + create(); + m_context = new QOpenGLContext(); + m_context->setFormat(format()); + m_context->create(); +} + +ModelOfflineRender::~ModelOfflineRender() +{ + delete m_context; + m_context = nullptr; + destroy(); + delete m_mesh; +} + +void ModelOfflineRender::updateMesh(Mesh *mesh) +{ + delete m_mesh; + m_mesh = mesh; +} + +void ModelOfflineRender::setRenderThread(QThread *thread) +{ + m_context->moveToThread(thread); +} + +QImage ModelOfflineRender::toImage(const QSize &size) +{ + QImage image; + + m_context->makeCurrent(this); + + QOpenGLFramebufferObjectFormat format; + format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + QOpenGLFramebufferObject *renderFbo = new QOpenGLFramebufferObject(size, format); + renderFbo->bind(); + m_context->functions()->glViewport(0, 0, size.width(), size.height()); + + if (nullptr != m_mesh) { + int xRot = 0; + int yRot = 0; + int zRot = 0; + QMatrix4x4 proj; + QMatrix4x4 camera; + QMatrix4x4 world; + + ModelShaderProgram *program = new ModelShaderProgram; + ModelMeshBinder meshBinder; + meshBinder.initialize(); + + program->setUniformValue(program->lightPosLoc(), QVector3D(0, 0, 70)); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glEnable(GL_LINE_SMOOTH); + + camera.setToIdentity(); + camera.translate(0, 0, -2.5); + + world.setToIdentity(); + world.rotate(180.0f - (xRot / 16.0f), 1, 0, 0); + world.rotate(yRot / 16.0f, 0, 1, 0); + world.rotate(zRot / 16.0f, 0, 0, 1); + + proj.setToIdentity(); + proj.perspective(45.0f, GLfloat(size.width()) / size.height(), 0.01f, 100.0f); + + program->bind(); + program->setUniformValue(program->projMatrixLoc(), proj); + program->setUniformValue(program->mvMatrixLoc(), camera * world); + QMatrix3x3 normalMatrix = world.normalMatrix(); + program->setUniformValue(program->normalMatrixLoc(), normalMatrix); + + meshBinder.updateMesh(m_mesh); + meshBinder.paint(); + + meshBinder.cleanup(); + + program->release(); + delete program; + + m_mesh = nullptr; + } + + m_context->functions()->glFlush(); + + image = renderFbo->toImage(); + + qDebug() << "Generated image size:" << image.size(); + + renderFbo->bindDefault(); + delete renderFbo; + + m_context->doneCurrent(); + + return image; +} diff --git a/src/modelofflinerender.h b/src/modelofflinerender.h new file mode 100644 index 00000000..04896f09 --- /dev/null +++ b/src/modelofflinerender.h @@ -0,0 +1,26 @@ +#ifndef MODEL_OFFLINE_RENDER_H +#define MODEL_OFFLINE_RENDER_H +#include +#include +#include +#include +#include +#include +#include "modelshaderprogram.h" +#include "modelmeshbinder.h" +#include "mesh.h" + +class ModelOfflineRender : QOffscreenSurface +{ +public: + ModelOfflineRender(QScreen *targetScreen = Q_NULLPTR); + ~ModelOfflineRender(); + void setRenderThread(QThread *thread); + void updateMesh(Mesh *mesh); + QImage toImage(const QSize &size); +private: + QOpenGLContext *m_context; + Mesh *m_mesh; +}; + +#endif diff --git a/src/modelwidget.cpp b/src/modelwidget.cpp index 511a2ab4..d46f6feb 100644 --- a/src/modelwidget.cpp +++ b/src/modelwidget.cpp @@ -1,6 +1,6 @@ #include "modelwidget.h" #include "ds3file.h" -#include "skeletoneditgraphicsview.h" +#include "skeletongraphicswidget.h" #include #include #include @@ -16,9 +16,9 @@ ModelWidget::ModelWidget(QWidget *parent) m_xRot(0), m_yRot(0), m_zRot(0), - m_program(NULL), + m_program(nullptr), m_moveStarted(false), - m_graphicsView(NULL) + m_graphicsFunctions(NULL) { // --transparent causes the clear color to be transparent. Therefore, on systems that // support it, the widget will become transparent apart from the logo. @@ -34,11 +34,12 @@ ModelWidget::ModelWidget(QWidget *parent) fmt.setSamples(4); setFormat(fmt); } + setMouseTracking(true); } -void ModelWidget::setGraphicsView(SkeletonEditGraphicsView *view) +void ModelWidget::setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions) { - m_graphicsView = view; + m_graphicsFunctions = graphicsFunctions; } ModelWidget::~ModelWidget() @@ -91,7 +92,7 @@ void ModelWidget::cleanup() makeCurrent(); m_meshBinder.cleanup(); delete m_program; - m_program = 0; + m_program = nullptr; doneCurrent(); } @@ -124,7 +125,7 @@ void ModelWidget::initializeGL() // Our camera never changes in this example. m_camera.setToIdentity(); - m_camera.translate(0, 0, -2.5); + m_camera.translate(0, 0, -3.5); // Light position is fixed. m_program->setUniformValue(m_program->lightPosLoc(), QVector3D(0, 0, 70)); @@ -164,10 +165,9 @@ void ModelWidget::resizeGL(int w, int h) void ModelWidget::mousePressEvent(QMouseEvent *event) { - if (!m_moveStarted && m_graphicsView && m_graphicsView->mousePress(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos())))) + if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->mousePress(event)) return; m_lastPos = event->pos(); - setMouseTracking(true); if (event->button() == Qt::LeftButton) { if (!m_moveStarted) { m_moveStartPos = mapToParent(event->pos()); @@ -179,7 +179,7 @@ void ModelWidget::mousePressEvent(QMouseEvent *event) void ModelWidget::mouseReleaseEvent(QMouseEvent *event) { - if (!m_moveStarted && m_graphicsView && m_graphicsView->mouseRelease(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos())))) + if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->mouseRelease(event)) return; if (m_moveStarted) { m_moveStarted = false; @@ -188,7 +188,7 @@ void ModelWidget::mouseReleaseEvent(QMouseEvent *event) void ModelWidget::mouseMoveEvent(QMouseEvent *event) { - if (!m_moveStarted && m_graphicsView && m_graphicsView->mouseMove(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos())))) + if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->mouseMove(event)) return; int dx = event->x() - m_lastPos.x(); @@ -210,7 +210,7 @@ void ModelWidget::mouseMoveEvent(QMouseEvent *event) void ModelWidget::wheelEvent(QWheelEvent *event) { - if (!m_moveStarted && m_graphicsView && m_graphicsView->wheel(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos())))) + if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->wheel(event)) return; if (m_moveStarted) return; diff --git a/src/modelwidget.h b/src/modelwidget.h index a7c35366..475ce3d5 100644 --- a/src/modelwidget.h +++ b/src/modelwidget.h @@ -11,7 +11,7 @@ #include "modelshaderprogram.h" #include "modelmeshbinder.h" -class SkeletonEditGraphicsView; +class SkeletonGraphicsFunctions; class ModelWidget : public QOpenGLWidget, protected QOpenGLFunctions { @@ -26,7 +26,7 @@ public: void updateMesh(Mesh *mesh); void exportMeshAsObj(const QString &filename); - void setGraphicsView(SkeletonEditGraphicsView *view); + void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions); public slots: void setXRotation(int angle); @@ -61,7 +61,7 @@ private: bool m_moveStarted; QPoint m_moveStartPos; QRect m_moveStartGeometry; - SkeletonEditGraphicsView *m_graphicsView; + SkeletonGraphicsFunctions *m_graphicsFunctions; }; #endif diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp new file mode 100644 index 00000000..c24d9a30 --- /dev/null +++ b/src/skeletondocument.cpp @@ -0,0 +1,706 @@ +#include +#include +#include +#include +#include "skeletondocument.h" +#include "util.h" + +SkeletonDocument::SkeletonDocument() : + m_resultMeshIsObsolete(false), + m_resultMesh(nullptr), + m_meshGenerator(nullptr) +{ +} + +SkeletonDocument::~SkeletonDocument() +{ + delete m_resultMesh; +} + +void SkeletonDocument::uiReady() +{ + emit editModeChanged(); +} + +void SkeletonDocument::removePart(QUuid partId) +{ +} + +void SkeletonDocument::showPart(QUuid partId) +{ +} + +void SkeletonDocument::hidePart(QUuid partId) +{ +} + +void SkeletonDocument::removeEdge(QUuid edgeId) +{ + const SkeletonEdge *edge = findEdge(edgeId); + if (nullptr == edge) { + qDebug() << "Find edge failed:" << edgeId; + return; + } + const SkeletonPart *oldPart = findPart(edge->partId); + if (nullptr == oldPart) { + qDebug() << "Find part failed:" << edge->partId; + return; + } + QString nextPartName = oldPart->name; + QUuid oldPartId = oldPart->id; + std::vector> groups; + splitPartByEdge(&groups, edgeId); + for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) { + SkeletonPart part; + part.name = nextPartName; + for (auto nodeIdIt = (*groupIt).begin(); nodeIdIt != (*groupIt).end(); nodeIdIt++) { + auto nodeIt = nodeMap.find(*nodeIdIt); + if (nodeIt == nodeMap.end()) { + qDebug() << "Find node failed:" << *nodeIdIt; + continue; + } + nodeIt->second.partId = part.id; + part.nodeIds.push_back(nodeIt->first); + for (auto edgeIdIt = nodeIt->second.edgeIds.begin(); edgeIdIt != nodeIt->second.edgeIds.end(); edgeIdIt++) { + auto edgeIt = edgeMap.find(*edgeIdIt); + if (edgeIt == edgeMap.end()) { + qDebug() << "Find edge failed:" << *edgeIdIt; + continue; + } + edgeIt->second.partId = part.id; + } + } + partMap[part.id] = part; + partIds.push_back(part.id); + emit partAdded(part.id); + } + for (auto nodeIdIt = edge->nodeIds.begin(); nodeIdIt != edge->nodeIds.end(); nodeIdIt++) { + auto nodeIt = nodeMap.find(*nodeIdIt); + if (nodeIt == nodeMap.end()) { + qDebug() << "Find node failed:" << *nodeIdIt; + continue; + } + nodeIt->second.edgeIds.erase(std::remove(nodeIt->second.edgeIds.begin(), nodeIt->second.edgeIds.end(), edgeId), nodeIt->second.edgeIds.end()); + emit nodeOriginChanged(nodeIt->first); + } + edgeMap.erase(edgeId); + emit edgeRemoved(edgeId); + partIds.erase(std::remove(partIds.begin(), partIds.end(), oldPartId), partIds.end()); + partMap.erase(oldPartId); + emit partRemoved(oldPartId); + emit partListChanged(); + + emit skeletonChanged(); +} + +void SkeletonDocument::removeNode(QUuid nodeId) +{ + const SkeletonNode *node = findNode(nodeId); + if (nullptr == node) { + qDebug() << "Find node failed:" << nodeId; + return; + } + const SkeletonPart *oldPart = findPart(node->partId); + if (nullptr == oldPart) { + qDebug() << "Find part failed:" << node->partId; + return; + } + QString nextPartName = oldPart->name; + QUuid oldPartId = oldPart->id; + std::vector> groups; + splitPartByNode(&groups, nodeId); + for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) { + SkeletonPart part; + part.name = nextPartName; + for (auto nodeIdIt = (*groupIt).begin(); nodeIdIt != (*groupIt).end(); nodeIdIt++) { + auto nodeIt = nodeMap.find(*nodeIdIt); + if (nodeIt == nodeMap.end()) { + qDebug() << "Find node failed:" << *nodeIdIt; + continue; + } + nodeIt->second.partId = part.id; + part.nodeIds.push_back(nodeIt->first); + for (auto edgeIdIt = nodeIt->second.edgeIds.begin(); edgeIdIt != nodeIt->second.edgeIds.end(); edgeIdIt++) { + auto edgeIt = edgeMap.find(*edgeIdIt); + if (edgeIt == edgeMap.end()) { + qDebug() << "Find edge failed:" << *edgeIdIt; + continue; + } + edgeIt->second.partId = part.id; + } + } + partMap[part.id] = part; + partIds.push_back(part.id); + emit partAdded(part.id); + } + for (auto edgeIdIt = node->edgeIds.begin(); edgeIdIt != node->edgeIds.end(); edgeIdIt++) { + auto edgeIt = edgeMap.find(*edgeIdIt); + if (edgeIt == edgeMap.end()) { + qDebug() << "Find edge failed:" << *edgeIdIt; + continue; + } + QUuid neighborId = edgeIt->second.neighborOf(nodeId); + auto nodeIt = nodeMap.find(neighborId); + if (nodeIt == nodeMap.end()) { + qDebug() << "Find node failed:" << neighborId; + continue; + } + nodeIt->second.edgeIds.erase(std::remove(nodeIt->second.edgeIds.begin(), nodeIt->second.edgeIds.end(), *edgeIdIt), nodeIt->second.edgeIds.end()); + edgeMap.erase(*edgeIdIt); + emit edgeRemoved(*edgeIdIt); + } + nodeMap.erase(nodeId); + emit nodeRemoved(nodeId); + partIds.erase(std::remove(partIds.begin(), partIds.end(), oldPartId), partIds.end()); + partMap.erase(oldPartId); + emit partRemoved(oldPartId); + emit partListChanged(); + + emit skeletonChanged(); +} + +void SkeletonDocument::addNode(float x, float y, float z, float radius, QUuid fromNodeId) +{ + QUuid partId; + const SkeletonNode *fromNode = nullptr; + bool newPartAdded = false; + if (fromNodeId.isNull()) { + SkeletonPart part; + partMap[part.id] = part; + partIds.push_back(part.id); + partId = part.id; + emit partAdded(partId); + newPartAdded = true; + } else { + fromNode = findNode(fromNodeId); + if (nullptr == fromNode) { + qDebug() << "Find node failed:" << fromNodeId; + return; + } + partId = fromNode->partId; + } + SkeletonNode node; + node.partId = partId; + node.radius = radius; + node.x = x; + node.y = y; + node.z = z; + nodeMap[node.id] = node; + partMap[partId].nodeIds.push_back(node.id); + + qDebug() << "Add node " << node.id << x << y << z; + + emit nodeAdded(node.id); + + if (nullptr != fromNode) { + SkeletonEdge edge; + edge.partId = partId; + edge.nodeIds.push_back(fromNode->id); + edge.nodeIds.push_back(node.id); + edgeMap[edge.id] = edge; + + nodeMap[node.id].edgeIds.push_back(edge.id); + nodeMap[fromNode->id].edgeIds.push_back(edge.id); + + emit edgeAdded(edge.id); + } + + emit partChanged(partId); + + if (newPartAdded) + emit partListChanged(); + + emit skeletonChanged(); +} + +const SkeletonEdge *SkeletonDocument::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const +{ + const SkeletonNode *firstNode = nullptr; + firstNode = findNode(firstNodeId); + if (nullptr == firstNode) { + qDebug() << "Find node failed:" << firstNodeId; + return nullptr; + } + for (auto edgeIdIt = firstNode->edgeIds.begin(); edgeIdIt != firstNode->edgeIds.end(); edgeIdIt++) { + auto edgeIt = edgeMap.find(*edgeIdIt); + if (edgeIt == edgeMap.end()) { + qDebug() << "Find edge failed:" << *edgeIdIt; + continue; + } + if (std::find(edgeIt->second.nodeIds.begin(), edgeIt->second.nodeIds.end(), secondNodeId) != edgeIt->second.nodeIds.end()) + return &edgeIt->second; + } + return nullptr; +} + +void SkeletonDocument::addEdge(QUuid fromNodeId, QUuid toNodeId) +{ + const SkeletonNode *fromNode = nullptr; + const SkeletonNode *toNode = nullptr; + bool toPartRemoved = false; + + fromNode = findNode(fromNodeId); + if (nullptr == fromNode) { + qDebug() << "Find node failed:" << fromNodeId; + return; + } + + toNode = findNode(toNodeId); + if (nullptr == toNode) { + qDebug() << "Find node failed:" << toNodeId; + return; + } + + QUuid toPartId = toNode->partId; + + auto fromPart = partMap.find(fromNode->partId); + if (fromPart == partMap.end()) { + qDebug() << "Find part failed:" << fromNode->partId; + return; + } + + if (fromNode->partId != toNode->partId) { + toPartRemoved = true; + std::vector toGroup; + std::set visitMap; + joinNodeAndNeiborsToGroup(&toGroup, toNodeId, &visitMap); + for (auto nodeIdIt = toGroup.begin(); nodeIdIt != toGroup.end(); nodeIdIt++) { + auto nodeIt = nodeMap.find(*nodeIdIt); + if (nodeIt == nodeMap.end()) { + qDebug() << "Find node failed:" << *nodeIdIt; + continue; + } + nodeIt->second.partId = fromNode->partId; + fromPart->second.nodeIds.push_back(nodeIt->first); + for (auto edgeIdIt = nodeIt->second.edgeIds.begin(); edgeIdIt != nodeIt->second.edgeIds.end(); edgeIdIt++) { + auto edgeIt = edgeMap.find(*edgeIdIt); + if (edgeIt == edgeMap.end()) { + qDebug() << "Find edge failed:" << *edgeIdIt; + continue; + } + edgeIt->second.partId = fromNode->partId; + } + } + } + + SkeletonEdge edge; + edge.partId = fromNode->partId; + edge.nodeIds.push_back(fromNode->id); + edge.nodeIds.push_back(toNodeId); + edgeMap[edge.id] = edge; + + nodeMap[toNodeId].edgeIds.push_back(edge.id); + nodeMap[fromNode->id].edgeIds.push_back(edge.id); + + emit edgeAdded(edge.id); + + if (toPartRemoved) { + partIds.erase(std::remove(partIds.begin(), partIds.end(), toPartId), partIds.end()); + partMap.erase(toPartId); + emit partRemoved(toPartId); + emit partListChanged(); + } + + emit skeletonChanged(); +} + +const SkeletonNode *SkeletonDocument::findNode(QUuid nodeId) const +{ + auto it = nodeMap.find(nodeId); + if (it == nodeMap.end()) + return nullptr; + return &it->second; +} + +const SkeletonEdge *SkeletonDocument::findEdge(QUuid edgeId) const +{ + auto it = edgeMap.find(edgeId); + if (it == edgeMap.end()) + return nullptr; + return &it->second; +} + +const SkeletonPart *SkeletonDocument::findPart(QUuid partId) const +{ + auto it = partMap.find(partId); + if (it == partMap.end()) + return nullptr; + return &it->second; +} + +void SkeletonDocument::scaleNodeByAddRadius(QUuid nodeId, float amount) +{ + auto it = nodeMap.find(nodeId); + if (it == nodeMap.end()) { + qDebug() << "Find node failed:" << nodeId; + return; + } + it->second.radius += amount; + emit nodeRadiusChanged(nodeId); + emit skeletonChanged(); +} + +void SkeletonDocument::moveNodeBy(QUuid nodeId, float x, float y, float z) +{ + auto it = nodeMap.find(nodeId); + if (it == nodeMap.end()) { + qDebug() << "Find node failed:" << nodeId; + return; + } + it->second.x += x; + it->second.y += y; + it->second.z += z; + emit nodeOriginChanged(nodeId); + emit skeletonChanged(); +} + +void SkeletonDocument::setNodeOrigin(QUuid nodeId, float x, float y, float z) +{ + auto it = nodeMap.find(nodeId); + if (it == nodeMap.end()) { + qDebug() << "Find node failed:" << nodeId; + return; + } + it->second.x = x; + it->second.y = y; + it->second.z = z; + emit nodeOriginChanged(nodeId); + emit skeletonChanged(); +} + +void SkeletonDocument::setNodeRadius(QUuid nodeId, float radius) +{ + auto it = nodeMap.find(nodeId); + if (it == nodeMap.end()) { + qDebug() << "Find node failed:" << nodeId; + return; + } + it->second.radius = radius; + emit nodeRadiusChanged(nodeId); + emit skeletonChanged(); +} + +void SkeletonDocument::updateTurnaround(const QImage &image) +{ + turnaround = image; + emit turnaroundChanged(); +} + +void SkeletonDocument::setEditMode(SkeletonDocumentEditMode mode) +{ + if (editMode == mode) + return; + + editMode = mode; + emit editModeChanged(); +} + +void SkeletonDocument::joinNodeAndNeiborsToGroup(std::vector *group, QUuid nodeId, std::set *visitMap, QUuid noUseEdgeId) +{ + if (nodeId.isNull() || visitMap->find(nodeId) != visitMap->end()) + return; + const SkeletonNode *node = findNode(nodeId); + if (nullptr == node) { + qDebug() << "Find node failed:" << nodeId; + return; + } + visitMap->insert(nodeId); + group->push_back(nodeId); + for (auto edgeIt = node->edgeIds.begin(); edgeIt != node->edgeIds.end(); edgeIt++) { + if (noUseEdgeId == *edgeIt) + continue; + const SkeletonEdge *edge = findEdge(*edgeIt); + if (nullptr == edge) { + qDebug() << "Find edge failed:" << *edgeIt; + continue; + } + for (auto nodeIt = edge->nodeIds.begin(); nodeIt != edge->nodeIds.end(); nodeIt++) { + joinNodeAndNeiborsToGroup(group, *nodeIt, visitMap, noUseEdgeId); + } + } +} + +void SkeletonDocument::splitPartByNode(std::vector> *groups, QUuid nodeId) +{ + const SkeletonNode *node = findNode(nodeId); + std::set visitMap; + for (auto edgeIt = node->edgeIds.begin(); edgeIt != node->edgeIds.end(); edgeIt++) { + std::vector group; + const SkeletonEdge *edge = findEdge(*edgeIt); + if (nullptr == edge) { + qDebug() << "Find edge failed:" << *edgeIt; + continue; + } + joinNodeAndNeiborsToGroup(&group, edge->neighborOf(nodeId), &visitMap, *edgeIt); + if (!group.empty()) + groups->push_back(group); + } +} + +void SkeletonDocument::splitPartByEdge(std::vector> *groups, QUuid edgeId) +{ + const SkeletonEdge *edge = findEdge(edgeId); + if (nullptr == edge) { + qDebug() << "Find edge failed:" << edgeId; + return; + } + std::set visitMap; + for (auto nodeIt = edge->nodeIds.begin(); nodeIt != edge->nodeIds.end(); nodeIt++) { + std::vector group; + joinNodeAndNeiborsToGroup(&group, *nodeIt, &visitMap, edgeId); + if (!group.empty()) + groups->push_back(group); + } +} + +void SkeletonDocument::setEdgeBranchMode(QUuid edgeId, SkeletonEdgeBranchMode mode) +{ + auto edgeIt = edgeMap.find(edgeId); + if (edgeIt == edgeMap.end()) { + qDebug() << "Find edge failed:" << edgeId; + return; + } + edgeIt->second.branchMode = mode; +} + +void SkeletonDocument::setNodeRootMarkMode(QUuid nodeId, SkeletonNodeRootMarkMode mode) +{ + auto nodeIt = nodeMap.find(nodeId); + if (nodeIt == nodeMap.end()) { + qDebug() << "Find node failed:" << nodeId; + return; + } + nodeIt->second.rootMarkMode = mode; +} + +void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot) +{ + for (const auto &partIt : partMap) { + std::map part; + part["id"] = partIt.second.id.toString(); + part["visible"] = partIt.second.visible ? "true" : "false"; + if (!partIt.second.name.isEmpty()) + part["name"] = partIt.second.name; + snapshot->parts[part["id"]] = part; + } + for (const auto &nodeIt: nodeMap) { + std::map node; + node["id"] = nodeIt.second.id.toString(); + node["radius"] = QString::number(nodeIt.second.radius); + node["x"] = QString::number(nodeIt.second.x); + node["y"] = QString::number(nodeIt.second.y); + node["z"] = QString::number(nodeIt.second.z); + node["rootMarkMode"] = SkeletonNodeRootMarkModeToString(nodeIt.second.rootMarkMode); + node["partId"] = nodeIt.second.partId.toString(); + if (!nodeIt.second.name.isEmpty()) + node["name"] = nodeIt.second.name; + snapshot->nodes[node["id"]] = node; + qDebug() << "Export node to snapshot " << node["id"] << node["x"] << node["y"] << node["z"]; + } + for (const auto &edgeIt: edgeMap) { + if (edgeIt.second.nodeIds.size() != 2) + continue; + std::map edge; + edge["id"] = edgeIt.second.id.toString(); + edge["from"] = edgeIt.second.nodeIds[0].toString(); + edge["to"] = edgeIt.second.nodeIds[1].toString(); + edge["branchMode"] = SkeletonEdgeBranchModeToString(edgeIt.second.branchMode); + edge["partId"] = edgeIt.second.partId.toString(); + if (!edgeIt.second.name.isEmpty()) + edge["name"] = edgeIt.second.name; + snapshot->edges[edge["id"]] = edge; + qDebug() << "Export edge to snapshot " << edge["from"] << "<=>" << edge["to"]; + } + for (const auto &partIdIt: partIds) { + snapshot->partIdList.push_back(partIdIt.toString()); + } +} + +void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot) +{ + for (const auto &nodeIt: nodeMap) { + emit nodeRemoved(nodeIt.first); + } + for (const auto &edgeIt: edgeMap) { + emit edgeRemoved(edgeIt.first); + } + for (const auto &partIt : partMap) { + emit partRemoved(partIt.first); + } + + nodeMap.clear(); + edgeMap.clear(); + partMap.clear(); + + for (const auto &nodeKv : snapshot.nodes) { + SkeletonNode node(QUuid(nodeKv.first)); + node.name = valueOfKeyInMapOrEmpty(nodeKv.second, "name"); + node.radius = valueOfKeyInMapOrEmpty(nodeKv.second, "radius").toFloat(); + node.x = valueOfKeyInMapOrEmpty(nodeKv.second, "x").toFloat(); + node.y = valueOfKeyInMapOrEmpty(nodeKv.second, "y").toFloat(); + node.z = valueOfKeyInMapOrEmpty(nodeKv.second, "z").toFloat(); + node.partId = QUuid(valueOfKeyInMapOrEmpty(nodeKv.second, "partId")); + node.rootMarkMode = SkeletonNodeRootMarkModeFromString(valueOfKeyInMapOrEmpty(nodeKv.second, "rootMarkMode")); + nodeMap[node.id] = node; + } + for (const auto &edgeKv : snapshot.edges) { + SkeletonEdge edge(QUuid(edgeKv.first)); + edge.name = valueOfKeyInMapOrEmpty(edgeKv.second, "name"); + edge.partId = QUuid(valueOfKeyInMapOrEmpty(edgeKv.second, "partId")); + edge.branchMode = SkeletonEdgeBranchModeFromString(valueOfKeyInMapOrEmpty(edgeKv.second, "branchMode")); + QString fromNodeId = valueOfKeyInMapOrEmpty(edgeKv.second, "from"); + if (!fromNodeId.isEmpty()) + edge.nodeIds.push_back(QUuid(fromNodeId)); + QString toNodeId = valueOfKeyInMapOrEmpty(edgeKv.second, "to"); + if (!toNodeId.isEmpty()) + edge.nodeIds.push_back(QUuid(toNodeId)); + edgeMap[edge.id] = edge; + } + for (const auto &partKv : snapshot.parts) { + SkeletonPart part(QUuid(partKv.first)); + part.name = valueOfKeyInMapOrEmpty(partKv.second, "name"); + part.visible = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "visible")); + partMap[part.id] = part; + } + for (const auto &partIdIt: snapshot.partIdList) { + partIds.push_back(QUuid(partIdIt)); + } + + for (const auto &nodeIt: nodeMap) { + emit nodeAdded(nodeIt.first); + } + for (const auto &edgeIt: edgeMap) { + emit edgeAdded(edgeIt.first); + } + for (const auto &partIt : partMap) { + emit partAdded(partIt.first); + } + + emit partListChanged(); +} + +const char *SkeletonNodeRootMarkModeToString(SkeletonNodeRootMarkMode mode) +{ + switch (mode) { + case SkeletonNodeRootMarkMode::Auto: + return "auto"; + case SkeletonNodeRootMarkMode::MarkAsRoot: + return "markAsRoot"; + case SkeletonNodeRootMarkMode::MarkAsNotRoot: + return "markAsNotRoot"; + default: + return ""; + } +} + +SkeletonNodeRootMarkMode SkeletonNodeRootMarkModeFromString(const QString &mode) +{ + if (mode == "auto") + return SkeletonNodeRootMarkMode::Auto; + if (mode == "markAsRoot") + return SkeletonNodeRootMarkMode::MarkAsRoot; + if (mode == "markAsNotRoot") + return SkeletonNodeRootMarkMode::MarkAsNotRoot; + return SkeletonNodeRootMarkMode::Auto; +} + +const char *SkeletonEdgeBranchModeToString(SkeletonEdgeBranchMode mode) +{ + switch (mode) { + case SkeletonEdgeBranchMode::Auto: + return "auto"; + case SkeletonEdgeBranchMode::NoTrivial: + return "noTrivial"; + case SkeletonEdgeBranchMode::Trivial: + return "trivial"; + default: + return ""; + } +} + +SkeletonEdgeBranchMode SkeletonEdgeBranchModeFromString(const QString &mode) +{ + if (mode == "auto") + return SkeletonEdgeBranchMode::Auto; + if (mode == "noTrivial") + return SkeletonEdgeBranchMode::NoTrivial; + if (mode == "trivial") + return SkeletonEdgeBranchMode::Trivial; + return SkeletonEdgeBranchMode::Auto; +} + +Mesh *SkeletonDocument::takeResultMesh() +{ + Mesh *resultMesh = m_resultMesh; + m_resultMesh = nullptr; + return resultMesh; +} + +void SkeletonDocument::meshReady() +{ + Mesh *resultMesh = m_meshGenerator->takeResultMesh(); + + QImage *resultPreview = m_meshGenerator->takePreview(); + if (resultPreview) { + preview = *resultPreview; + delete resultPreview; + } + + for (auto &part: partMap) { + QImage *resultPartPreview = m_meshGenerator->takePartPreview(part.first.toString()); + if (resultPartPreview) { + part.second.preview = *resultPartPreview; + emit partPreviewChanged(part.first); + delete resultPartPreview; + } + } + + delete m_resultMesh; + m_resultMesh = resultMesh; + + if (nullptr == m_resultMesh) { + qDebug() << "Result mesh is null"; + } + + delete m_meshGenerator; + m_meshGenerator = nullptr; + + qDebug() << "Mesh generation done"; + + emit resultMeshChanged(); + + if (m_resultMeshIsObsolete) { + generateMesh(); + } +} + +void SkeletonDocument::generateMesh() +{ + if (nullptr != m_meshGenerator) { + m_resultMeshIsObsolete = true; + return; + } + + qDebug() << "Mesh generating.."; + + m_resultMeshIsObsolete = false; + + QThread *thread = new QThread; + + SkeletonSnapshot *snapshot = new SkeletonSnapshot; + toSnapshot(snapshot); + m_meshGenerator = new MeshGenerator(snapshot, thread); + m_meshGenerator->moveToThread(thread); + m_meshGenerator->addPreviewRequirement(); + for (auto &part: partMap) { + //if (part.second.previewIsObsolete) { + // part.second.previewIsObsolete = false; + m_meshGenerator->addPartPreviewRequirement(part.first.toString()); + //} + } + connect(thread, &QThread::started, m_meshGenerator, &MeshGenerator::process); + connect(m_meshGenerator, &MeshGenerator::finished, this, &SkeletonDocument::meshReady); + connect(m_meshGenerator, &MeshGenerator::finished, thread, &QThread::quit); + connect(thread, &QThread::finished, thread, &QThread::deleteLater); + thread->start(); +} diff --git a/src/skeletondocument.h b/src/skeletondocument.h new file mode 100644 index 00000000..5dd39399 --- /dev/null +++ b/src/skeletondocument.h @@ -0,0 +1,184 @@ +#ifndef SKELETON_DOCUMENT_H +#define SKELETON_DOCUMENT_H +#include +#include +#include +#include +#include +#include +#include "skeletonsnapshot.h" +#include "mesh.h" +#include "meshgenerator.h" + +enum class SkeletonNodeRootMarkMode +{ + Auto = 0, + MarkAsRoot, + MarkAsNotRoot +}; + +const char *SkeletonNodeRootMarkModeToString(SkeletonNodeRootMarkMode mode); +SkeletonNodeRootMarkMode SkeletonNodeRootMarkModeFromString(const QString &mode); + +class SkeletonNode +{ +public: + SkeletonNode(const QUuid &withId=QUuid()) : + x(0), + y(0), + z(0), + radius(0), + rootMarkMode(SkeletonNodeRootMarkMode::Auto) + { + id = withId.isNull() ? QUuid::createUuid() : withId; + } + QUuid id; + QUuid partId; + QString name; + float x; + float y; + float z; + float radius; + SkeletonNodeRootMarkMode rootMarkMode; + std::vector edgeIds; +}; + +enum class SkeletonEdgeBranchMode +{ + Auto = 0, + Trivial, + NoTrivial +}; + +const char *SkeletonEdgeBranchModeToString(SkeletonEdgeBranchMode mode); +SkeletonEdgeBranchMode SkeletonEdgeBranchModeFromString(const QString &mode); + +class SkeletonEdge +{ +public: + SkeletonEdge(const QUuid &withId=QUuid()) : + branchMode(SkeletonEdgeBranchMode::Auto) + { + id = withId.isNull() ? QUuid::createUuid() : withId; + } + QUuid id; + QUuid partId; + QString name; + SkeletonEdgeBranchMode branchMode; + std::vector nodeIds; + QUuid neighborOf(QUuid nodeId) const + { + if (nodeIds.size() != 2) + return QUuid(); + return nodeIds[0] == nodeId ? nodeIds[1] : nodeIds[0]; + } +}; + +class SkeletonPart +{ +public: + QUuid id; + QString name; + bool visible; + QImage preview; + std::vector nodeIds; + bool previewIsObsolete; + SkeletonPart(const QUuid &withId=QUuid()) : + visible(true), + previewIsObsolete(true) + { + id = withId.isNull() ? QUuid::createUuid() : withId; + } +}; + +class SkeletonHistoryItem +{ +public: + SkeletonSnapshot snapshot; + QImage preview; +}; + +enum class SkeletonDocumentEditMode +{ + Add = 0, + Select, + Drag, + ZoomIn, + ZoomOut +}; + +enum class SkeletonProfile +{ + Unknown = 0, + Main, + Side +}; + +class SkeletonDocument : public QObject +{ + Q_OBJECT +signals: + void partAdded(QUuid partId); + void nodeAdded(QUuid nodeId); + void edgeAdded(QUuid edgeId); + void partRemoved(QUuid partId); + void partListChanged(); + void nodeRemoved(QUuid nodeId); + void edgeRemoved(QUuid edgeId); + void nodeRadiusChanged(QUuid nodeId); + void nodeOriginChanged(QUuid nodeId); + void edgeChanged(QUuid edgeId); + void partChanged(QUuid partId); + void partPreviewChanged(QUuid partId); + void resultMeshChanged(); + void turnaroundChanged(); + void editModeChanged(); + void skeletonChanged(); +public: + SkeletonDocument(); + ~SkeletonDocument(); + std::map partMap; + std::map nodeMap; + std::map edgeMap; + std::vector historyItems; + std::vector partIds; + QImage turnaround; + SkeletonDocumentEditMode editMode; + void toSnapshot(SkeletonSnapshot *snapshot); + void fromSnapshot(const SkeletonSnapshot &snapshot); + const SkeletonNode *findNode(QUuid nodeId) const; + const SkeletonEdge *findEdge(QUuid edgeId) const; + const SkeletonPart *findPart(QUuid partId) const; + const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const; + Mesh *takeResultMesh(); + QImage preview; + void updateTurnaround(const QImage &image); +public slots: + void removeNode(QUuid nodeId); + void removeEdge(QUuid edgeId); + void removePart(QUuid partId); + void showPart(QUuid partId); + void hidePart(QUuid partId); + void addNode(float x, float y, float z, float radius, QUuid fromNodeId); + void scaleNodeByAddRadius(QUuid nodeId, float amount); + void moveNodeBy(QUuid nodeId, float x, float y, float z); + void setNodeOrigin(QUuid nodeId, float x, float y, float z); + void setNodeRadius(QUuid nodeId, float radius); + void addEdge(QUuid fromNodeId, QUuid toNodeId); + void setEditMode(SkeletonDocumentEditMode mode); + void setEdgeBranchMode(QUuid edgeId, SkeletonEdgeBranchMode mode); + void setNodeRootMarkMode(QUuid nodeId, SkeletonNodeRootMarkMode mode); + void uiReady(); + void generateMesh(); + void meshReady(); +private: + void splitPartByNode(std::vector> *groups, QUuid nodeId); + void joinNodeAndNeiborsToGroup(std::vector *group, QUuid nodeId, std::set *visitMap, QUuid noUseEdgeId=QUuid()); + void splitPartByEdge(std::vector> *groups, QUuid edgeId); +private: + bool m_resultMeshIsObsolete; + MeshGenerator *m_meshGenerator; + Mesh *m_resultMesh; +}; + +#endif diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp new file mode 100644 index 00000000..e7b8610f --- /dev/null +++ b/src/skeletondocumentwindow.cpp @@ -0,0 +1,256 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "skeletondocumentwindow.h" +#include "skeletongraphicswidget.h" +#include "skeletonpartlistwidget.h" +#include "skeletonedgepropertywidget.h" +#include "skeletonnodepropertywidget.h" +#include "theme.h" + +SkeletonDocumentWindow::SkeletonDocumentWindow() : + m_document(nullptr), + m_firstShow(true) +{ + m_document = new SkeletonDocument; + + QVBoxLayout *toolButtonLayout = new QVBoxLayout; + toolButtonLayout->setSpacing(0); + toolButtonLayout->setContentsMargins(5, 10, 4, 0); + + QPushButton *undoButton = new QPushButton(QChar(fa::undo)); + undoButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize)); + undoButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize); + + QPushButton *addButton = new QPushButton(QChar(fa::plus)); + addButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize)); + addButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize); + + QPushButton *selectButton = new QPushButton(QChar(fa::mousepointer)); + selectButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize)); + selectButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize); + + QPushButton *dragButton = new QPushButton(QChar(fa::handrocko)); + dragButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize)); + dragButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize); + + QPushButton *zoomInButton = new QPushButton(QChar(fa::searchplus)); + zoomInButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize)); + zoomInButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize); + + QPushButton *zoomOutButton = new QPushButton(QChar(fa::searchminus)); + zoomOutButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize)); + zoomOutButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize); + + QPushButton *changeTurnaroundButton = new QPushButton(QChar(fa::image)); + changeTurnaroundButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize)); + changeTurnaroundButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize); + + QPushButton *resetButton = new QPushButton(QChar(fa::trash)); + resetButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize)); + resetButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize); + + toolButtonLayout->addWidget(undoButton); + toolButtonLayout->addSpacing(10); + toolButtonLayout->addWidget(addButton); + toolButtonLayout->addWidget(selectButton); + toolButtonLayout->addWidget(dragButton); + toolButtonLayout->addWidget(zoomInButton); + toolButtonLayout->addWidget(zoomOutButton); + toolButtonLayout->addSpacing(10); + toolButtonLayout->addWidget(changeTurnaroundButton); + toolButtonLayout->addSpacing(30); + toolButtonLayout->addWidget(resetButton); + + QLabel *dust3dJezzasoftLabel = new QLabel; + QImage dust3dJezzasoftImage; + dust3dJezzasoftImage.load(":/resources/dust3d_jezzasoft.png"); + dust3dJezzasoftLabel->setPixmap(QPixmap::fromImage(dust3dJezzasoftImage)); + + QHBoxLayout *logoLayout = new QHBoxLayout; + logoLayout->addWidget(dust3dJezzasoftLabel); + logoLayout->setContentsMargins(0, 0, 0, 0); + + QVBoxLayout *mainLeftLayout = new QVBoxLayout; + mainLeftLayout->setSpacing(0); + mainLeftLayout->setContentsMargins(0, 0, 0, 0); + mainLeftLayout->addLayout(toolButtonLayout); + mainLeftLayout->addStretch(); + mainLeftLayout->addLayout(logoLayout); + mainLeftLayout->addSpacing(10); + + SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(m_document); + + SkeletonGraphicsContainerWidget *containerWidget = new SkeletonGraphicsContainerWidget; + + containerWidget->setGraphicsWidget(graphicsWidget); + QGridLayout *containerLayout = new QGridLayout; + containerLayout->setSpacing(0); + containerLayout->setContentsMargins(1, 0, 0, 0); + containerLayout->addWidget(graphicsWidget); + containerWidget->setLayout(containerLayout); + containerWidget->setMinimumSize(400, 400); + + m_modelWidget = new ModelWidget(containerWidget); + m_modelWidget->setMinimumSize(128, 128); + m_modelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + m_modelWidget->move(10, 10); + m_modelWidget->setGraphicsFunctions(graphicsWidget); + + SkeletonPartListWidget *partListWidget = new SkeletonPartListWidget(m_document); + + QTabWidget *firstTabWidget = new QTabWidget; + firstTabWidget->setFont(Theme::awesome()->font(Theme::toolIconFontSize * 3 / 4)); + firstTabWidget->setMaximumWidth(200); + firstTabWidget->addTab(partListWidget, QChar(fa::puzzlepiece)); + firstTabWidget->addTab(new QWidget, QChar(fa::history)); + firstTabWidget->addTab(new QWidget, QChar(fa::wrench)); + + SkeletonEdgePropertyWidget *edgePropertyWidget = new SkeletonEdgePropertyWidget(m_document); + SkeletonNodePropertyWidget *nodePropertyWidget = new SkeletonNodePropertyWidget(m_document); + + QVBoxLayout *propertyLayout = new QVBoxLayout; + propertyLayout->addWidget(edgePropertyWidget); + propertyLayout->addWidget(nodePropertyWidget); + + QWidget *propertyWidget = new QWidget; + propertyWidget->setLayout(propertyLayout); + + QTabWidget *secondTabWidget = new QTabWidget; + secondTabWidget->setFont(Theme::awesome()->font(Theme::toolIconFontSize * 3 / 4)); + secondTabWidget->setMaximumWidth(200); + secondTabWidget->setMaximumHeight(90); + secondTabWidget->addTab(propertyWidget, QChar(fa::adjust)); + + QVBoxLayout *mainRightLayout = new QVBoxLayout; + mainRightLayout->setSpacing(0); + mainRightLayout->setContentsMargins(5, 5, 5, 5); + mainRightLayout->addWidget(firstTabWidget); + mainRightLayout->addWidget(secondTabWidget); + + QHBoxLayout *mainLayout = new QHBoxLayout; + mainLayout->setSpacing(0); + mainLayout->setContentsMargins(0, 0, 0, 0); + mainLayout->addLayout(mainLeftLayout); + mainLayout->addWidget(containerWidget); + mainLayout->addLayout(mainRightLayout); + + QWidget *centralWidget = new QWidget; + centralWidget->setLayout(mainLayout); + + setCentralWidget(centralWidget); + setWindowTitle(tr("Dust3D")); + + connect(containerWidget, &SkeletonGraphicsContainerWidget::containerSizeChanged, + graphicsWidget, &SkeletonGraphicsWidget::canvasResized); + + connect(m_document, &SkeletonDocument::turnaroundChanged, + graphicsWidget, &SkeletonGraphicsWidget::turnaroundChanged); + + connect(changeTurnaroundButton, &QPushButton::clicked, this, &SkeletonDocumentWindow::changeTurnaround); + + connect(addButton, &QPushButton::clicked, [=]() { + m_document->setEditMode(SkeletonDocumentEditMode::Add); + }); + + connect(selectButton, &QPushButton::clicked, [=]() { + m_document->setEditMode(SkeletonDocumentEditMode::Select); + }); + + connect(dragButton, &QPushButton::clicked, [=]() { + m_document->setEditMode(SkeletonDocumentEditMode::Drag); + }); + + connect(zoomInButton, &QPushButton::clicked, [=]() { + m_document->setEditMode(SkeletonDocumentEditMode::ZoomIn); + }); + + connect(zoomOutButton, &QPushButton::clicked, [=]() { + m_document->setEditMode(SkeletonDocumentEditMode::ZoomOut); + }); + + connect(m_document, &SkeletonDocument::editModeChanged, graphicsWidget, &SkeletonGraphicsWidget::editModeChanged); + + connect(graphicsWidget, &SkeletonGraphicsWidget::addNode, m_document, &SkeletonDocument::addNode); + connect(graphicsWidget, &SkeletonGraphicsWidget::scaleNodeByAddRadius, m_document, &SkeletonDocument::scaleNodeByAddRadius); + connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_document, &SkeletonDocument::moveNodeBy); + connect(graphicsWidget, &SkeletonGraphicsWidget::removeNode, m_document, &SkeletonDocument::removeNode); + connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &SkeletonDocument::setEditMode); + connect(graphicsWidget, &SkeletonGraphicsWidget::removeEdge, m_document, &SkeletonDocument::removeEdge); + connect(graphicsWidget, &SkeletonGraphicsWidget::addEdge, m_document, &SkeletonDocument::addEdge); + + connect(m_document, &SkeletonDocument::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded); + connect(m_document, &SkeletonDocument::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved); + connect(m_document, &SkeletonDocument::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded); + connect(m_document, &SkeletonDocument::edgeRemoved, graphicsWidget, &SkeletonGraphicsWidget::edgeRemoved); + connect(m_document, &SkeletonDocument::nodeRadiusChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeRadiusChanged); + connect(m_document, &SkeletonDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged); + + connect(m_document, &SkeletonDocument::partListChanged, partListWidget, &SkeletonPartListWidget::partListChanged); + connect(m_document, &SkeletonDocument::partPreviewChanged, partListWidget, &SkeletonPartListWidget::partPreviewChanged); + + connect(graphicsWidget, &SkeletonGraphicsWidget::checkEdge, edgePropertyWidget, &SkeletonEdgePropertyWidget::showProperties); + connect(graphicsWidget, &SkeletonGraphicsWidget::uncheckEdge, [=](QUuid edgeId) { + edgePropertyWidget->hide(); + }); + connect(m_document, &SkeletonDocument::edgeRemoved, [=](QUuid edgeId) { + if (edgeId == edgePropertyWidget->currentEdgeId()) { + edgePropertyWidget->hide(); + } + }); + connect(edgePropertyWidget, &SkeletonEdgePropertyWidget::setEdgeBranchMode, m_document, &SkeletonDocument::setEdgeBranchMode); + + connect(graphicsWidget, &SkeletonGraphicsWidget::checkNode, nodePropertyWidget, &SkeletonNodePropertyWidget::showProperties); + connect(graphicsWidget, &SkeletonGraphicsWidget::uncheckNode, [=](QUuid nodeId) { + nodePropertyWidget->hide(); + }); + connect(m_document, &SkeletonDocument::nodeRemoved, [=](QUuid nodeId) { + if (nodeId == nodePropertyWidget->currentNodeId()) { + nodePropertyWidget->hide(); + } + }); + connect(nodePropertyWidget, &SkeletonNodePropertyWidget::setNodeRootMarkMode, m_document, &SkeletonDocument::setNodeRootMarkMode); + + connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh); + + connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() { + m_modelWidget->updateMesh(m_document->takeResultMesh()); + }); + + connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() { + m_modelWidget->setCursor(graphicsWidget->cursor()); + }); + + connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady); +} + +SkeletonDocumentWindow::~SkeletonDocumentWindow() +{ +} + +void SkeletonDocumentWindow::showEvent(QShowEvent *event) +{ + QMainWindow::showEvent(event); + if (m_firstShow) { + m_firstShow = false; + emit initialized(); + } +} + +void SkeletonDocumentWindow::changeTurnaround() +{ + QString fileName = QFileDialog::getOpenFileName(this, tr("Open Turnaround Reference Image"), + QString(), + tr("Image Files (*.png *.jpg *.bmp)")).trimmed(); + if (fileName.isEmpty()) + return; + QImage image; + if (!image.load(fileName)) + return; + m_document->updateTurnaround(image); +} diff --git a/src/skeletondocumentwindow.h b/src/skeletondocumentwindow.h new file mode 100644 index 00000000..4f3a11c2 --- /dev/null +++ b/src/skeletondocumentwindow.h @@ -0,0 +1,27 @@ +#ifndef SKELETON_DOCUMENT_WINDOW_H +#define SKELETON_DOCUMENT_WINDOW_H +#include +#include +#include "skeletondocument.h" +#include "modelwidget.h" + +class SkeletonDocumentWindow : public QMainWindow +{ + Q_OBJECT +signals: + void initialized(); +public: + SkeletonDocumentWindow(); + ~SkeletonDocumentWindow(); +protected: + void showEvent(QShowEvent *event); +public slots: + void changeTurnaround(); +private: + SkeletonDocument *m_document; + bool m_firstShow; + ModelWidget *m_modelWidget; +}; + +#endif + diff --git a/src/skeletonedgepropertywidget.cpp b/src/skeletonedgepropertywidget.cpp new file mode 100644 index 00000000..fcdd9b33 --- /dev/null +++ b/src/skeletonedgepropertywidget.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include +#include "skeletonedgepropertywidget.h" + +SkeletonEdgePropertyWidget::SkeletonEdgePropertyWidget(const SkeletonDocument *document) : + m_branchModeComboBox(nullptr), + m_document(document) +{ + m_branchModeComboBox = new QComboBox; + + m_branchModeComboBox->insertItem(0, tr("Auto"), (int)SkeletonEdgeBranchMode::Auto); + m_branchModeComboBox->insertItem(1, tr("No Trivial"), (int)SkeletonEdgeBranchMode::NoTrivial); + m_branchModeComboBox->insertItem(2, tr("Trivial"), (int)SkeletonEdgeBranchMode::Trivial); + + QFormLayout *formLayout = new QFormLayout; + formLayout->addRow(tr("Joint Branch Mode:"), m_branchModeComboBox); + + setLayout(formLayout); + + hide(); + + connect(m_branchModeComboBox, static_cast(&QComboBox::currentIndexChanged), [=](int index) { + if (-1 != index) { + int mode = m_branchModeComboBox->itemData(index).toInt(); + emit setEdgeBranchMode(m_edgeId, static_cast(mode)); + } + }); +} + +void SkeletonEdgePropertyWidget::showProperties(QUuid edgeId) +{ + m_edgeId = edgeId; + updateData(); + show(); +} + +void SkeletonEdgePropertyWidget::updateData() +{ + const SkeletonEdge *edge = m_document->findEdge(m_edgeId); + if (nullptr == edge) { + hide(); + return; + } + int selectIndex = m_branchModeComboBox->findData((int)edge->branchMode); + m_branchModeComboBox->setCurrentIndex(selectIndex); +} + +QUuid SkeletonEdgePropertyWidget::currentEdgeId() +{ + return m_edgeId; +} diff --git a/src/skeletonedgepropertywidget.h b/src/skeletonedgepropertywidget.h new file mode 100644 index 00000000..5e8282b2 --- /dev/null +++ b/src/skeletonedgepropertywidget.h @@ -0,0 +1,26 @@ +#ifndef SKELETON_EDGE_PROPERTY_WIDGET_H +#define SKELETON_EDGE_PROPERTY_WIDGET_H +#include +#include +#include +#include "skeletondocument.h" + +class SkeletonEdgePropertyWidget : public QWidget +{ + Q_OBJECT +signals: + void setEdgeBranchMode(QUuid edgeId, SkeletonEdgeBranchMode mode); +public slots: + void showProperties(QUuid edgeId); +public: + SkeletonEdgePropertyWidget(const SkeletonDocument *document); + QUuid currentEdgeId(); +private: + void updateData(); +private: + QComboBox *m_branchModeComboBox; + const SkeletonDocument *m_document; + QUuid m_edgeId; +}; + +#endif diff --git a/src/skeletoneditedgeitem.cpp b/src/skeletoneditedgeitem.cpp deleted file mode 100644 index 2cde95da..00000000 --- a/src/skeletoneditedgeitem.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include "skeletoneditedgeitem.h" -#include "theme.h" - -SkeletonEditEdgeItem::SkeletonEditEdgeItem(QGraphicsItem *parent) : - QGraphicsLineItem(parent), - m_firstNode(NULL), - m_secondNode(NULL) -{ - setData(0, "edge"); - m_checked = false; - updateAppearance(); -} - -void SkeletonEditEdgeItem::setChecked(bool checked) -{ - if (m_checked == checked) { - return; - } - m_checked = checked; - updateAppearance(); -} - -bool SkeletonEditEdgeItem::checked() -{ - return m_checked; -} - -void SkeletonEditEdgeItem::updateAppearance() -{ - QPen pen; - if (m_firstNode) { - QColor color = m_firstNode->sideColor(); - color.setAlphaF(Theme::edgeAlpha); - pen.setColor(color); - } else if (m_secondNode) { - QColor color = m_secondNode->sideColor(); - color.setAlphaF(Theme::edgeAlpha); - pen.setColor(color); - } - pen.setWidth(Theme::skeletonEdgeWidth); - setPen(pen); -} - -void SkeletonEditEdgeItem::setNodes(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second) -{ - m_firstNode = first; - m_secondNode = second; - updatePosition(); -} - -bool SkeletonEditEdgeItem::connects(SkeletonEditNodeItem *nodeItem) -{ - return m_firstNode == nodeItem || m_secondNode == nodeItem; -} - -void SkeletonEditEdgeItem::updatePosition() -{ - if (m_firstNode && m_secondNode) { - QLineF line(m_firstNode->origin(), m_secondNode->origin()); - setLine(line); - } - updateAppearance(); -} - -SkeletonEditNodeItem *SkeletonEditEdgeItem::firstNode() -{ - return m_firstNode; -} - -SkeletonEditNodeItem *SkeletonEditEdgeItem::secondNode() -{ - return m_secondNode; -} diff --git a/src/skeletoneditedgeitem.h b/src/skeletoneditedgeitem.h deleted file mode 100644 index 40099e9b..00000000 --- a/src/skeletoneditedgeitem.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef SKELETON_EDIT_EDGE_ITEM_H -#define SKELETON_EDIT_EDGE_ITEM_H -#include -#include "skeletoneditnodeitem.h" - -class SkeletonEditEdgeItem : public QGraphicsLineItem -{ -public: - SkeletonEditEdgeItem(QGraphicsItem *parent = 0); - void setNodes(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second); - void updatePosition(); - SkeletonEditNodeItem *firstNode(); - SkeletonEditNodeItem *secondNode(); - bool connects(SkeletonEditNodeItem *nodeItem); - void setChecked(bool checked); - bool checked(); -private: - SkeletonEditNodeItem *m_firstNode; - SkeletonEditNodeItem *m_secondNode; - bool m_checked; -private: - void updateAppearance(); -}; - -#endif diff --git a/src/skeletoneditgraphicsview.cpp b/src/skeletoneditgraphicsview.cpp deleted file mode 100644 index 97287f49..00000000 --- a/src/skeletoneditgraphicsview.cpp +++ /dev/null @@ -1,713 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "skeletoneditgraphicsview.h" -#include "skeletoneditnodeitem.h" -#include "skeletoneditedgeitem.h" -#include "theme.h" - -qreal SkeletonEditGraphicsView::m_initialNodeSize = 128; -qreal SkeletonEditGraphicsView::m_minimalNodeSize = 8; - -SkeletonEditGraphicsView::SkeletonEditGraphicsView(QWidget *parent) : - QGraphicsView(parent), - m_pendingNodeItem(NULL), - m_pendingEdgeItem(NULL), - m_inAddNodeMode(true), - m_nextStartNodeItem(NULL), - m_lastHoverNodeItem(NULL), - m_lastMousePos(0, 0), - m_isMovingNodeItem(false), - m_backgroundLoaded(false), - m_modelWidget(NULL), - m_modelWidgetProxy(NULL), - m_combineEnabled(true), - m_unionEnabled(true), - m_subdivEnabled(false) -{ - setScene(new QGraphicsScene()); - - m_modelWidget = new ModelWidget(this); - m_modelWidget->setMinimumSize(128, 128); - m_modelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - m_modelWidget->move(100, 100); - m_modelWidget->setGraphicsView(this); - m_modelWidgetProxy = scene()->addWidget(m_modelWidget); - - m_backgroundItem = new QGraphicsPixmapItem(); - m_backgroundItem->setOpacity(0.25); - scene()->addItem(m_backgroundItem); - - m_pendingNodeItem = new QGraphicsEllipseItem(0, 0, m_initialNodeSize, m_initialNodeSize); - m_pendingNodeItem->setVisible(false); - scene()->addItem(m_pendingNodeItem); - - m_pendingEdgeItem = new QGraphicsLineItem(0, 0, 0, 0); - m_pendingEdgeItem->setVisible(false); - scene()->addItem(m_pendingEdgeItem); -} - -ModelWidget *SkeletonEditGraphicsView::modelWidget() -{ - return m_modelWidget; -} - -void SkeletonEditGraphicsView::toggleAddNodeMode() -{ - if (!m_backgroundLoaded) - return; - m_inAddNodeMode = !m_inAddNodeMode; - applyAddNodeMode(); -} - -void SkeletonEditGraphicsView::applyAddNodeMode() -{ - m_pendingNodeItem->setVisible(m_inAddNodeMode); - m_pendingEdgeItem->setVisible(m_inAddNodeMode && m_nextStartNodeItem); - setMouseTracking(true); -} - -void SkeletonEditGraphicsView::turnOffAddNodeMode() -{ - if (!m_backgroundLoaded) - return; - m_inAddNodeMode = false; - applyAddNodeMode(); -} - -void SkeletonEditGraphicsView::turnOnAddNodeMode() -{ - if (!m_backgroundLoaded) - return; - m_inAddNodeMode = true; - applyAddNodeMode(); -} - -SkeletonEditNodeItem *SkeletonEditGraphicsView::findNodeItemByPos(QPointF pos) -{ - QList::iterator it; - QList list = scene()->items(); - for (it = list.begin(); it != list.end(); ++it) { - if ((*it)->data(0).toString() == "node") { - SkeletonEditNodeItem *nodeItem = static_cast(*it); - if (nodeItem->shape().contains(pos)) { - return nodeItem; - } - } - } - return NULL; -} - -SkeletonEditEdgeItem *SkeletonEditGraphicsView::findEdgeItemByPos(QPointF pos) -{ - QList::iterator it; - QList list = scene()->items(); - for (it = list.begin(); it != list.end(); ++it) { - if ((*it)->data(0).toString() == "edge") { - SkeletonEditEdgeItem *edgeItem = static_cast(*it); - if (edgeItem->shape().contains(pos)) { - return edgeItem; - } - } - } - return NULL; -} - -SkeletonEditEdgeItem *SkeletonEditGraphicsView::findEdgeItemByNodePair(SkeletonEditNodeItem *first, - SkeletonEditNodeItem *second) -{ - QList::iterator it; - QList list = scene()->items(); - assert(first != second); - for (it = list.begin(); it != list.end(); ++it) { - if ((*it)->data(0).toString() == "edge") { - SkeletonEditEdgeItem *edgeItem = static_cast(*it); - if ((edgeItem->firstNode() == first || edgeItem->secondNode() == first) && - (edgeItem->firstNode() == second || edgeItem->secondNode() == second)) { - return edgeItem; - } - } - } - return NULL; -} - -bool SkeletonEditGraphicsView::mousePress(QMouseEvent *event, const QPointF &scenePos) -{ - bool processed = false; - if (!m_backgroundLoaded) - return false; - QPointF pos = scenePos; - if (event->button() == Qt::LeftButton) { - if (!m_inAddNodeMode) { - if (m_lastHoverNodeItem) { - setNextStartNodeItem(m_lastHoverNodeItem); - m_lastHoverNodeItem = NULL; - processed = true; - } else { - if (m_nextStartNodeItem) { - setNextStartNodeItem(NULL); - processed = true; - } - } - } - } - m_lastMousePos = pos; - return processed; -} - -bool SkeletonEditGraphicsView::mouseDoubleClick(QMouseEvent *event, const QPointF &scenePos) -{ - bool processed = false; - if (QApplication::keyboardModifiers() & Qt::ControlModifier) { - processed = true; - emit changeTurnaroundTriggered(); - } - return processed; -} - -void SkeletonEditGraphicsView::removeSelectedItems() -{ - if (m_nextStartNodeItem) { - SkeletonEditNodeItem *nodeItem = m_nextStartNodeItem; - setNextStartNodeItem(NULL); - removeNodeItem(nodeItem->nextSidePair()); - removeNodeItem(nodeItem); - emit nodesChanged(); - } -} - -void SkeletonEditGraphicsView::removeNodeItem(SkeletonEditNodeItem *nodeItem) -{ - scene()->removeItem(nodeItem); - QList::iterator it; - QList list = scene()->items(); - for (it = list.begin(); it != list.end(); ++it) { - if ((*it)->data(0).toString() == "edge") { - SkeletonEditEdgeItem *edgeItem = static_cast(*it); - if (edgeItem->firstNode() == nodeItem || edgeItem->secondNode() == nodeItem) { - scene()->removeItem(edgeItem); - } - } - } -} - -void SkeletonEditGraphicsView::fetchNodeItemAndAllSidePairs(SkeletonEditNodeItem *nodeItem, std::vector *sidePairs) -{ - sidePairs->push_back(nodeItem); - sidePairs->push_back(nodeItem->nextSidePair()); - sidePairs->push_back(nodeItem->nextSidePair()->nextSidePair()); -} - -void SkeletonEditGraphicsView::removeNodeItemAndSidePairs(SkeletonEditNodeItem *nodeItem) -{ - std::vector nodes; - fetchNodeItemAndAllSidePairs(nodeItem, &nodes); - for (size_t i = 0; i < nodes.size(); i++) { - removeNodeItem(nodes[i]); - } -} - -bool SkeletonEditGraphicsView::keyPress(QKeyEvent *event, const QPointF &scenePos) -{ - bool processed = false; - if (!m_backgroundLoaded) - return false; - if (event->key() == Qt::Key_A) { - toggleAddNodeMode(); - processed = true; - } else if (event->key() == Qt::Key_C) { - m_combineEnabled = !m_combineEnabled; - emit nodesChanged(); - } else if (event->key() == Qt::Key_U) { - m_unionEnabled = !m_unionEnabled; - emit nodesChanged(); - } else if (event->key() == Qt::Key_S) { - m_subdivEnabled = !m_subdivEnabled; - emit nodesChanged(); - } else if (event->key() == Qt::Key_Tab) { - if (m_nextStartNodeItem) - setNextStartNodeItem(m_nextStartNodeItem->nextSidePair()); - } else if (event->key() == Qt::Key_Delete || event->key() ==Qt::Key_Backspace) { - removeSelectedItems(); - processed = true; - } - return processed; -} - -void SkeletonEditGraphicsView::resizeEvent(QResizeEvent *event) -{ - QFrame::resizeEvent(event); - emit sizeChanged(); -} - -SkeletonEditNodeItem *SkeletonEditGraphicsView::addNodeItemAndSidePairs(QRectF area, SkeletonEditNodeItem *fromNodeItem, const QString &sideColorName) -{ - float pairedX = 0; - QRectF pairedRect = area; - if (fromNodeItem) { - pairedX = fromNodeItem->nextSidePair()->rect().x(); - } else { - if (area.center().x() < scene()->sceneRect().width() / 2) { - pairedX = area.center().x() + scene()->sceneRect().width() / 4; - } else { - pairedX = area.center().x() - scene()->sceneRect().width() / 4; - } - } - pairedRect.translate(pairedX - area.x(), 0); - SkeletonEditNodeItem *firstNode = new SkeletonEditNodeItem(area); - scene()->addItem(firstNode); - firstNode->setSideColorName(fromNodeItem ? fromNodeItem->sideColorName() : sideColorName); - SkeletonEditNodeItem *secondNode = new SkeletonEditNodeItem(pairedRect); - scene()->addItem(secondNode); - secondNode->setSideColorName(firstNode->nextSideColorName()); - firstNode->setNextSidePair(secondNode); - secondNode->setNextSidePair(firstNode); - setNextStartNodeItem(firstNode); - if (!fromNodeItem) { - return firstNode; - } - addEdgeItem(fromNodeItem, firstNode); - addEdgeItem(fromNodeItem->nextSidePair(), firstNode->nextSidePair()); - return firstNode; -} - -SkeletonEditNodeItem *SkeletonEditGraphicsView::addNodeItem(float originX, float originY, float radius) -{ - QRectF area(originX - radius, originY - radius, radius * 2, radius * 2); - SkeletonEditNodeItem *firstNode = new SkeletonEditNodeItem(area); - scene()->addItem(firstNode); - return firstNode; -} - -void SkeletonEditGraphicsView::addEdgeItem(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second) -{ - SkeletonEditEdgeItem *newEdge = new SkeletonEditEdgeItem(); - newEdge->setNodes(first, second); - scene()->addItem(newEdge); -} - -bool SkeletonEditGraphicsView::mouseRelease(QMouseEvent *event, const QPointF &scenePos) -{ - bool processed = false; - if (!m_backgroundLoaded) - return false; - if (event->button() == Qt::LeftButton) { - if (m_inAddNodeMode) { - if (m_lastHoverNodeItem && m_nextStartNodeItem && - m_lastHoverNodeItem != m_nextStartNodeItem && - m_lastHoverNodeItem->sideColor() == m_nextStartNodeItem->sideColor()) { - if (!findEdgeItemByNodePair(m_lastHoverNodeItem, m_nextStartNodeItem)) { - addEdgeItem(m_nextStartNodeItem, m_lastHoverNodeItem); - addEdgeItem(m_nextStartNodeItem->nextSidePair(), m_lastHoverNodeItem->nextSidePair()); - processed = true; - emit nodesChanged(); - } - } else { - addNodeItemAndSidePairs(m_pendingNodeItem->rect(), m_nextStartNodeItem); - processed = true; - emit nodesChanged(); - } - } - m_isMovingNodeItem = false; - } - return processed; -} - -bool SkeletonEditGraphicsView::canNodeItemMoveTo(SkeletonEditNodeItem *item, QPointF moveTo) -{ - if (moveTo.x() < 0) - return false; - if (moveTo.y() < 0) - return false; - if (moveTo.x() + item->rect().width() >= scene()->sceneRect().width()) - return false; - if (moveTo.y() + item->rect().height() >= scene()->sceneRect().height()) - return false; - return true; -} - -bool SkeletonEditGraphicsView::mouseMove(QMouseEvent *event, const QPointF &scenePos) -{ - bool processed = false; - if (!m_backgroundLoaded) - return false; - QPointF pos = scenePos; - QPointF moveTo = QPointF(pos.x() - m_pendingNodeItem->rect().width() / 2, pos.y() - m_pendingNodeItem->rect().height() / 2); - if (moveTo.x() < 0) - moveTo.setX(0); - if (moveTo.y() < 0) - moveTo.setY(0); - if (moveTo.x() + m_pendingNodeItem->rect().width() >= scene()->sceneRect().width()) - moveTo.setX(scene()->sceneRect().width() - m_pendingNodeItem->rect().width()); - if (moveTo.y() + m_pendingNodeItem->rect().height() >= scene()->sceneRect().height()) - moveTo.setY(scene()->sceneRect().height() - m_pendingNodeItem->rect().height()); - QSizeF oldSize = m_pendingNodeItem->rect().size(); - m_pendingNodeItem->setRect(moveTo.x(), moveTo.y(), - oldSize.width(), - oldSize.height()); - if (m_nextStartNodeItem) { - m_pendingEdgeItem->setLine(QLineF(m_nextStartNodeItem->origin(), QPointF(moveTo.x() + m_pendingNodeItem->rect().width() / 2, - moveTo.y() + m_pendingNodeItem->rect().height() / 2))); - } - if (!m_isMovingNodeItem) { - SkeletonEditNodeItem *hoverNodeItem = findNodeItemByPos(pos); - if (hoverNodeItem) { - hoverNodeItem->setHovered(true); - processed = true; - } - if (hoverNodeItem != m_lastHoverNodeItem) { - if (m_lastHoverNodeItem) - m_lastHoverNodeItem->setHovered(false); - m_lastHoverNodeItem = hoverNodeItem; - processed = true; - } - } - QPointF curMousePos = pos; - if (m_lastHoverNodeItem) { - if ((event->buttons() & Qt::LeftButton) && - (curMousePos != m_lastMousePos || m_isMovingNodeItem)) { - m_isMovingNodeItem = true; - QRectF rect = m_lastHoverNodeItem->rect(); - QRectF pairedRect; - - rect.translate(curMousePos.x() - m_lastMousePos.x(), curMousePos.y() - m_lastMousePos.y()); - pairedRect = m_lastHoverNodeItem->nextSidePair()->rect(); - pairedRect.translate(0, curMousePos.y() - m_lastMousePos.y()); - - m_lastHoverNodeItem->setRect(rect); - m_lastHoverNodeItem->nextSidePair()->setRect(pairedRect); - - QList::iterator it; - QList list = scene()->items(); - for (it = list.begin(); it != list.end(); ++it) { - if ((*it)->data(0).toString() == "edge") { - SkeletonEditEdgeItem *edgeItem = static_cast(*it); - if (edgeItem->connects(m_lastHoverNodeItem) || - edgeItem->connects(m_lastHoverNodeItem->nextSidePair())) - edgeItem->updatePosition(); - } - } - processed = true; - emit nodesChanged(); - } - } - m_lastMousePos = curMousePos; - return processed; -} - -void SkeletonEditGraphicsView::AddItemRadius(QGraphicsEllipseItem *item, float delta) -{ - QSizeF oldSize = item->rect().size(); - QPointF originPt = QPointF(item->rect().left() + oldSize.width() / 2, - item->rect().top() + oldSize.height() / 2); - QSizeF newSize = QSizeF(oldSize.width() + delta, oldSize.height() + delta); - if (newSize.width() < m_minimalNodeSize || newSize.height() < m_minimalNodeSize) { - newSize.setWidth(m_minimalNodeSize); - newSize.setHeight(m_minimalNodeSize); - } - QPointF newLeftTop = QPointF(originPt.x() - newSize.width() / 2, - originPt.y() - newSize.height() / 2); - if (newLeftTop.x() < 0 || newLeftTop.x() + newSize.width() >= scene()->sceneRect().width()) - return; - if (newLeftTop.y() < 0 || newLeftTop.y() + newSize.height() >= scene()->sceneRect().height()) - return; - item->setRect(newLeftTop.x(), - newLeftTop.y(), - newSize.width(), - newSize.height()); -} - -bool SkeletonEditGraphicsView::canAddItemRadius(QGraphicsEllipseItem *item, float delta) -{ - QSizeF oldSize = item->rect().size(); - QPointF originPt = QPointF(item->rect().left() + oldSize.width() / 2, - item->rect().top() + oldSize.height() / 2); - QSizeF newSize = QSizeF(oldSize.width() + delta, oldSize.height() + delta); - if (newSize.width() < m_minimalNodeSize || newSize.height() < m_minimalNodeSize) { - newSize.setWidth(m_minimalNodeSize); - newSize.setHeight(m_minimalNodeSize); - } - QPointF newLeftTop = QPointF(originPt.x() - newSize.width() / 2, - originPt.y() - newSize.height() / 2); - if (newLeftTop.x() < 0 || newLeftTop.x() + newSize.width() >= scene()->sceneRect().width()) - return false; - if (newLeftTop.y() < 0 || newLeftTop.y() + newSize.height() >= scene()->sceneRect().height()) - return false; - return true; -} - -bool SkeletonEditGraphicsView::wheel(QWheelEvent *event, const QPointF &scenePos) -{ - bool processed = false; - if (!m_backgroundLoaded) - return false; - qreal delta = event->delta() / 10; - if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { - if (delta > 0) - delta = 1; - else - delta = -1; - } else { - if (fabs(delta) < 1) - delta = delta < 0 ? -1.0 : 1.0; - } - AddItemRadius(m_pendingNodeItem, delta); - if (!m_inAddNodeMode && m_lastHoverNodeItem) { - AddItemRadius(m_lastHoverNodeItem, delta); - AddItemRadius(m_lastHoverNodeItem->nextSidePair(), delta); - processed = true; - emit nodesChanged(); - } - if (m_inAddNodeMode) - processed = true; - return processed; -} - -void SkeletonEditGraphicsView::setNextStartNodeItem(SkeletonEditNodeItem *item) -{ - if (m_nextStartNodeItem != item) { - if (m_nextStartNodeItem) - m_nextStartNodeItem->setChecked(false); - } - m_nextStartNodeItem = item; - if (m_nextStartNodeItem) - m_nextStartNodeItem->setChecked(true); - applyAddNodeMode(); -} - -void SkeletonEditGraphicsView::updateBackgroundImage(const QImage &image) -{ - QSizeF oldSceneSize = scene()->sceneRect().size(); - QPixmap pixmap = QPixmap::fromImage(image); - scene()->setSceneRect(pixmap.rect()); - m_backgroundItem->setPixmap(pixmap); - adjustItems(oldSceneSize, scene()->sceneRect().size()); - if (!m_backgroundLoaded) { - m_backgroundLoaded = true; - applyAddNodeMode(); - } -} - -QPixmap SkeletonEditGraphicsView::backgroundImage() -{ - return m_backgroundItem->pixmap(); -} - -bool SkeletonEditGraphicsView::hasBackgroundImage() -{ - return m_backgroundLoaded; -} - -void SkeletonEditGraphicsView::adjustItems(QSizeF oldSceneSize, QSizeF newSceneSize) -{ - if (oldSceneSize == newSceneSize) - return; - float radiusMul = (float)newSceneSize.height() / oldSceneSize.height(); - float xMul = (float)newSceneSize.width() / oldSceneSize.width(); - float yMul = radiusMul; - QList::iterator it; - QList list = scene()->items(); - for (it = list.begin(); it != list.end(); ++it) { - if ((*it)->data(0).toString() == "node") { - SkeletonEditNodeItem *nodeItem = static_cast(*it); - nodeItem->setRadius(nodeItem->radius() * radiusMul); - QPointF oldOrigin = nodeItem->origin(); - nodeItem->setOrigin(QPointF(oldOrigin.x() * xMul, oldOrigin.y() * yMul)); - } - } - for (it = list.begin(); it != list.end(); ++it) { - if ((*it)->data(0).toString() == "edge") { - SkeletonEditEdgeItem *edgeItem = static_cast(*it); - edgeItem->updatePosition(); - } - } -} - -void SkeletonEditGraphicsView::loadFromSnapshot(SkeletonSnapshot *snapshot) -{ - float radiusMul = 1.0; - float xMul = 1.0; - float yMul = radiusMul; - - QString canvasWidth = snapshot->canvas["width"]; - QString canvasHeight = snapshot->canvas["height"]; - float canvasWidthVal = canvasWidth.toFloat(); - float canvasHeightVal = canvasHeight.toFloat(); - if (!hasBackgroundImage()) { - QPixmap emptyImage((int)canvasWidthVal, (int)canvasHeightVal); - emptyImage.fill(QWidget::palette().color(QWidget::backgroundRole())); - updateBackgroundImage(emptyImage.toImage()); - } - if (canvasHeightVal > 0) - radiusMul = (float)scene()->sceneRect().height() / canvasHeightVal; - if (canvasWidthVal > 0) - xMul = (float)scene()->sceneRect().width() / canvasWidthVal; - yMul = radiusMul; - - std::map nodeItemMap; - std::map>::iterator nodeIterator; - for (nodeIterator = snapshot->nodes.begin(); nodeIterator != snapshot->nodes.end(); nodeIterator++) { - std::map *snapshotNode = &nodeIterator->second; - SkeletonEditNodeItem *nodeItem = addNodeItem((*snapshotNode)["x"].toFloat() * xMul, - (*snapshotNode)["y"].toFloat() * yMul, - (*snapshotNode)["radius"].toFloat() * radiusMul); - nodeItem->setSideColorName((*snapshotNode)["sideColorName"]); - nodeItem->markAsBranch("true" == (*snapshotNode)["isBranch"]); - nodeItemMap[nodeIterator->first] = nodeItem; - } - for (nodeIterator = snapshot->nodes.begin(); nodeIterator != snapshot->nodes.end(); nodeIterator++) { - std::map *snapshotNode = &nodeIterator->second; - SkeletonEditNodeItem *nodeItem = nodeItemMap[nodeIterator->first]; - nodeItem->setNextSidePair(nodeItemMap[(*snapshotNode)["nextSidePair"]]); - } - - std::map>::iterator edgeIterator; - for (edgeIterator = snapshot->edges.begin(); edgeIterator != snapshot->edges.end(); edgeIterator++) { - std::map *snapshotEdge = &edgeIterator->second; - addEdgeItem(nodeItemMap[(*snapshotEdge)["from"]], nodeItemMap[(*snapshotEdge)["to"]]); - } - - emit nodesChanged(); -} - -void SkeletonEditGraphicsView::saveToSnapshot(SkeletonSnapshot *snapshot) -{ - snapshot->canvas["width"] = QString("%1").arg(scene()->sceneRect().width()); - snapshot->canvas["height"] = QString("%1").arg(scene()->sceneRect().height()); - snapshot->canvas["combine"] = m_combineEnabled ? "true" : "false"; - snapshot->canvas["union"] = m_unionEnabled ? "true" : "false"; - snapshot->canvas["subdiv"] = m_subdivEnabled ? "true" : "false"; - - QList::iterator it; - QList list = scene()->items(); - - int nextNodeId = 1; - std::map nodeIdMap; - for (it = list.begin(); it != list.end(); ++it) { - if ((*it)->data(0).toString() == "node") { - SkeletonEditNodeItem *nodeItem = static_cast(*it); - QString nodeId = QString("node%1").arg(nextNodeId); - std::map *snapshotNode = &snapshot->nodes[nodeId]; - (*snapshotNode)["id"] = nodeId; - (*snapshotNode)["sideColorName"] = nodeItem->sideColorName(); - (*snapshotNode)["radius"] = QString("%1").arg(nodeItem->radius()); - (*snapshotNode)["isBranch"] = QString("%1").arg(nodeItem->isBranch() ? "true" : "false"); - (*snapshotNode)["isRoot"] = QString("%1").arg(nodeItem->isRoot() ? "true" : "false"); - QPointF origin = nodeItem->origin(); - (*snapshotNode)["x"] = QString("%1").arg(origin.x()); - (*snapshotNode)["y"] = QString("%1").arg(origin.y()); - nodeIdMap[nodeItem] = nodeId; - nextNodeId++; - } - } - - for (it = list.begin(); it != list.end(); ++it) { - if ((*it)->data(0).toString() == "node") { - SkeletonEditNodeItem *nodeItem = static_cast(*it); - QString nodeId = nodeIdMap[nodeItem]; - std::map *snapshotNode = &snapshot->nodes[nodeId]; - (*snapshotNode)["nextSidePair"] = nodeIdMap[nodeItem->nextSidePair()]; - } - } - - int nextEdgeId = 1; - for (it = list.begin(); it != list.end(); ++it) { - if ((*it)->data(0).toString() == "edge") { - SkeletonEditEdgeItem *edgeItem = static_cast(*it); - QString edgeId = QString("edge%1").arg(nextEdgeId); - std::map *snapshotEdge = &snapshot->edges[edgeId]; - (*snapshotEdge)["id"] = edgeId; - (*snapshotEdge)["from"] = nodeIdMap[edgeItem->firstNode()]; - (*snapshotEdge)["to"] = nodeIdMap[edgeItem->secondNode()]; - nextEdgeId++; - } - } -} - -void SkeletonEditGraphicsView::markAsBranch() -{ - if (m_nextStartNodeItem) { - m_nextStartNodeItem->markAsBranch(true); - m_nextStartNodeItem->nextSidePair()->markAsBranch(true); - emit nodesChanged(); - } -} - -void SkeletonEditGraphicsView::markAsTrunk() -{ - if (m_nextStartNodeItem) { - m_nextStartNodeItem->markAsBranch(false); - m_nextStartNodeItem->nextSidePair()->markAsBranch(false); - emit nodesChanged(); - } -} - -void SkeletonEditGraphicsView::markAsRoot() -{ - if (m_nextStartNodeItem) { - m_nextStartNodeItem->markAsRoot(true); - m_nextStartNodeItem->nextSidePair()->markAsRoot(true); - emit nodesChanged(); - } -} - -void SkeletonEditGraphicsView::markAsChild() -{ - if (m_nextStartNodeItem) { - m_nextStartNodeItem->markAsRoot(false); - m_nextStartNodeItem->nextSidePair()->markAsRoot(false); - emit nodesChanged(); - } -} - -void SkeletonEditGraphicsView::mouseMoveEvent(QMouseEvent *event) -{ - mouseMove(event, mapToScene(event->pos())); -} - -void SkeletonEditGraphicsView::wheelEvent(QWheelEvent *event) -{ - wheel(event, mapToScene(event->pos())); -} - -void SkeletonEditGraphicsView::mouseReleaseEvent(QMouseEvent *event) -{ - mouseRelease(event, mapToScene(event->pos())); -} - -void SkeletonEditGraphicsView::mousePressEvent(QMouseEvent *event) -{ - mousePress(event, mapToScene(event->pos())); -} - -void SkeletonEditGraphicsView::mouseDoubleClickEvent(QMouseEvent *event) -{ - mouseDoubleClick(event, mapToScene(event->pos())); -} - -void SkeletonEditGraphicsView::keyPressEvent(QKeyEvent *event) -{ - keyPress(event, QPointF()); -} - -bool SkeletonEditGraphicsView::combineEnabled() -{ - return m_combineEnabled; -} - -bool SkeletonEditGraphicsView::unionEnabled() -{ - return m_unionEnabled; -} - -bool SkeletonEditGraphicsView::subdivEnabled() -{ - return m_subdivEnabled; -} - diff --git a/src/skeletoneditgraphicsview.h b/src/skeletoneditgraphicsview.h deleted file mode 100644 index bd603ea6..00000000 --- a/src/skeletoneditgraphicsview.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef SKELETON_EDIT_GRAPHICS_VIEW_H -#define SKELETON_EDIT_GRAPHICS_VIEW_H -#include -#include -#include -#include -#include -#include -#include -#include "skeletoneditnodeitem.h" -#include "skeletoneditedgeitem.h" -#include "skeletonsnapshot.h" -#include "modelwidget.h" - -class SkeletonEditGraphicsView : public QGraphicsView -{ - Q_OBJECT -signals: - void sizeChanged(); - void nodesChanged(); - void changeTurnaroundTriggered(); - void edgeCheckStateChanged(); -public slots: - void turnOffAddNodeMode(); - void turnOnAddNodeMode(); - void markAsBranch(); - void markAsTrunk(); - void markAsRoot(); - void markAsChild(); - bool mouseMove(QMouseEvent *event, const QPointF &scenePos); - bool wheel(QWheelEvent *event, const QPointF &scenePos); - bool mouseRelease(QMouseEvent *event, const QPointF &scenePos); - bool mousePress(QMouseEvent *event, const QPointF &scenePos); - bool mouseDoubleClick(QMouseEvent *event, const QPointF &scenePos); - bool keyPress(QKeyEvent *event, const QPointF &scenePos); -public: - SkeletonEditGraphicsView(QWidget *parent = 0); - void updateBackgroundImage(const QImage &image); - bool hasBackgroundImage(); - QPixmap backgroundImage(); - void saveToSnapshot(SkeletonSnapshot *snapshot); - void loadFromSnapshot(SkeletonSnapshot *snapshot); - ModelWidget *modelWidget(); - bool combineEnabled(); - bool unionEnabled(); - bool subdivEnabled(); -protected: - void mouseMoveEvent(QMouseEvent *event); - void wheelEvent(QWheelEvent *event); - void mouseReleaseEvent(QMouseEvent *event); - void mousePressEvent(QMouseEvent *event); - void mouseDoubleClickEvent(QMouseEvent *event); - void keyPressEvent(QKeyEvent *event); - void resizeEvent(QResizeEvent *event); -private: - QGraphicsPixmapItem *m_backgroundItem; - QGraphicsEllipseItem *m_pendingNodeItem; - QGraphicsLineItem *m_pendingEdgeItem; - static qreal m_initialNodeSize; - static qreal m_minimalNodeSize; - bool m_inAddNodeMode; - SkeletonEditNodeItem *m_nextStartNodeItem; - SkeletonEditNodeItem *m_lastHoverNodeItem; - QPointF m_lastMousePos; - bool m_isMovingNodeItem; - bool m_backgroundLoaded; - ModelWidget *m_modelWidget; - QGraphicsProxyWidget *m_modelWidgetProxy; - bool m_combineEnabled; - bool m_unionEnabled; - bool m_subdivEnabled; -private: - void toggleAddNodeMode(); - void applyAddNodeMode(); - SkeletonEditNodeItem *findNodeItemByPos(QPointF pos); - SkeletonEditEdgeItem *findEdgeItemByPos(QPointF pos); - SkeletonEditEdgeItem *findEdgeItemByNodePair(SkeletonEditNodeItem *first, - SkeletonEditNodeItem *second); - void setNextStartNodeItem(SkeletonEditNodeItem *item); - bool canNodeItemMoveTo(SkeletonEditNodeItem *item, QPointF moveTo); - void AddItemRadius(QGraphicsEllipseItem *item, float delta); - bool canAddItemRadius(QGraphicsEllipseItem *item, float delta); - void adjustItems(QSizeF oldSceneSize, QSizeF newSceneSize); - void removeSelectedItems(); - void removeNodeItem(SkeletonEditNodeItem *nodeItem); - void removeNodeItemAndSidePairs(SkeletonEditNodeItem *nodeItem); - void fetchNodeItemAndAllSidePairs(SkeletonEditNodeItem *nodeItem, std::vector *sidePairs); - SkeletonEditNodeItem *addNodeItemAndSidePairs(QRectF area, SkeletonEditNodeItem *fromNodeItem, const QString &sideColorName="red"); - SkeletonEditNodeItem *addNodeItem(float originX, float originY, float radius); - void addEdgeItem(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second); -}; - -#endif diff --git a/src/skeletoneditnodeitem.cpp b/src/skeletoneditnodeitem.cpp deleted file mode 100644 index b5b0e74f..00000000 --- a/src/skeletoneditnodeitem.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include -#include "skeletoneditnodeitem.h" -#include "theme.h" - -SkeletonEditNodeItem::SkeletonEditNodeItem(const QRectF &rect, QGraphicsItem *parent) : - QGraphicsEllipseItem(rect, parent), - m_hovered(false), - m_checked(false), - m_nextSidePair(NULL), - m_sideColor(Theme::red), - m_sideColorName("red"), - m_isBranch(false), - m_isRoot(false) -{ - setData(0, "node"); - updateAppearance(); -} - -void SkeletonEditNodeItem::markAsBranch(bool isBranch) -{ - m_isBranch = isBranch; - updateAppearance(); -} - -bool SkeletonEditNodeItem::isBranch() -{ - return m_isBranch; -} - -void SkeletonEditNodeItem::markAsRoot(bool isRoot) -{ - m_isRoot = isRoot; - updateAppearance(); -} - -bool SkeletonEditNodeItem::isRoot() -{ - return m_isRoot; -} - -const QString &SkeletonEditNodeItem::sideColorName() -{ - return m_sideColorName; -} - -QString SkeletonEditNodeItem::nextSideColorName() -{ - return Theme::nextSideColorNameMap[m_sideColorName]; -} - -void SkeletonEditNodeItem::setSideColorName(const QString &name) -{ - m_sideColorName = name; - m_sideColor = Theme::sideColorNameToColorMap[m_sideColorName]; - updateAppearance(); -} - -bool SkeletonEditNodeItem::hovered() -{ - return m_hovered; -} - -void SkeletonEditNodeItem::setHovered(bool hovered) -{ - m_hovered = hovered; - updateAppearance(); -} - -bool SkeletonEditNodeItem::checked() -{ - return m_checked; -} - -void SkeletonEditNodeItem::setChecked(bool checked) -{ - m_checked = checked; - updateAppearance(); -} - -SkeletonEditNodeItem *SkeletonEditNodeItem::nextSidePair() -{ - return m_nextSidePair; -} - -void SkeletonEditNodeItem::setNextSidePair(SkeletonEditNodeItem *nodeItem) -{ - m_nextSidePair = nodeItem; - updateAppearance(); -} - -const QColor &SkeletonEditNodeItem::sideColor() -{ - return m_sideColor; -} - -QPointF SkeletonEditNodeItem::origin() -{ - return QPointF(rect().x() + rect().width() / 2, - rect().y() + rect().height() / 2); -} - -float SkeletonEditNodeItem::radius() -{ - return rect().width() / 2; -} - -void SkeletonEditNodeItem::setRadius(float radius) -{ - QPointF oldOrigin = origin(); - setRect(oldOrigin.x() - radius, oldOrigin.y() - radius, - radius * 2, radius * 2); -} - -void SkeletonEditNodeItem::setOrigin(QPointF point) -{ - QPointF moveBy = point - origin(); - QRectF newRect = rect(); - newRect.adjust(moveBy.x(), moveBy.y(), moveBy.x(), moveBy.y()); - setRect(newRect); -} - -void SkeletonEditNodeItem::updateAppearance() -{ - QColor penColor = m_sideColor; - penColor.setAlphaF(m_checked ? Theme::checkedAlpha : (m_isBranch ? Theme::branchAlpha : Theme::normalAlpha)); - QPen pen(penColor); - pen.setWidth(m_isRoot ? (Theme::skeletonNodeBorderSize * 2) : Theme::skeletonNodeBorderSize); - setPen(pen); - - QColor brushColor = m_sideColor; - brushColor.setAlphaF((m_hovered || m_checked) ? Theme::fillAlpha : 0); - QBrush brush(brushColor); - setBrush(brush); -} - -QColor SkeletonEditNodeItem::nextSideColor() -{ - return Theme::sideColorNameToColorMap[Theme::nextSideColorNameMap[m_sideColorName]]; -} - diff --git a/src/skeletoneditnodeitem.h b/src/skeletoneditnodeitem.h deleted file mode 100644 index 7f97a168..00000000 --- a/src/skeletoneditnodeitem.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef SKELETON_EDIT_NODE_ITEM_H -#define SKELETON_EDIT_NODE_ITEM_H -#include -#include -#include -#include - -class SkeletonEditEdgeItem; - -class SkeletonEditNodeItem : public QGraphicsEllipseItem -{ -public: - SkeletonEditNodeItem(const QRectF &rect, QGraphicsItem *parent = 0); - QPointF origin(); - void setOrigin(QPointF point); - float radius(); - void setRadius(float radius); - bool hovered(); - void setHovered(bool hovered); - bool checked(); - void setChecked(bool checked); - void markAsBranch(bool isBranch); - bool isBranch(); - void markAsRoot(bool isRoot); - bool isRoot(); - SkeletonEditNodeItem *nextSidePair(); - void setNextSidePair(SkeletonEditNodeItem *nodeItem); - const QColor &sideColor(); - QColor nextSideColor(); - const QString &sideColorName(); - QString nextSideColorName(); - void setSideColorName(const QString &name); -private: - bool m_hovered; - bool m_checked; - SkeletonEditNodeItem *m_nextSidePair; - QColor m_sideColor; - QString m_sideColorName; - bool m_isBranch; - bool m_isRoot; -private: - void updateAppearance(); -}; - -#endif - diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp new file mode 100644 index 00000000..1a27dea0 --- /dev/null +++ b/src/skeletongraphicswidget.cpp @@ -0,0 +1,644 @@ +#include +#include +#include +#include +#include +#include +#include "skeletongraphicswidget.h" +#include "theme.h" +#include "util.h" + +SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) : + m_document(document), + m_turnaroundChanged(false), + m_turnaroundLoader(nullptr), + m_dragStarted(false), + m_cursorNodeItem(nullptr), + m_cursorEdgeItem(nullptr), + m_checkedNodeItem(nullptr), + m_moveStarted(false), + m_hoveredNodeItem(nullptr), + m_hoveredEdgeItem(nullptr), + m_checkedEdgeItem(nullptr), + m_lastAddedX(0), + m_lastAddedY(0), + m_lastAddedZ(0) +{ + setRenderHint(QPainter::Antialiasing, false); + setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern)); + setContentsMargins(0, 0, 0, 0); + setFrameStyle(QFrame::NoFrame); + + setAlignment(Qt::AlignCenter); + + setScene(new QGraphicsScene()); + + m_backgroundItem = new QGraphicsPixmapItem(); + m_backgroundItem->setOpacity(0.25); + scene()->addItem(m_backgroundItem); + + m_cursorNodeItem = new SkeletonGraphicsNodeItem(); + m_cursorNodeItem->hide(); + scene()->addItem(m_cursorNodeItem); + + m_cursorEdgeItem = new SkeletonGraphicsEdgeItem(); + m_cursorEdgeItem->hide(); + scene()->addItem(m_cursorEdgeItem); + + scene()->setSceneRect(rect()); + + setMouseTracking(true); +} + +void SkeletonGraphicsWidget::updateItems() +{ + for (auto nodeItemIt = nodeItemMap.begin(); nodeItemIt != nodeItemMap.end(); nodeItemIt++) { + nodeRadiusChanged(nodeItemIt->first); + nodeOriginChanged(nodeItemIt->first); + } +} + +void SkeletonGraphicsWidget::canvasResized() +{ + updateTurnaround(); +} + +void SkeletonGraphicsWidget::turnaroundChanged() +{ + updateTurnaround(); + setTransform(QTransform()); +} + +void SkeletonGraphicsWidget::updateTurnaround() +{ + if (m_document->turnaround.isNull()) + return; + + m_turnaroundChanged = true; + if (m_turnaroundLoader) + return; + + qDebug() << "Fit turnaround to view size:" << parentWidget()->rect().size(); + + m_turnaroundChanged = false; + + QThread *thread = new QThread; + m_turnaroundLoader = new TurnaroundLoader(m_document->turnaround, + parentWidget()->rect().size()); + m_turnaroundLoader->moveToThread(thread); + connect(thread, SIGNAL(started()), m_turnaroundLoader, SLOT(process())); + connect(m_turnaroundLoader, SIGNAL(finished()), this, SLOT(turnaroundImageReady())); + connect(m_turnaroundLoader, SIGNAL(finished()), thread, SLOT(quit())); + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + thread->start(); +} + +void SkeletonGraphicsWidget::turnaroundImageReady() +{ + QImage *backgroundImage = m_turnaroundLoader->takeResultImage(); + if (backgroundImage && backgroundImage->width() > 0 && backgroundImage->height() > 0) { + qDebug() << "Fit turnaround finished with image size:" << backgroundImage->size(); + setFixedSize(backgroundImage->size()); + scene()->setSceneRect(rect()); + m_backgroundItem->setPixmap(QPixmap::fromImage(*backgroundImage)); + updateItems(); + } else { + qDebug() << "Fit turnaround failed"; + } + delete backgroundImage; + delete m_turnaroundLoader; + m_turnaroundLoader = nullptr; + + if (m_turnaroundChanged) { + updateTurnaround(); + } +} + +void SkeletonGraphicsWidget::updateCursor() +{ + if (SkeletonDocumentEditMode::Add != m_document->editMode) { + m_cursorEdgeItem->hide(); + m_cursorNodeItem->hide(); + } + + switch (m_document->editMode) { + case SkeletonDocumentEditMode::Add: + setCursor(QCursor(Theme::awesome()->icon(fa::plus).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize))); + break; + case SkeletonDocumentEditMode::Select: + setCursor(QCursor(Theme::awesome()->icon(fa::mousepointer).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize))); + break; + case SkeletonDocumentEditMode::Drag: + setCursor(QCursor(Theme::awesome()->icon(m_dragStarted ? fa::handrocko : fa::handpapero).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize))); + break; + case SkeletonDocumentEditMode::ZoomIn: + setCursor(QCursor(Theme::awesome()->icon(fa::searchplus).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize))); + break; + case SkeletonDocumentEditMode::ZoomOut: + setCursor(QCursor(Theme::awesome()->icon(fa::searchminus).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize))); + break; + } + + emit cursorChanged(); +} + +void SkeletonGraphicsWidget::editModeChanged() +{ + if (m_checkedNodeItem) { + if (!m_checkedNodeItem->checked()) { + if (m_hoveredNodeItem == m_checkedNodeItem) { + m_checkedNodeItem->setHovered(false); + m_hoveredNodeItem = nullptr; + } + m_checkedNodeItem = nullptr; + } + } + updateCursor(); +} + +void SkeletonGraphicsWidget::mouseMoveEvent(QMouseEvent *event) +{ + QGraphicsView::mouseMoveEvent(event); + mouseMove(event); +} + +void SkeletonGraphicsWidget::wheelEvent(QWheelEvent *event) +{ + if (SkeletonDocumentEditMode::ZoomIn == m_document->editMode || + SkeletonDocumentEditMode::ZoomOut == m_document->editMode || + SkeletonDocumentEditMode::Drag == m_document->editMode) + QGraphicsView::wheelEvent(event); + wheel(event); +} + +void SkeletonGraphicsWidget::mouseReleaseEvent(QMouseEvent *event) +{ + QGraphicsView::mouseReleaseEvent(event); + mouseRelease(event); +} + +void SkeletonGraphicsWidget::mousePressEvent(QMouseEvent *event) +{ + QGraphicsView::mousePressEvent(event); + mousePress(event); +} + +void SkeletonGraphicsWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + QGraphicsView::mouseDoubleClickEvent(event); + mouseDoubleClick(event); +} + +void SkeletonGraphicsWidget::keyPressEvent(QKeyEvent *event) +{ + QGraphicsView::keyPressEvent(event); + keyPress(event); +} + +bool SkeletonGraphicsWidget::mouseMove(QMouseEvent *event) +{ + if (m_dragStarted) { + QPoint currentGlobalPos = event->globalPos(); + if (verticalScrollBar()) + verticalScrollBar()->setValue(verticalScrollBar()->value() + m_lastGlobalPos.y() - currentGlobalPos.y()); + if (horizontalScrollBar()) + horizontalScrollBar()->setValue(horizontalScrollBar()->value() + m_lastGlobalPos.x() - currentGlobalPos.x()); + m_lastGlobalPos = currentGlobalPos; + return true; + } + + if (SkeletonDocumentEditMode::Select == m_document->editMode || + SkeletonDocumentEditMode::Add == m_document->editMode) { + SkeletonGraphicsNodeItem *newHoverNodeItem = nullptr; + SkeletonGraphicsEdgeItem *newHoverEdgeItem = nullptr; + QList items = scene()->items(mouseEventScenePos(event)); + for (auto it = items.begin(); it != items.end(); it++) { + QGraphicsItem *item = *it; + if (item->data(0) == "node") { + newHoverNodeItem = (SkeletonGraphicsNodeItem *)item; + break; + } else if (item->data(0) == "edge") { + newHoverEdgeItem = (SkeletonGraphicsEdgeItem *)item; + } + } + if (newHoverNodeItem) { + newHoverEdgeItem = nullptr; + } + if (newHoverNodeItem != m_hoveredNodeItem) { + if (nullptr != m_hoveredNodeItem) { + m_hoveredNodeItem->setHovered(false); + } + m_hoveredNodeItem = newHoverNodeItem; + if (nullptr != m_hoveredNodeItem) { + m_hoveredNodeItem->setHovered(true); + } + } + if (newHoverEdgeItem != m_hoveredEdgeItem) { + if (nullptr != m_hoveredEdgeItem) { + m_hoveredEdgeItem->setHovered(false); + } + m_hoveredEdgeItem = newHoverEdgeItem; + if (nullptr != m_hoveredEdgeItem) { + m_hoveredEdgeItem->setHovered(true); + } + } + } + + if (SkeletonDocumentEditMode::Add == m_document->editMode) { + QPointF mouseScenePos = mouseEventScenePos(event); + m_cursorNodeItem->setOrigin(mouseScenePos); + if (!m_cursorNodeItem->isVisible()) { + m_cursorNodeItem->show(); + } + if (m_checkedNodeItem) { + m_cursorEdgeItem->setEndpoints(m_checkedNodeItem, m_cursorNodeItem); + if (!m_cursorEdgeItem->isVisible()) { + m_cursorEdgeItem->show(); + } + } + return true; + } + + if (SkeletonDocumentEditMode::Select == m_document->editMode) { + if (m_moveStarted && m_checkedNodeItem) { + QPointF mouseScenePos = mouseEventScenePos(event); + float byX = sceneRadiusToUnified(mouseScenePos.x() - m_lastScenePos.x()); + float byY = sceneRadiusToUnified(mouseScenePos.y() - m_lastScenePos.y()); + if (SkeletonProfile::Main == m_checkedNodeItem->profile()) { + emit moveNodeBy(m_checkedNodeItem->id(), byX, byY, 0); + } else { + emit moveNodeBy(m_checkedNodeItem->id(), 0, byY, byX); + } + m_lastScenePos = mouseScenePos; + return true; + } + } + + return false; +} + +bool SkeletonGraphicsWidget::wheel(QWheelEvent *event) +{ + qreal delta = event->delta() / 10; + if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { + if (delta > 0) + delta = 1; + else + delta = -1; + } else { + if (fabs(delta) < 1) + delta = delta < 0 ? -1.0 : 1.0; + } + if (SkeletonDocumentEditMode::Add == m_document->editMode) { + if (m_cursorNodeItem->isVisible()) { + m_cursorNodeItem->setRadius(m_cursorNodeItem->radius() + delta); + return true; + } + } else if (SkeletonDocumentEditMode::Select == m_document->editMode) { + if (m_hoveredNodeItem) { + emit scaleNodeByAddRadius(m_hoveredNodeItem->id(), sceneRadiusToUnified(delta)); + return true; + } else if (m_checkedNodeItem) { + emit scaleNodeByAddRadius(m_checkedNodeItem->id(), sceneRadiusToUnified(delta)); + return true; + } + } + return false; +} + +bool SkeletonGraphicsWidget::mouseRelease(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + bool processed = m_dragStarted || m_moveStarted; + if (m_dragStarted) { + m_dragStarted = false; + updateCursor(); + } + if (m_moveStarted) { + m_moveStarted = false; + } + return processed; + } + return false; +} + +QPointF SkeletonGraphicsWidget::mouseEventScenePos(QMouseEvent *event) +{ + return mapToScene(mapFromGlobal(event->globalPos())); +} + +bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + if (SkeletonDocumentEditMode::ZoomIn == m_document->editMode) { + ViewportAnchor lastAnchor = transformationAnchor(); + setTransformationAnchor(QGraphicsView::AnchorUnderMouse) ; + scale(1.5, 1.5); + setTransformationAnchor(lastAnchor); + return true; + } else if (SkeletonDocumentEditMode::ZoomOut == m_document->editMode) { + ViewportAnchor lastAnchor = transformationAnchor(); + setTransformationAnchor(QGraphicsView::AnchorUnderMouse) ; + scale(0.5, 0.5); + setTransformationAnchor(lastAnchor); + if ((!verticalScrollBar() || !verticalScrollBar()->isVisible()) && + (!horizontalScrollBar() || !horizontalScrollBar()->isVisible())) { + setTransform(QTransform()); + } + return true; + } else if (SkeletonDocumentEditMode::Drag == m_document->editMode) { + if (!m_dragStarted) { + m_lastGlobalPos = event->globalPos(); + m_dragStarted = true; + updateCursor(); + } + } else if (SkeletonDocumentEditMode::Add == m_document->editMode) { + if (m_cursorNodeItem->isVisible()) { + if (m_checkedNodeItem) { + if (m_hoveredNodeItem && m_checkedNodeItem && + m_hoveredNodeItem != m_checkedNodeItem && + m_hoveredNodeItem->profile() == m_checkedNodeItem->profile()) { + if (m_document->findEdgeByNodes(m_checkedNodeItem->id(), m_hoveredNodeItem->id())) + return true; + emit addEdge(m_checkedNodeItem->id(), m_hoveredNodeItem->id()); + return true; + } + } + QPointF mainProfile = m_cursorNodeItem->origin(); + QPointF sideProfile = mainProfile; + if (mainProfile.x() >= scene()->width() / 2) { + sideProfile.setX(mainProfile.x() - scene()->width() / 4); + } else { + sideProfile.setX(mainProfile.x() +scene()->width() / 4); + } + QPointF unifiedMainPos = scenePosToUnified(mainProfile); + QPointF unifiedSidePos = scenePosToUnified(sideProfile); + if (isFloatEqual(m_lastAddedX, unifiedMainPos.x()) && isFloatEqual(m_lastAddedY, unifiedMainPos.y()) && isFloatEqual(m_lastAddedZ, unifiedSidePos.x())) + return true; + m_lastAddedX = unifiedMainPos.x(); + m_lastAddedY = unifiedMainPos.y(); + m_lastAddedZ = unifiedSidePos.x(); + qDebug() << "Emit add node " << m_lastAddedX << m_lastAddedY << m_lastAddedZ; + emit addNode(unifiedMainPos.x(), unifiedMainPos.y(), unifiedSidePos.x(), sceneRadiusToUnified(m_cursorNodeItem->radius()), nullptr == m_checkedNodeItem ? QUuid() : m_checkedNodeItem->id()); + return true; + } + } else if (SkeletonDocumentEditMode::Select == m_document->editMode) { + bool processed = false; + if (m_hoveredNodeItem) { + if (m_checkedNodeItem != m_hoveredNodeItem) { + if (m_checkedNodeItem) { + emit uncheckNode(m_checkedNodeItem->id()); + m_checkedNodeItem->setChecked(false); + } + m_checkedNodeItem = m_hoveredNodeItem; + m_checkedNodeItem->setChecked(true); + emit checkNode(m_checkedNodeItem->id()); + } + m_moveStarted = true; + m_lastScenePos = mouseEventScenePos(event); + processed = true; + } else { + if (m_checkedNodeItem) { + m_checkedNodeItem->setChecked(false); + emit uncheckNode(m_checkedNodeItem->id()); + m_checkedNodeItem = nullptr; + processed = true; + } + } + if (m_hoveredEdgeItem) { + if (m_checkedEdgeItem != m_hoveredEdgeItem) { + if (m_checkedEdgeItem) { + emit uncheckEdge(m_checkedEdgeItem->id()); + m_checkedEdgeItem->setChecked(false); + } + m_checkedEdgeItem = m_hoveredEdgeItem; + m_checkedEdgeItem->setChecked(true); + emit checkEdge(m_checkedEdgeItem->id()); + } + } else { + if (m_checkedEdgeItem) { + m_checkedEdgeItem->setChecked(false); + emit uncheckEdge(m_checkedEdgeItem->id()); + m_checkedEdgeItem = nullptr; + processed = true; + } + } + if (processed) { + return true; + } + } + } + return false; +} + +float SkeletonGraphicsWidget::sceneRadiusToUnified(float radius) +{ + if (0 == scene()->height()) + return 0; + return radius / scene()->height(); +} + +float SkeletonGraphicsWidget::sceneRadiusFromUnified(float radius) +{ + return radius * scene()->height(); +} + +QPointF SkeletonGraphicsWidget::scenePosToUnified(QPointF pos) +{ + if (0 == scene()->height()) + return QPointF(0, 0); + return QPointF(pos.x() / scene()->height(), pos.y() / scene()->height()); +} + +QPointF SkeletonGraphicsWidget::scenePosFromUnified(QPointF pos) +{ + return QPointF(pos.x() * scene()->height(), pos.y() * scene()->height()); +} + +bool SkeletonGraphicsWidget::mouseDoubleClick(QMouseEvent *event) +{ + return false; +} + +bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Delete || event->key() ==Qt::Key_Backspace) { + bool processed = false; + if (m_checkedNodeItem) { + emit removeNode(m_checkedNodeItem->id()); + processed = true; + } + if (m_checkedEdgeItem) { + emit removeEdge(m_checkedEdgeItem->id()); + processed = true; + } + if (processed) { + return true; + } + } else if (event->key() == Qt::Key_A) { + if (SkeletonDocumentEditMode::Add == m_document->editMode) { + emit setEditMode(SkeletonDocumentEditMode::Select); + } else { + emit setEditMode(SkeletonDocumentEditMode::Add); + } + return true; + } + return false; +} + +void SkeletonGraphicsWidget::nodeAdded(QUuid nodeId) +{ + const SkeletonNode *node = m_document->findNode(nodeId); + if (nullptr == node) { + qDebug() << "New node added but node id not exist:" << nodeId; + return; + } + SkeletonGraphicsNodeItem *mainProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Main); + SkeletonGraphicsNodeItem *sideProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Side); + mainProfileItem->setOrigin(scenePosFromUnified(QPointF(node->x, node->y))); + sideProfileItem->setOrigin(scenePosFromUnified(QPointF(node->z, node->y))); + mainProfileItem->setRadius(sceneRadiusFromUnified(node->radius)); + sideProfileItem->setRadius(sceneRadiusFromUnified(node->radius)); + mainProfileItem->setId(nodeId); + sideProfileItem->setId(nodeId); + scene()->addItem(mainProfileItem); + scene()->addItem(sideProfileItem); + nodeItemMap[nodeId] = std::make_pair(mainProfileItem, sideProfileItem); + + if (nullptr == m_checkedNodeItem) { + m_checkedNodeItem = mainProfileItem; + } else { + if (SkeletonProfile::Main == m_checkedNodeItem->profile()) { + m_checkedNodeItem = mainProfileItem; + } else { + m_checkedNodeItem = sideProfileItem; + } + m_cursorEdgeItem->setEndpoints(m_checkedNodeItem, m_cursorNodeItem); + } +} + +void SkeletonGraphicsWidget::edgeAdded(QUuid edgeId) +{ + const SkeletonEdge *edge = m_document->findEdge(edgeId); + if (nullptr == edge) { + qDebug() << "New edge added but edge id not exist:" << edgeId; + return; + } + if (edge->nodeIds.size() != 2) { + qDebug() << "Invalid node count of edge:" << edgeId; + return; + } + QUuid fromNodeId = edge->nodeIds[0]; + QUuid toNodeId = edge->nodeIds[1]; + auto fromIt = nodeItemMap.find(fromNodeId); + if (fromIt == nodeItemMap.end()) { + qDebug() << "Node not found:" << fromNodeId; + return; + } + auto toIt = nodeItemMap.find(toNodeId); + if (toIt == nodeItemMap.end()) { + qDebug() << "Node not found:" << toNodeId; + return; + } + SkeletonGraphicsEdgeItem *mainProfileEdgeItem = new SkeletonGraphicsEdgeItem(); + SkeletonGraphicsEdgeItem *sideProfileEdgeItem = new SkeletonGraphicsEdgeItem(); + mainProfileEdgeItem->setId(edgeId); + sideProfileEdgeItem->setId(edgeId); + mainProfileEdgeItem->setEndpoints(fromIt->second.first, toIt->second.first); + sideProfileEdgeItem->setEndpoints(fromIt->second.second, toIt->second.second); + scene()->addItem(mainProfileEdgeItem); + scene()->addItem(sideProfileEdgeItem); + edgeItemMap[edgeId] = std::make_pair(mainProfileEdgeItem, sideProfileEdgeItem); +} + +void SkeletonGraphicsWidget::nodeRemoved(QUuid nodeId) +{ + m_lastAddedX = 0; + m_lastAddedY = 0; + m_lastAddedZ = 0; + auto nodeItemIt = nodeItemMap.find(nodeId); + if (nodeItemIt == nodeItemMap.end()) { + qDebug() << "Node removed but node id not exist:" << nodeId; + return; + } + if (m_hoveredNodeItem == nodeItemIt->second.first) + m_hoveredNodeItem = nullptr; + if (m_hoveredNodeItem == nodeItemIt->second.second) + m_hoveredNodeItem = nullptr; + if (m_checkedNodeItem == nodeItemIt->second.first) + m_checkedNodeItem = nullptr; + if (m_checkedNodeItem == nodeItemIt->second.second) + m_checkedNodeItem = nullptr; + delete nodeItemIt->second.first; + delete nodeItemIt->second.second; + nodeItemMap.erase(nodeItemIt); +} + +void SkeletonGraphicsWidget::edgeRemoved(QUuid edgeId) +{ + auto edgeItemIt = edgeItemMap.find(edgeId); + if (edgeItemIt == edgeItemMap.end()) { + qDebug() << "Edge removed but edge id not exist:" << edgeId; + return; + } + if (m_hoveredEdgeItem == edgeItemIt->second.first) + m_hoveredEdgeItem = nullptr; + if (m_hoveredEdgeItem == edgeItemIt->second.second) + m_hoveredEdgeItem = nullptr; + if (m_checkedEdgeItem == edgeItemIt->second.first) + m_checkedEdgeItem = nullptr; + if (m_checkedEdgeItem == edgeItemIt->second.second) + m_checkedEdgeItem = nullptr; + delete edgeItemIt->second.first; + delete edgeItemIt->second.second; + edgeItemMap.erase(edgeItemIt); +} + +void SkeletonGraphicsWidget::nodeRadiusChanged(QUuid nodeId) +{ + const SkeletonNode *node = m_document->findNode(nodeId); + if (nullptr == node) { + qDebug() << "Node changed but node id not exist:" << nodeId; + return; + } + auto it = nodeItemMap.find(nodeId); + if (it == nodeItemMap.end()) { + qDebug() << "Node not found:" << nodeId; + return; + } + float sceneRadius = sceneRadiusFromUnified(node->radius); + it->second.first->setRadius(sceneRadius); + it->second.second->setRadius(sceneRadius); +} + +void SkeletonGraphicsWidget::nodeOriginChanged(QUuid nodeId) +{ + const SkeletonNode *node = m_document->findNode(nodeId); + if (nullptr == node) { + qDebug() << "Node changed but node id not exist:" << nodeId; + return; + } + auto it = nodeItemMap.find(nodeId); + if (it == nodeItemMap.end()) { + qDebug() << "Node not found:" << nodeId; + return; + } + QPointF mainPos = scenePosFromUnified(QPointF(node->x, node->y)); + QPointF sidePos = scenePosFromUnified(QPointF(node->z, node->y)); + it->second.first->setOrigin(mainPos); + it->second.second->setOrigin(sidePos); + for (auto edgeIt = node->edgeIds.begin(); edgeIt != node->edgeIds.end(); edgeIt++) { + auto edgeItemIt = edgeItemMap.find(*edgeIt); + if (edgeItemIt == edgeItemMap.end()) { + qDebug() << "Edge item not found:" << *edgeIt; + continue; + } + edgeItemIt->second.first->updateAppearance(); + edgeItemIt->second.second->updateAppearance(); + } +} + +void SkeletonGraphicsWidget::edgeChanged(QUuid edgeId) +{ +} + diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h new file mode 100644 index 00000000..62c2e99b --- /dev/null +++ b/src/skeletongraphicswidget.h @@ -0,0 +1,320 @@ +#ifndef SKELETON_GRAPHICS_VIEW_H +#define SKELETON_GRAPHICS_VIEW_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "skeletondocument.h" +#include "turnaroundloader.h" +#include "theme.h" + +class SkeletonGraphicsNodeItem : public QGraphicsEllipseItem +{ +public: + SkeletonGraphicsNodeItem(SkeletonProfile profile=SkeletonProfile::Unknown) : + m_profile(profile), + m_hovered(false), + m_checked(false) + { + setData(0, "node"); + setRadius(32); + } + + void updateAppearance() + { + QColor color = Theme::white; + + switch (m_profile) + { + case SkeletonProfile::Unknown: + break; + case SkeletonProfile::Main: + color = Theme::red; + break; + case SkeletonProfile::Side: + color = Theme::green; + break; + } + + QColor penColor = color; + penColor.setAlphaF(m_checked ? Theme::checkedAlpha : Theme::normalAlpha); + QPen pen(penColor); + pen.setWidth(0); + setPen(pen); + + QColor brushColor = color; + brushColor.setAlphaF((m_checked || m_hovered) ? Theme::fillAlpha : 0); + QBrush brush(brushColor); + setBrush(brush); + } + + void setOrigin(QPointF point) + { + QPointF moveBy = point - origin(); + QRectF newRect = rect(); + newRect.adjust(moveBy.x(), moveBy.y(), moveBy.x(), moveBy.y()); + setRect(newRect); + updateAppearance(); + } + + QPointF origin() + { + return QPointF(rect().x() + rect().width() / 2, + rect().y() + rect().height() / 2); + } + + float radius() + { + return rect().width() / 2; + } + + void setRadius(float radius) + { + if (radius < 4) + radius = 4; + QPointF oldOrigin = origin(); + setRect(oldOrigin.x() - radius, oldOrigin.y() - radius, + radius * 2, radius * 2); + updateAppearance(); + } + + SkeletonProfile profile() + { + return m_profile; + } + + QUuid id() + { + return m_uuid; + } + + void setId(QUuid id) + { + m_uuid = id; + } + + void setHovered(bool hovered) + { + m_hovered = hovered; + updateAppearance(); + } + + void setChecked(bool checked) + { + m_checked = checked; + updateAppearance(); + } + + bool checked() + { + return m_checked; + } +private: + QUuid m_uuid; + SkeletonProfile m_profile; + bool m_hovered; + bool m_checked; +}; + +class SkeletonGraphicsEdgeItem : public QGraphicsPolygonItem +{ +public: + SkeletonGraphicsEdgeItem() : + m_firstItem(nullptr), + m_secondItem(nullptr), + m_hovered(false), + m_checked(false) + { + setData(0, "edge"); + } + + void setEndpoints(SkeletonGraphicsNodeItem *first, SkeletonGraphicsNodeItem *second) + { + m_firstItem = first; + m_secondItem = second; + updateAppearance(); + } + + void updateAppearance() + { + if (nullptr == m_firstItem || nullptr == m_secondItem) + return; + + QLineF line(m_firstItem->origin(), m_secondItem->origin()); + + QPolygonF polygon; + float radAngle = line.angle() * M_PI / 180; + float dx = 5 * sin(radAngle); + float dy = 5 * cos(radAngle); + QPointF offset1 = QPointF(dx, dy); + QPointF offset2 = QPointF(-dx, -dy); + polygon << line.p1() + offset1 << line.p1() + offset2 << line.p2() + offset2 << line.p2() + offset1; + setPolygon(polygon); + + QColor color = Theme::white; + + switch (m_firstItem->profile()) + { + case SkeletonProfile::Unknown: + break; + case SkeletonProfile::Main: + color = Theme::red; + break; + case SkeletonProfile::Side: + color = Theme::green; + break; + } + + QColor penColor = color; + penColor.setAlphaF((m_checked || m_hovered) ? Theme::checkedAlpha : Theme::normalAlpha); + QPen pen(penColor); + pen.setWidth(0); + setPen(pen); + } + + QUuid id() + { + return m_uuid; + } + + void setId(QUuid id) + { + m_uuid = id; + } + + void setHovered(bool hovered) + { + m_hovered = hovered; + updateAppearance(); + } + + void setChecked(bool checked) + { + m_checked = checked; + updateAppearance(); + } + +private: + QUuid m_uuid; + SkeletonGraphicsNodeItem *m_firstItem; + SkeletonGraphicsNodeItem *m_secondItem; + QPolygonF m_selectionPolygon; + bool m_hovered; + bool m_checked; +}; + +class SkeletonGraphicsFunctions +{ +public: + virtual bool mouseMove(QMouseEvent *event) = 0; + virtual bool wheel(QWheelEvent *event) = 0; + virtual bool mouseRelease(QMouseEvent *event) = 0; + virtual bool mousePress(QMouseEvent *event) = 0; + virtual bool mouseDoubleClick(QMouseEvent *event) = 0; + virtual bool keyPress(QKeyEvent *event) = 0; +}; + +class SkeletonGraphicsWidget : public QGraphicsView, public SkeletonGraphicsFunctions +{ + Q_OBJECT +signals: + void addNode(float x, float y, float z, float radius, QUuid fromNodeId); + void uncheckNode(QUuid nodeId); + void checkNode(QUuid nodeId); + void uncheckEdge(QUuid edgeId); + void checkEdge(QUuid edgeId); + void scaleNodeByAddRadius(QUuid nodeId, float amount); + void moveNodeBy(QUuid nodeId, float x, float y, float z); + void removeNode(QUuid nodeId); + void setEditMode(SkeletonDocumentEditMode mode); + void removeEdge(QUuid edgeId); + void addEdge(QUuid fromNodeId, QUuid toNodeId); + void cursorChanged(); +public: + SkeletonGraphicsWidget(const SkeletonDocument *document); + std::map> nodeItemMap; + std::map> edgeItemMap; + bool mouseMove(QMouseEvent *event); + bool wheel(QWheelEvent *event); + bool mouseRelease(QMouseEvent *event); + bool mousePress(QMouseEvent *event); + bool mouseDoubleClick(QMouseEvent *event); + bool keyPress(QKeyEvent *event); +protected: + void mouseMoveEvent(QMouseEvent *event); + void wheelEvent(QWheelEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); + void keyPressEvent(QKeyEvent *event); +public slots: + void nodeAdded(QUuid nodeId); + void edgeAdded(QUuid edgeId); + void nodeRemoved(QUuid nodeId); + void edgeRemoved(QUuid edgeId); + void nodeRadiusChanged(QUuid nodeId); + void nodeOriginChanged(QUuid nodeId); + void edgeChanged(QUuid edgeId); + void turnaroundChanged(); + void canvasResized(); + void editModeChanged(); + void updateCursor(); +private slots: + void turnaroundImageReady(); +private: + QPointF mouseEventScenePos(QMouseEvent *event); + QPointF scenePosToUnified(QPointF pos); + QPointF scenePosFromUnified(QPointF pos); + float sceneRadiusToUnified(float radius); + float sceneRadiusFromUnified(float radius); + void updateTurnaround(); + void updateItems(); +private: + QGraphicsPixmapItem *m_backgroundItem; + const SkeletonDocument *m_document; + bool m_turnaroundChanged; + TurnaroundLoader *m_turnaroundLoader; + bool m_dragStarted; + QPoint m_lastGlobalPos; + bool m_moveStarted; + QPointF m_lastScenePos; + SkeletonGraphicsNodeItem *m_cursorNodeItem; + SkeletonGraphicsEdgeItem *m_cursorEdgeItem; + SkeletonGraphicsNodeItem *m_checkedNodeItem; + SkeletonGraphicsNodeItem *m_hoveredNodeItem; + SkeletonGraphicsEdgeItem *m_hoveredEdgeItem; + SkeletonGraphicsEdgeItem *m_checkedEdgeItem; + float m_lastAddedX; + float m_lastAddedY; + float m_lastAddedZ; +}; + +class SkeletonGraphicsContainerWidget : public QWidget +{ + Q_OBJECT +signals: + void containerSizeChanged(QSize size); +public: + SkeletonGraphicsContainerWidget() : + m_graphicsWidget(nullptr) + { + } + void resizeEvent(QResizeEvent *event) override + { + if (m_graphicsWidget && m_graphicsWidget->size() != event->size()) + emit containerSizeChanged(event->size()); + } + void setGraphicsWidget(SkeletonGraphicsWidget *graphicsWidget) + { + m_graphicsWidget = graphicsWidget; + } +private: + SkeletonGraphicsWidget *m_graphicsWidget; +}; + +#endif diff --git a/src/skeletonhistorylistwidget.cpp b/src/skeletonhistorylistwidget.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/skeletonhistorylistwidget.h b/src/skeletonhistorylistwidget.h new file mode 100644 index 00000000..e69de29b diff --git a/src/skeletonnodepropertywidget.cpp b/src/skeletonnodepropertywidget.cpp new file mode 100644 index 00000000..88a88101 --- /dev/null +++ b/src/skeletonnodepropertywidget.cpp @@ -0,0 +1,50 @@ +#include +#include "skeletonnodepropertywidget.h" + +SkeletonNodePropertyWidget::SkeletonNodePropertyWidget(const SkeletonDocument *document) : + m_rootMarkModeComboBox(nullptr), + m_document(document) +{ + m_rootMarkModeComboBox = new QComboBox; + + m_rootMarkModeComboBox->insertItem(0, tr("Auto"), (int)SkeletonNodeRootMarkMode::Auto); + m_rootMarkModeComboBox->insertItem(1, tr("Mark as Root"), (int)SkeletonNodeRootMarkMode::MarkAsRoot); + m_rootMarkModeComboBox->insertItem(2, tr("Mark as Not Root"), (int)SkeletonNodeRootMarkMode::MarkAsNotRoot); + + QFormLayout *formLayout = new QFormLayout; + formLayout->addRow(tr("Root Mark:"), m_rootMarkModeComboBox); + + setLayout(formLayout); + + hide(); + + connect(m_rootMarkModeComboBox, static_cast(&QComboBox::currentIndexChanged), [=](int index) { + if (-1 != index) { + int mode = m_rootMarkModeComboBox->itemData(index).toInt(); + emit setNodeRootMarkMode(m_nodeId, static_cast(mode)); + } + }); +} + +void SkeletonNodePropertyWidget::showProperties(QUuid nodeId) +{ + m_nodeId = nodeId; + updateData(); + show(); +} + +void SkeletonNodePropertyWidget::updateData() +{ + const SkeletonNode *node = m_document->findNode(m_nodeId); + if (nullptr == node) { + hide(); + return; + } + int selectIndex = m_rootMarkModeComboBox->findData((int)node->rootMarkMode); + m_rootMarkModeComboBox->setCurrentIndex(selectIndex); +} + +QUuid SkeletonNodePropertyWidget::currentNodeId() +{ + return m_nodeId; +} diff --git a/src/skeletonnodepropertywidget.h b/src/skeletonnodepropertywidget.h new file mode 100644 index 00000000..6f3dcd95 --- /dev/null +++ b/src/skeletonnodepropertywidget.h @@ -0,0 +1,26 @@ +#ifndef SKELETON_NODE_PROPERTY_WIDGET_H +#define SKELETON_NODE_PROPERTY_WIDGET_H +#include +#include +#include +#include "skeletondocument.h" + +class SkeletonNodePropertyWidget : public QWidget +{ + Q_OBJECT +signals: + void setNodeRootMarkMode(QUuid nodeId, SkeletonNodeRootMarkMode mode); +public slots: + void showProperties(QUuid nodeId); +public: + SkeletonNodePropertyWidget(const SkeletonDocument *document); + QUuid currentNodeId(); +private: + void updateData(); +private: + QComboBox *m_rootMarkModeComboBox; + const SkeletonDocument *m_document; + QUuid m_nodeId; +}; + +#endif diff --git a/src/skeletonpartlistwidget.cpp b/src/skeletonpartlistwidget.cpp new file mode 100644 index 00000000..f6c5a82f --- /dev/null +++ b/src/skeletonpartlistwidget.cpp @@ -0,0 +1,89 @@ +#include +#include +#include "skeletonpartlistwidget.h" +#include "theme.h" + +SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid partId) : + m_document(document), + m_partId(partId) +{ + m_visibleButton = new QPushButton(QChar(fa::eye)); + //m_visibleButton->setStyleSheet("QPushButton {border: none; background: none;}"); + m_visibleButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize * 2 / 3)); + m_visibleButton->setFixedSize(Theme::toolIconSize * 2 / 3, Theme::toolIconSize * 2 / 3); + + m_previewLabel = new QLabel; + + //m_nameLabel = new QLabel; + + QGridLayout *mainLayout = new QGridLayout; + mainLayout->addWidget(m_visibleButton, 0, 0); + mainLayout->addWidget(m_previewLabel, 0, 1); + //mainLayout->addWidget(m_nameLabel, 0, 2); + + setLayout(mainLayout); +} + +QLabel *SkeletonPartWidget::previewLabel() +{ + return m_previewLabel; +} + +void SkeletonPartWidget::reload() +{ + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; + } + //m_nameLabel->setText(part->name.isEmpty() ? part->id.toString() : part->name); + m_previewLabel->setPixmap(QPixmap::fromImage(part->preview)); +} + +SkeletonPartListWidget::SkeletonPartListWidget(const SkeletonDocument *document) : + m_document(document) +{ + setSelectionMode(QAbstractItemView::NoSelection); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); +} + +void SkeletonPartListWidget::partChanged(QUuid partId) +{ + auto itemIt = m_itemMap.find(partId); + if (itemIt == m_itemMap.end()) { + return; + } +} + +void SkeletonPartListWidget::partListChanged() +{ + clear(); + m_itemMap.clear(); + + for (auto partIdIt = m_document->partIds.begin(); partIdIt != m_document->partIds.end(); partIdIt++) { + QUuid partId = *partIdIt; + QListWidgetItem *item = new QListWidgetItem(this); + item->setSizeHint(QSize(width(), Theme::previewImageSize)); + addItem(item); + SkeletonPartWidget *widget = new SkeletonPartWidget(m_document, partId); + setItemWidget(item, widget); + widget->reload(); + m_itemMap[partId] = item; + } +} + +void SkeletonPartListWidget::partPreviewChanged(QUuid partid) +{ + const SkeletonPart *part = m_document->findPart(partid); + if (!part) { + qDebug() << "Part not found:" << partid; + return; + } + auto item = m_itemMap.find(partid); + if (item == m_itemMap.end()) { + qDebug() << "Part item not found:" << partid; + return; + } + SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second); + widget->previewLabel()->setPixmap(QPixmap::fromImage(part->preview)); +} diff --git a/src/skeletonpartlistwidget.h b/src/skeletonpartlistwidget.h new file mode 100644 index 00000000..385d40d4 --- /dev/null +++ b/src/skeletonpartlistwidget.h @@ -0,0 +1,38 @@ +#ifndef SKELETON_PART_LIST_WIDGET_H +#define SKELETON_PART_LIST_WIDGET_H +#include +#include +#include +#include +#include "skeletondocument.h" + +class SkeletonPartWidget : public QWidget +{ + Q_OBJECT +public: + SkeletonPartWidget(const SkeletonDocument *document, QUuid partId); + void reload(); + QLabel *previewLabel(); +private: + const SkeletonDocument *m_document; + QUuid m_partId; + QLabel *m_previewLabel; + QPushButton *m_visibleButton; + QLabel *m_nameLabel; +}; + +class SkeletonPartListWidget : public QListWidget +{ + Q_OBJECT +public: + SkeletonPartListWidget(const SkeletonDocument *document); +public slots: + void partChanged(QUuid partId); + void partListChanged(); + void partPreviewChanged(QUuid partid); +private: + const SkeletonDocument *m_document; + std::map m_itemMap; +}; + +#endif diff --git a/src/skeletonsnapshot.h b/src/skeletonsnapshot.h index e8a33971..b94974dc 100644 --- a/src/skeletonsnapshot.h +++ b/src/skeletonsnapshot.h @@ -12,6 +12,8 @@ public: std::map canvas; std::map> nodes; std::map> edges; + std::map> parts; + std::vector partIdList; public: SkeletonSnapshot(); void splitByConnectivity(std::vector *groups); diff --git a/src/skeletontomesh.cpp b/src/skeletontomesh.cpp deleted file mode 100644 index f8ae1567..00000000 --- a/src/skeletontomesh.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include "skeletontomesh.h" -#include "meshlite.h" -#include "skeletoneditnodeitem.h" -#include "skeletoneditedgeitem.h" -#include "skeletonsnapshot.h" -#include "unionmesh.h" -#include - -struct NodeItemInfo -{ - int index; - int neighborCount; -}; - -SkeletonToMesh::SkeletonToMesh(SkeletonSnapshot *snapshot) : - m_mesh(NULL), - m_snapshot(*snapshot) -{ -} - -SkeletonToMesh::~SkeletonToMesh() -{ - delete m_mesh; -} - -Mesh *SkeletonToMesh::takeResultMesh() -{ - Mesh *mesh = m_mesh; - m_mesh = NULL; - return mesh; -} - -#if USE_CARVE == 1 -#define ExternalMesh carve::poly::Polyhedron -#define makeExternalMeshFromMeshlite makeCarveMeshFromMeshlite -#define unionExternalMeshs unionCarveMeshs -#define makeMeshliteMeshFromExternal makeMeshliteMeshFromCarve -#endif - -#if USE_CGAL == 1 -#define ExternalMesh CgalMesh -#define makeExternalMeshFromMeshlite makeCgalMeshFromMeshlite -#define unionExternalMeshs unionCgalMeshs -#define makeMeshliteMeshFromExternal makeMeshliteMeshFromCgal -#endif - -void SkeletonToMesh::process() -{ - std::vector groups; - QRectF globalFront = m_snapshot.boundingBoxFront(); - QRectF globalSide = m_snapshot.boundingBoxSide(); - QString rootNodeId = m_snapshot.rootNode(); - printf("rootNodeId:%s\n", rootNodeId.toUtf8().constData()); - float frontMiddleX = m_snapshot.nodes[rootNodeId]["x"].toFloat(); - float frontMiddleY = m_snapshot.nodes[rootNodeId]["y"].toFloat(); - float sideMiddleX = m_snapshot.nodes[m_snapshot.nodes[rootNodeId]["nextSidePair"]]["x"].toFloat(); - bool combineEnabled = "true" == m_snapshot.canvas["combine"]; - bool unionEnabled = "true" == m_snapshot.canvas["union"]; - bool subdivEnabled = "true" == m_snapshot.canvas["subdiv"]; - m_snapshot.splitByConnectivity(&groups); - - std::vector meshIds; - void *meshliteContext = meshlite_create_context(); - for (size_t i = 0; i < groups.size(); i++) { - SkeletonSnapshot *skeleton = &groups[i]; - //QRectF front = skeleton->boundingBoxFront(); - //QRectF side = skeleton->boundingBoxSide(); - //float canvasWidth = skeleton->canvas["width"].toFloat(); - //float canvasHeight = skeleton->canvas["height"].toFloat(); - float canvasHeight = globalFront.height(); - - std::map bmeshNodeMap; - - std::map>::iterator nodeIterator; - int bmeshId = meshlite_bmesh_create(meshliteContext); - for (nodeIterator = skeleton->nodes.begin(); nodeIterator != skeleton->nodes.end(); nodeIterator++) { - if ("red" != nodeIterator->second["sideColorName"]) - continue; - std::map>::iterator nextSidePair = skeleton->nodes.find(nodeIterator->second["nextSidePair"]); - if (nextSidePair == skeleton->nodes.end()) - continue; - float x = (nodeIterator->second["x"].toFloat() - frontMiddleX) / canvasHeight; - float y = (nodeIterator->second["y"].toFloat() - frontMiddleY) / canvasHeight; - float z = (nextSidePair->second["x"].toFloat() - sideMiddleX) / canvasHeight; - float r = nodeIterator->second["radius"].toFloat() / canvasHeight; - int bmeshNodeId = meshlite_bmesh_add_node(meshliteContext, bmeshId, x, y, z, r); - printf("meshlite_bmesh_add_node x:%f y:%f z:%f r:%f nodeName:%s bmeshNodeId:%d\n", x, y, z, r, nodeIterator->first.toUtf8().constData(), bmeshNodeId); - bmeshNodeMap[nodeIterator->first] = bmeshNodeId; - } - - std::map>::iterator edgeIterator; - for (edgeIterator = skeleton->edges.begin(); edgeIterator != skeleton->edges.end(); edgeIterator++) { - std::map::iterator from = bmeshNodeMap.find(edgeIterator->second["from"]); - if (from == bmeshNodeMap.end()) - continue; - std::map::iterator to = bmeshNodeMap.find(edgeIterator->second["to"]); - if (to == bmeshNodeMap.end()) - continue; - printf("meshlite_bmesh_add_edge %d -> %d\n", from->second, to->second); - meshlite_bmesh_add_edge(meshliteContext, bmeshId, from->second, to->second); - } - - if (bmeshNodeMap.size() > 0 && !skeleton->rootNode().isEmpty()) { - int meshId = meshlite_bmesh_generate_mesh(meshliteContext, bmeshId, bmeshNodeMap[skeleton->rootNode()]); - meshIds.push_back(meshId); - } - - meshlite_bmesh_destroy(meshliteContext, bmeshId); - } - - if (meshIds.size() > 0) { - int mergedMeshId = 0; - if (unionEnabled) { - mergedMeshId = unionMeshs(meshliteContext, meshIds); - } else { - mergedMeshId = mergeMeshs(meshliteContext, meshIds); - } - if (combineEnabled) { - if (mergedMeshId > 0) { - mergedMeshId = meshlite_combine_coplanar_faces(meshliteContext, mergedMeshId); - } - } - if (subdivEnabled) { - if (mergedMeshId > 0 && meshlite_is_triangulated_manifold(meshliteContext, mergedMeshId)) { - mergedMeshId = meshlite_subdivide(meshliteContext, mergedMeshId); - } - } - if (mergedMeshId > 0) { - m_mesh = new Mesh(meshliteContext, mergedMeshId); - } - } - - meshlite_destroy_context(meshliteContext); - emit finished(); -} diff --git a/src/skeletontomesh.h b/src/skeletontomesh.h deleted file mode 100644 index 721e5b1a..00000000 --- a/src/skeletontomesh.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef SKELETON_TO_MESH_H -#define SKELETON_TO_MESH_H -#include -#include -#include -#include -#include "skeletoneditgraphicsview.h" -#include "mesh.h" -#include "skeletonsnapshot.h" - -class SkeletonToMesh : public QObject -{ - Q_OBJECT -public: - SkeletonToMesh(SkeletonSnapshot *snapshot); - ~SkeletonToMesh(); - Mesh *takeResultMesh(); -signals: - void finished(); -public slots: - void process(); -private: - Mesh *m_mesh; - SkeletonSnapshot m_snapshot; -}; - -#endif diff --git a/src/skeletonwidget.cpp b/src/skeletonwidget.cpp deleted file mode 100644 index 9dd9bc3e..00000000 --- a/src/skeletonwidget.cpp +++ /dev/null @@ -1,214 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "skeletonwidget.h" -#include "meshlite.h" -#include "skeletontomesh.h" -#include "turnaroundloader.h" - -SkeletonWidget::SkeletonWidget(QWidget *parent) : - QWidget(parent), - m_skeletonToMesh(NULL), - m_skeletonDirty(false), - m_turnaroundLoader(NULL), - m_turnaroundDirty(false) -{ - QHBoxLayout *topLayout = new QHBoxLayout; - topLayout->addStretch(); - topLayout->setContentsMargins(0, 0, 0, 0); - - m_graphicsView = new SkeletonEditGraphicsView(this); - m_graphicsView->setRenderHint(QPainter::Antialiasing, false); - m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - m_graphicsView->setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern)); - m_graphicsView->setContentsMargins(0, 0, 0, 0); - m_graphicsView->setFrameStyle(QFrame::NoFrame); - - /* - m_modelWidget = new ModelWidget(this); - m_modelWidget->setMinimumSize(128, 128); - m_modelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - m_modelWidget->setWindowFlags(Qt::Tool | Qt::Window); - m_modelWidget->setWindowTitle("3D Model"); - */ - - QVBoxLayout *rightLayout = new QVBoxLayout; - rightLayout->addSpacing(0); - rightLayout->addStretch(); - rightLayout->setContentsMargins(0, 0, 0, 0); - - QToolBar *toolbar = new QToolBar; - toolbar->setIconSize(QSize(18, 18)); - toolbar->setOrientation(Qt::Vertical); - - QAction *addAction = new QAction(tr("Add"), this); - addAction->setIcon(QPixmap(":/resources/add.svg")); - toolbar->addAction(addAction); - - QAction *selectAction = new QAction(tr("Select"), this); - selectAction->setIcon(QPixmap(":/resources/rotate.svg")); - toolbar->addAction(selectAction); - - QAction *rangeSelectAction = new QAction(tr("Range Select"), this); - rangeSelectAction->setIcon(QPixmap(":/resources/zoomin.svg")); - toolbar->addAction(rangeSelectAction); - - toolbar->setContentsMargins(0, 0, 0, 0); - - QVBoxLayout *leftLayout = new QVBoxLayout; - leftLayout->addWidget(toolbar); - leftLayout->addStretch(); - leftLayout->addSpacing(0); - leftLayout->setContentsMargins(0, 0, 0, 0); - - QHBoxLayout *middleLayout = new QHBoxLayout; - //middleLayout->addLayout(leftLayout); - middleLayout->addWidget(m_graphicsView); - //middleLayout->addLayout(rightLayout); - middleLayout->setContentsMargins(0, 0, 0, 0); - - QVBoxLayout *mainLayout = new QVBoxLayout; - //mainLayout->addLayout(topLayout); - //mainLayout->addSpacing(10); - mainLayout->addLayout(middleLayout); - mainLayout->setContentsMargins(0, 0, 0, 0); - - setLayout(mainLayout); - setContentsMargins(0, 0, 0, 0); - - setWindowTitle(tr("Dust 3D")); - - bool connectResult; - - connectResult = connect(addAction, SIGNAL(triggered(bool)), m_graphicsView, SLOT(turnOnAddNodeMode())); - assert(connectResult); - - connectResult = connectResult = connect(selectAction, SIGNAL(triggered(bool)), m_graphicsView, SLOT(turnOffAddNodeMode())); - assert(connectResult); - - connectResult = connect(m_graphicsView, SIGNAL(nodesChanged()), this, SLOT(skeletonChanged())); - assert(connectResult); - - connectResult = connect(m_graphicsView, SIGNAL(sizeChanged()), this, SLOT(turnaroundChanged())); - assert(connectResult); - - connectResult = connect(m_graphicsView, SIGNAL(changeTurnaroundTriggered()), this, SLOT(changeTurnaround())); - assert(connectResult); - - //connectResult = connect(clipButton, SIGNAL(clicked()), this, SLOT(saveClip())); - //assert(connectResult); -} - -SkeletonEditGraphicsView *SkeletonWidget::graphicsView() -{ - return m_graphicsView; -} - -ModelWidget *SkeletonWidget::modelWidget() -{ - //return m_modelWidget; - return graphicsView()->modelWidget(); -} - -void SkeletonWidget::showModelingWidgetAtCorner() -{ - /* - if (!m_modelWidget->isVisible()) { - QPoint pos = QPoint(QApplication::desktop()->width(), - QApplication::desktop()->height()); - m_modelWidget->move(pos.x() - m_modelWidget->width(), - pos.y() - m_modelWidget->height()); - m_modelWidget->show(); - }*/ -} - -void SkeletonWidget::meshReady() -{ - Mesh *resultMesh = m_skeletonToMesh->takeResultMesh(); - //showModelingWidgetAtCorner(); - //m_modelWidget->updateMesh(resultMesh); - modelWidget()->updateMesh(resultMesh); - delete m_skeletonToMesh; - m_skeletonToMesh = NULL; - if (m_skeletonDirty) { - skeletonChanged(); - } -} - -void SkeletonWidget::skeletonChanged() -{ - if (m_skeletonToMesh) { - m_skeletonDirty = true; - return; - } - - m_skeletonDirty = false; - - QThread *thread = new QThread; - SkeletonSnapshot snapshot; - m_graphicsView->saveToSnapshot(&snapshot); - m_skeletonToMesh = new SkeletonToMesh(&snapshot); - m_skeletonToMesh->moveToThread(thread); - connect(thread, SIGNAL(started()), m_skeletonToMesh, SLOT(process())); - connect(m_skeletonToMesh, SIGNAL(finished()), this, SLOT(meshReady())); - connect(m_skeletonToMesh, SIGNAL(finished()), thread, SLOT(quit())); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - thread->start(); -} - -void SkeletonWidget::turnaroundChanged() -{ - if (m_turnaroundFilename.isEmpty()) - return; - - if (m_turnaroundLoader) { - m_turnaroundDirty = true; - return; - } - - m_turnaroundDirty = false; - - QThread *thread = new QThread; - m_turnaroundLoader = new TurnaroundLoader(m_turnaroundFilename, - m_graphicsView->rect().size()); - m_turnaroundLoader->moveToThread(thread); - connect(thread, SIGNAL(started()), m_turnaroundLoader, SLOT(process())); - connect(m_turnaroundLoader, SIGNAL(finished()), this, SLOT(turnaroundImageReady())); - connect(m_turnaroundLoader, SIGNAL(finished()), thread, SLOT(quit())); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); - thread->start(); -} - -void SkeletonWidget::turnaroundImageReady() -{ - QImage *backgroundImage = m_turnaroundLoader->takeResultImage(); - if (backgroundImage && backgroundImage->width() > 0 && backgroundImage->height() > 0) { - m_graphicsView->updateBackgroundImage(*backgroundImage); - } - delete backgroundImage; - delete m_turnaroundLoader; - m_turnaroundLoader = NULL; - if (m_turnaroundDirty) { - turnaroundChanged(); - } -} - -void SkeletonWidget::changeTurnaround() -{ - QString fileName = QFileDialog::getOpenFileName(this, tr("Open Turnaround Reference Image"), - QString(), - tr("Image Files (*.png *.jpg *.bmp)")).trimmed(); - if (fileName.isEmpty()) - return; - m_turnaroundFilename = fileName; - turnaroundChanged(); -} - diff --git a/src/skeletonwidget.h b/src/skeletonwidget.h deleted file mode 100644 index 73796e8f..00000000 --- a/src/skeletonwidget.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef SKELETON_WIDGET_H -#define SKELETON_WIDGET_H -#include -#include -#include -#include -#include "modelwidget.h" -#include "skeletontomesh.h" -#include "turnaroundloader.h" -#include "skeletoneditgraphicsview.h" - -class SkeletonWidget : public QWidget -{ - Q_OBJECT -public: - SkeletonWidget(QWidget *parent=0); - SkeletonEditGraphicsView *graphicsView(); - ModelWidget *modelWidget(); -public slots: - void skeletonChanged(); - void meshReady(); - void turnaroundChanged(); - void turnaroundImageReady(); - void changeTurnaround(); - void showModelingWidgetAtCorner(); -private: - ModelWidget *m_modelWidget; - SkeletonEditGraphicsView *m_graphicsView; - SkeletonToMesh *m_skeletonToMesh; - bool m_skeletonDirty; - TurnaroundLoader *m_turnaroundLoader; - bool m_turnaroundDirty; - QString m_turnaroundFilename; -}; - -#endif diff --git a/src/theme.cpp b/src/theme.cpp index 2590a3e9..99eee21e 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -25,6 +25,23 @@ float Theme::edgeAlpha = 1.0; float Theme::fillAlpha = 50.0 / 255; int Theme::skeletonNodeBorderSize = 0; int Theme::skeletonEdgeWidth = 0; +int Theme::toolIconFontSize = 16; +int Theme::toolIconSize = 24; +int Theme::previewImageSize = 64; + +QtAwesome *Theme::awesome() +{ + static QtAwesome *s_awesome = nullptr; + if (nullptr == s_awesome) { + s_awesome = new QtAwesome(); + s_awesome->initFontAwesome(); + s_awesome->setDefaultOption("color", Theme::white); + s_awesome->setDefaultOption("color-disabled", QColor(0xcc, 0xcc, 0xcc)); + s_awesome->setDefaultOption("color-active", Theme::white); + s_awesome->setDefaultOption("color-selected", Theme::white); + } + return s_awesome; +} std::map createSideColorNameMap() { std::map map; diff --git a/src/theme.h b/src/theme.h index 8bc81764..594ee0c0 100644 --- a/src/theme.h +++ b/src/theme.h @@ -3,6 +3,7 @@ #include #include #include +#include "QtAwesome.h" class Theme { @@ -22,6 +23,10 @@ public: static QString tabButtonStylesheet; static std::map nextSideColorNameMap; static std::map sideColorNameToColorMap; + static QtAwesome *awesome(); + static int toolIconFontSize; + static int toolIconSize; + static int previewImageSize; }; #endif diff --git a/src/turnaroundloader.cpp b/src/turnaroundloader.cpp index 145786c6..f4ec127b 100644 --- a/src/turnaroundloader.cpp +++ b/src/turnaroundloader.cpp @@ -1,12 +1,18 @@ #include "turnaroundloader.h" TurnaroundLoader::TurnaroundLoader(const QString &filename, QSize viewSize) : - m_resultImage(NULL) + m_resultImage(nullptr) { m_filename = filename; m_viewSize = viewSize; } +TurnaroundLoader::TurnaroundLoader(const QImage &image, QSize viewSize) +{ + m_inputImage = image; + m_viewSize = viewSize; +} + TurnaroundLoader::~TurnaroundLoader() { delete m_resultImage; @@ -15,13 +21,17 @@ TurnaroundLoader::~TurnaroundLoader() QImage *TurnaroundLoader::takeResultImage() { QImage *returnImage = m_resultImage; - m_resultImage = NULL; + m_resultImage = nullptr; return returnImage; } void TurnaroundLoader::process() { - QImage image(m_filename); - m_resultImage = new QImage(image.scaled(m_viewSize, Qt::KeepAspectRatio)); + if (m_inputImage.isNull()) { + QImage image(m_filename); + m_resultImage = new QImage(image.scaled(m_viewSize, Qt::KeepAspectRatio)); + } else { + m_resultImage = new QImage(m_inputImage.scaled(m_viewSize, Qt::KeepAspectRatio)); + } emit finished(); } diff --git a/src/turnaroundloader.h b/src/turnaroundloader.h index 219a121a..dae0a6fd 100644 --- a/src/turnaroundloader.h +++ b/src/turnaroundloader.h @@ -10,6 +10,7 @@ class TurnaroundLoader : public QObject Q_OBJECT public: TurnaroundLoader(const QString &filename, QSize viewSize); + TurnaroundLoader(const QImage &image, QSize viewSize); ~TurnaroundLoader(); QImage *takeResultImage(); signals: @@ -18,6 +19,7 @@ public slots: void process(); private: QImage *m_resultImage; + QImage m_inputImage; QString m_filename; QSize m_viewSize; }; diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 00000000..37eefc8b --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,20 @@ +#include +#include "util.h" + +QString valueOfKeyInMapOrEmpty(const std::map &map, const QString &key) +{ + auto it = map.find(key); + if (it == map.end()) + return QString(); + return it->second; +} + +bool isTrueValueString(const QString &str) +{ + return "true" == str || "True" == str || "1" == str; +} + +bool isFloatEqual(float a, float b) +{ + return fabs(a - b) <= 0.000001; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 00000000..4f614e9b --- /dev/null +++ b/src/util.h @@ -0,0 +1,10 @@ +#ifndef UTIL_H +#define UTIL_H +#include +#include + +QString valueOfKeyInMapOrEmpty(const std::map &map, const QString &key); +bool isTrueValueString(const QString &str); +bool isFloatEqual(float a, float b); + +#endif diff --git a/thirdparty/QtAwesome/LICENSE.md b/thirdparty/QtAwesome/LICENSE.md new file mode 100755 index 00000000..73dc8189 --- /dev/null +++ b/thirdparty/QtAwesome/LICENSE.md @@ -0,0 +1,29 @@ +MIT License +=========== + +Copyright 2013-2015 [Reliable Bits Software by Blommers IT](http://blommersit.nl). All Rights Reserved. +Author [Rick Blommers](mailto:rick@blommersit.nl) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Font Awesome License +==================== + +[https://github.com/FortAwesome/Font-Awesome](https://github.com/FortAwesome/Font-Awesome) + +The Font Awesome font is licensed under the SIL Open Font License - [http://scripts.sil.org/OFL](http://scripts.sil.org/OFL) +The Font Awesome pictograms are licensed under the CC BY 3.0 License - [http://creativecommons.org/licenses/by/3.0/](http://creativecommons.org/licenses/by/3.0/) +"Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome" + + diff --git a/thirdparty/QtAwesome/QtAwesome/QtAwesome.cpp b/thirdparty/QtAwesome/QtAwesome/QtAwesome.cpp new file mode 100755 index 00000000..d0c286d8 --- /dev/null +++ b/thirdparty/QtAwesome/QtAwesome/QtAwesome.cpp @@ -0,0 +1,1146 @@ +/** + * QtAwesome - use font-awesome (or other font icons) in your c++ / Qt Application + * + * MIT Licensed + * + * Copyright 2013-2016 - Reliable Bits Software by Blommers IT. All Rights Reserved. + * Author Rick Blommers + */ + +#include "QtAwesome.h" +#include "QtAwesomeAnim.h" + +#include +#include +#include + + + +/// The font-awesome icon painter +class QtAwesomeCharIconPainter: public QtAwesomeIconPainter +{ + +protected: + + QStringList optionKeysForModeAndState( const QString& key, QIcon::Mode mode, QIcon::State state) + { + QString modePostfix; + switch(mode) { + case QIcon::Disabled: + modePostfix = "-disabled"; + break; + case QIcon::Active: + modePostfix = "-active"; + break; + case QIcon::Selected: + modePostfix = "-selected"; + break; + default: // QIcon::Normal: + // modePostfix = ""; + break; + } + + QString statePostfix; + if( state == QIcon::Off) { + statePostfix = "-off"; + } + + // the keys that need to bet tested: key-mode-state | key-mode | key-state | key + QStringList result; + if( !modePostfix.isEmpty() ) { + if( !statePostfix.isEmpty() ) { + result.push_back( key + modePostfix + statePostfix ); + } + result.push_back( key + modePostfix ); + } + if( !statePostfix.isEmpty() ) { + result.push_back( key + statePostfix ); + } + return result; + } + + + QVariant optionValueForModeAndState( const QString& baseKey, QIcon::Mode mode, QIcon::State state, const QVariantMap& options ) + { + foreach( QString key, optionKeysForModeAndState(baseKey, mode, state) ) { + //if ( options.contains(key) && options.value(key).toString().isEmpty()) qDebug() << "Not found:" << key; + if( options.contains(key) && !(options.value(key).toString().isEmpty()) ) + return options.value(key); + } + + return options.value(baseKey); + } + +public: + + + virtual void paint( QtAwesome* awesome, QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state, const QVariantMap& options ) + { + painter->save(); + + QVariant var =options.value("anim"); + QtAwesomeAnimation* anim = var.value(); + if( anim ) { + anim->setup( *painter, rect ); + } + + // set the default options + QColor color = optionValueForModeAndState("color", mode, state, options).value(); + QString text = optionValueForModeAndState("text", mode, state, options).toString(); + + Q_ASSERT(color.isValid()); + Q_ASSERT(!text.isEmpty()); + + painter->setPen(color); + + // add some 'padding' around the icon + int drawSize = qRound(rect.height()*options.value("scale-factor").toFloat()); + + painter->setFont( awesome->font(drawSize) ); + painter->drawText( rect, text, QTextOption( Qt::AlignCenter|Qt::AlignVCenter ) ); + painter->restore(); + } + +}; + + +//--------------------------------------------------------------------------------------- + + +/// The painter icon engine. +class QtAwesomeIconPainterIconEngine : public QIconEngine +{ + +public: + + QtAwesomeIconPainterIconEngine( QtAwesome* awesome, QtAwesomeIconPainter* painter, const QVariantMap& options ) + : awesomeRef_(awesome) + , iconPainterRef_(painter) + , options_(options) + { + } + + virtual ~QtAwesomeIconPainterIconEngine() {} + + QtAwesomeIconPainterIconEngine* clone() const + { + return new QtAwesomeIconPainterIconEngine( awesomeRef_, iconPainterRef_, options_ ); + } + + virtual void paint(QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state) + { + Q_UNUSED( mode ); + Q_UNUSED( state ); + iconPainterRef_->paint( awesomeRef_, painter, rect, mode, state, options_ ); + } + + virtual QPixmap pixmap(const QSize& size, QIcon::Mode mode, QIcon::State state) + { + QPixmap pm(size); + pm.fill( Qt::transparent ); // we need transparency + { + QPainter p(&pm); + paint(&p, QRect(QPoint(0,0),size), mode, state); + } + return pm; + } + +private: + + QtAwesome* awesomeRef_; ///< a reference to the QtAwesome instance + QtAwesomeIconPainter* iconPainterRef_; ///< a reference to the icon painter + QVariantMap options_; ///< the options for this icon painter +}; + + +//--------------------------------------------------------------------------------------- + +/// The default icon colors +QtAwesome::QtAwesome( QObject* parent ) + : QObject( parent ) + , namedCodepoints_() +{ + // initialize the default options + setDefaultOption( "color", QColor(50,50,50) ); + setDefaultOption( "color-disabled", QColor(70,70,70,60)); + setDefaultOption( "color-active", QColor(10,10,10)); + setDefaultOption( "color-selected", QColor(10,10,10)); + setDefaultOption( "scale-factor", 0.9 ); + + setDefaultOption( "text", QVariant() ); + setDefaultOption( "text-disabled", QVariant() ); + setDefaultOption( "text-active", QVariant() ); + setDefaultOption( "text-selected", QVariant() ); + + fontIconPainter_ = new QtAwesomeCharIconPainter(); + +} + + +QtAwesome::~QtAwesome() +{ + delete fontIconPainter_; +// delete errorIconPainter_; + qDeleteAll(painterMap_); +} + +/// initializes the QtAwesome icon factory with the given fontname +void QtAwesome::init(const QString& fontname) +{ + fontName_ = fontname; +} + +struct FANameIcon { + const char *name; + fa::icon icon; +}; + +static const FANameIcon faNameIconArray[] = { + { "fa_500px" , fa::fa_500px }, + { "addressbook" , fa::addressbook }, + { "addressbooko" , fa::addressbooko }, + { "addresscard" , fa::addresscard }, + { "addresscardo" , fa::addresscardo }, + { "adjust" , fa::adjust }, + { "adn" , fa::adn }, + { "aligncenter" , fa::aligncenter }, + { "alignjustify" , fa::alignjustify }, + { "alignleft" , fa::alignleft }, + { "alignright" , fa::alignright }, + { "amazon" , fa::amazon }, + { "ambulance" , fa::ambulance }, + { "americansignlanguageinterpreting" , fa::americansignlanguageinterpreting }, + { "anchor" , fa::anchor }, + { "android" , fa::android }, + { "angellist" , fa::angellist }, + { "angledoubledown" , fa::angledoubledown }, + { "angledoubleleft" , fa::angledoubleleft }, + { "angledoubleright" , fa::angledoubleright }, + { "angledoubleup" , fa::angledoubleup }, + { "angledown" , fa::angledown }, + { "angleleft" , fa::angleleft }, + { "angleright" , fa::angleright }, + { "angleup" , fa::angleup }, + { "apple" , fa::apple }, + { "archive" , fa::archive }, + { "areachart" , fa::areachart }, + { "arrowcircledown" , fa::arrowcircledown }, + { "arrowcircleleft" , fa::arrowcircleleft }, + { "arrowcircleodown" , fa::arrowcircleodown }, + { "arrowcircleoleft" , fa::arrowcircleoleft }, + { "arrowcircleoright" , fa::arrowcircleoright }, + { "arrowcircleoup" , fa::arrowcircleoup }, + { "arrowcircleright" , fa::arrowcircleright }, + { "arrowcircleup" , fa::arrowcircleup }, + { "arrowdown" , fa::arrowdown }, + { "arrowleft" , fa::arrowleft }, + { "arrowright" , fa::arrowright }, + { "arrowup" , fa::arrowup }, + { "arrows" , fa::arrows }, + { "arrowsalt" , fa::arrowsalt }, + { "arrowsh" , fa::arrowsh }, + { "arrowsv" , fa::arrowsv }, + { "aslinterpreting" , fa::aslinterpreting }, + { "assistivelisteningsystems" , fa::assistivelisteningsystems }, + { "asterisk" , fa::asterisk }, + { "at" , fa::at }, + { "audiodescription" , fa::audiodescription }, + { "automobile" , fa::automobile }, + { "backward" , fa::backward }, + { "balancescale" , fa::balancescale }, + { "ban" , fa::ban }, + { "bandcamp" , fa::bandcamp }, + { "bank" , fa::bank }, + { "barchart" , fa::barchart }, + { "barcharto" , fa::barcharto }, + { "barcode" , fa::barcode }, + { "bars" , fa::bars }, + { "bath" , fa::bath }, + { "bathtub" , fa::bathtub }, + { "battery" , fa::battery }, + { "battery0" , fa::battery0 }, + { "battery1" , fa::battery1 }, + { "battery2" , fa::battery2 }, + { "battery3" , fa::battery3 }, + { "battery4" , fa::battery4 }, + { "batteryempty" , fa::batteryempty }, + { "batteryfull" , fa::batteryfull }, + { "batteryhalf" , fa::batteryhalf }, + { "batteryquarter" , fa::batteryquarter }, + { "batterythreequarters" , fa::batterythreequarters }, + { "bed" , fa::bed }, + { "beer" , fa::beer }, + { "behance" , fa::behance }, + { "behancesquare" , fa::behancesquare }, + { "bell" , fa::bell }, + { "bello" , fa::bello }, + { "bellslash" , fa::bellslash }, + { "bellslasho" , fa::bellslasho }, + { "bicycle" , fa::bicycle }, + { "binoculars" , fa::binoculars }, + { "birthdaycake" , fa::birthdaycake }, + { "bitbucket" , fa::bitbucket }, + { "bitbucketsquare" , fa::bitbucketsquare }, + { "bitcoin" , fa::bitcoin }, + { "blacktie" , fa::blacktie }, + { "blind" , fa::blind }, + { "bluetooth" , fa::bluetooth }, + { "bluetoothb" , fa::bluetoothb }, + { "bold" , fa::bold }, + { "bolt" , fa::bolt }, + { "bomb" , fa::bomb }, + { "book" , fa::book }, + { "bookmark" , fa::bookmark }, + { "bookmarko" , fa::bookmarko }, + { "braille" , fa::braille }, + { "briefcase" , fa::briefcase }, + { "btc" , fa::btc }, + { "bug" , fa::bug }, + { "building" , fa::building }, + { "buildingo" , fa::buildingo }, + { "bullhorn" , fa::bullhorn }, + { "bullseye" , fa::bullseye }, + { "bus" , fa::bus }, + { "buysellads" , fa::buysellads }, + { "cab" , fa::cab }, + { "calculator" , fa::calculator }, + { "calendar" , fa::calendar }, + { "calendarchecko" , fa::calendarchecko }, + { "calendarminuso" , fa::calendarminuso }, + { "calendaro" , fa::calendaro }, + { "calendarpluso" , fa::calendarpluso }, + { "calendartimeso" , fa::calendartimeso }, + { "camera" , fa::camera }, + { "cameraretro" , fa::cameraretro }, + { "car" , fa::car }, + { "caretdown" , fa::caretdown }, + { "caretleft" , fa::caretleft }, + { "caretright" , fa::caretright }, + { "caretsquareodown" , fa::caretsquareodown }, + { "caretsquareoleft" , fa::caretsquareoleft }, + { "caretsquareoright" , fa::caretsquareoright }, + { "caretsquareoup" , fa::caretsquareoup }, + { "caretup" , fa::caretup }, + { "cartarrowdown" , fa::cartarrowdown }, + { "cartplus" , fa::cartplus }, + { "cc" , fa::cc }, + { "ccamex" , fa::ccamex }, + { "ccdinersclub" , fa::ccdinersclub }, + { "ccdiscover" , fa::ccdiscover }, + { "ccjcb" , fa::ccjcb }, + { "ccmastercard" , fa::ccmastercard }, + { "ccpaypal" , fa::ccpaypal }, + { "ccstripe" , fa::ccstripe }, + { "ccvisa" , fa::ccvisa }, + { "certificate" , fa::certificate }, + { "chain" , fa::chain }, + { "chainbroken" , fa::chainbroken }, + { "check" , fa::check }, + { "checkcircle" , fa::checkcircle }, + { "checkcircleo" , fa::checkcircleo }, + { "checksquare" , fa::checksquare }, + { "checksquareo" , fa::checksquareo }, + { "chevroncircledown" , fa::chevroncircledown }, + { "chevroncircleleft" , fa::chevroncircleleft }, + { "chevroncircleright" , fa::chevroncircleright }, + { "chevroncircleup" , fa::chevroncircleup }, + { "chevrondown" , fa::chevrondown }, + { "chevronleft" , fa::chevronleft }, + { "chevronright" , fa::chevronright }, + { "chevronup" , fa::chevronup }, + { "child" , fa::child }, + { "chrome" , fa::chrome }, + { "circle" , fa::circle }, + { "circleo" , fa::circleo }, + { "circleonotch" , fa::circleonotch }, + { "circlethin" , fa::circlethin }, + { "clipboard" , fa::clipboard }, + { "clocko" , fa::clocko }, + { "clone" , fa::clone }, + { "close" , fa::close }, + { "cloud" , fa::cloud }, + { "clouddownload" , fa::clouddownload }, + { "cloudupload" , fa::cloudupload }, + { "cny" , fa::cny }, + { "code" , fa::code }, + { "codefork" , fa::codefork }, + { "codepen" , fa::codepen }, + { "codiepie" , fa::codiepie }, + { "coffee" , fa::coffee }, + { "cog" , fa::cog }, + { "cogs" , fa::cogs }, + { "columns" , fa::columns }, + { "comment" , fa::comment }, + { "commento" , fa::commento }, + { "commenting" , fa::commenting }, + { "commentingo" , fa::commentingo }, + { "comments" , fa::comments }, + { "commentso" , fa::commentso }, + { "compass" , fa::compass }, + { "compress" , fa::compress }, + { "connectdevelop" , fa::connectdevelop }, + { "contao" , fa::contao }, + { "copy" , fa::copy }, + { "copyright" , fa::copyright }, + { "creativecommons" , fa::creativecommons }, + { "creditcard" , fa::creditcard }, + { "creditcardalt" , fa::creditcardalt }, + { "crop" , fa::crop }, + { "crosshairs" , fa::crosshairs }, + { "css3" , fa::css3 }, + { "cube" , fa::cube }, + { "cubes" , fa::cubes }, + { "cut" , fa::cut }, + { "cutlery" , fa::cutlery }, + { "dashboard" , fa::dashboard }, + { "dashcube" , fa::dashcube }, + { "database" , fa::database }, + { "deaf" , fa::deaf }, + { "deafness" , fa::deafness }, + { "dedent" , fa::dedent }, + { "delicious" , fa::delicious }, + { "desktop" , fa::desktop }, + { "deviantart" , fa::deviantart }, + { "diamond" , fa::diamond }, + { "digg" , fa::digg }, + { "dollar" , fa::dollar }, + { "dotcircleo" , fa::dotcircleo }, + { "download" , fa::download }, + { "dribbble" , fa::dribbble }, + { "driverslicense" , fa::driverslicense }, + { "driverslicenseo" , fa::driverslicenseo }, + { "dropbox" , fa::dropbox }, + { "drupal" , fa::drupal }, + { "edge" , fa::edge }, + { "edit" , fa::edit }, + { "eercast" , fa::eercast }, + { "eject" , fa::eject }, + { "ellipsish" , fa::ellipsish }, + { "ellipsisv" , fa::ellipsisv }, + { "empire" , fa::empire }, + { "envelope" , fa::envelope }, + { "envelopeo" , fa::envelopeo }, + { "envelopeopen" , fa::envelopeopen }, + { "envelopeopeno" , fa::envelopeopeno }, + { "envelopesquare" , fa::envelopesquare }, + { "envira" , fa::envira }, + { "eraser" , fa::eraser }, + { "etsy" , fa::etsy }, + { "eur" , fa::eur }, + { "euro" , fa::euro }, + { "exchange" , fa::exchange }, + { "exclamation" , fa::exclamation }, + { "exclamationcircle" , fa::exclamationcircle }, + { "exclamationtriangle" , fa::exclamationtriangle }, + { "expand" , fa::expand }, + { "expeditedssl" , fa::expeditedssl }, + { "externallink" , fa::externallink }, + { "externallinksquare" , fa::externallinksquare }, + { "eye" , fa::eye }, + { "eyeslash" , fa::eyeslash }, + { "eyedropper" , fa::eyedropper }, + { "fa" , fa::fa }, + { "facebook" , fa::facebook }, + { "facebookf" , fa::facebookf }, + { "facebookofficial" , fa::facebookofficial }, + { "facebooksquare" , fa::facebooksquare }, + { "fastbackward" , fa::fastbackward }, + { "fastforward" , fa::fastforward }, + { "fax" , fa::fax }, + { "feed" , fa::feed }, + { "female" , fa::female }, + { "fighterjet" , fa::fighterjet }, + { "file" , fa::file }, + { "filearchiveo" , fa::filearchiveo }, + { "fileaudioo" , fa::fileaudioo }, + { "filecodeo" , fa::filecodeo }, + { "fileexcelo" , fa::fileexcelo }, + { "fileimageo" , fa::fileimageo }, + { "filemovieo" , fa::filemovieo }, + { "fileo" , fa::fileo }, + { "filepdfo" , fa::filepdfo }, + { "filephotoo" , fa::filephotoo }, + { "filepictureo" , fa::filepictureo }, + { "filepowerpointo" , fa::filepowerpointo }, + { "filesoundo" , fa::filesoundo }, + { "filetext" , fa::filetext }, + { "filetexto" , fa::filetexto }, + { "filevideoo" , fa::filevideoo }, + { "filewordo" , fa::filewordo }, + { "filezipo" , fa::filezipo }, + { "fileso" , fa::fileso }, + { "film" , fa::film }, + { "filter" , fa::filter }, + { "fire" , fa::fire }, + { "fireextinguisher" , fa::fireextinguisher }, + { "firefox" , fa::firefox }, + { "firstorder" , fa::firstorder }, + { "flag" , fa::flag }, + { "flagcheckered" , fa::flagcheckered }, + { "flago" , fa::flago }, + { "flash" , fa::flash }, + { "flask" , fa::flask }, + { "flickr" , fa::flickr }, + { "floppyo" , fa::floppyo }, + { "folder" , fa::folder }, + { "foldero" , fa::foldero }, + { "folderopen" , fa::folderopen }, + { "folderopeno" , fa::folderopeno }, + { "font" , fa::font }, + { "fontawesome" , fa::fontawesome }, + { "fonticons" , fa::fonticons }, + { "fortawesome" , fa::fortawesome }, + { "forumbee" , fa::forumbee }, + { "forward" , fa::forward }, + { "foursquare" , fa::foursquare }, + { "freecodecamp" , fa::freecodecamp }, + { "frowno" , fa::frowno }, + { "futbolo" , fa::futbolo }, + { "gamepad" , fa::gamepad }, + { "gavel" , fa::gavel }, + { "gbp" , fa::gbp }, + { "ge" , fa::ge }, + { "gear" , fa::gear }, + { "gears" , fa::gears }, + { "genderless" , fa::genderless }, + { "getpocket" , fa::getpocket }, + { "gg" , fa::gg }, + { "ggcircle" , fa::ggcircle }, + { "gift" , fa::gift }, + { "git" , fa::git }, + { "gitsquare" , fa::gitsquare }, + { "github" , fa::github }, + { "githubalt" , fa::githubalt }, + { "githubsquare" , fa::githubsquare }, + { "gitlab" , fa::gitlab }, + { "gittip" , fa::gittip }, + { "glass" , fa::glass }, + { "glide" , fa::glide }, + { "glideg" , fa::glideg }, + { "globe" , fa::globe }, + { "google" , fa::google }, + { "googleplus" , fa::googleplus }, + { "googlepluscircle" , fa::googlepluscircle }, + { "googleplusofficial" , fa::googleplusofficial }, + { "googleplussquare" , fa::googleplussquare }, + { "googlewallet" , fa::googlewallet }, + { "graduationcap" , fa::graduationcap }, + { "gratipay" , fa::gratipay }, + { "grav" , fa::grav }, + { "group" , fa::group }, + { "hsquare" , fa::hsquare }, + { "hackernews" , fa::hackernews }, + { "handgrabo" , fa::handgrabo }, + { "handlizardo" , fa::handlizardo }, + { "handodown" , fa::handodown }, + { "handoleft" , fa::handoleft }, + { "handoright" , fa::handoright }, + { "handoup" , fa::handoup }, + { "handpapero" , fa::handpapero }, + { "handpeaceo" , fa::handpeaceo }, + { "handpointero" , fa::handpointero }, + { "handrocko" , fa::handrocko }, + { "handscissorso" , fa::handscissorso }, + { "handspocko" , fa::handspocko }, + { "handstopo" , fa::handstopo }, + { "handshakeo" , fa::handshakeo }, + { "hardofhearing" , fa::hardofhearing }, + { "hashtag" , fa::hashtag }, + { "hddo" , fa::hddo }, + { "header" , fa::header }, + { "headphones" , fa::headphones }, + { "heart" , fa::heart }, + { "hearto" , fa::hearto }, + { "heartbeat" , fa::heartbeat }, + { "history" , fa::history }, + { "home" , fa::home }, + { "hospitalo" , fa::hospitalo }, + { "hotel" , fa::hotel }, + { "hourglass" , fa::hourglass }, + { "hourglass1" , fa::hourglass1 }, + { "hourglass2" , fa::hourglass2 }, + { "hourglass3" , fa::hourglass3 }, + { "hourglassend" , fa::hourglassend }, + { "hourglasshalf" , fa::hourglasshalf }, + { "hourglasso" , fa::hourglasso }, + { "hourglassstart" , fa::hourglassstart }, + { "houzz" , fa::houzz }, + { "html5" , fa::html5 }, + { "icursor" , fa::icursor }, + { "idbadge" , fa::idbadge }, + { "idcard" , fa::idcard }, + { "idcardo" , fa::idcardo }, + { "ils" , fa::ils }, + { "image" , fa::image }, + { "imdb" , fa::imdb }, + { "inbox" , fa::inbox }, + { "indent" , fa::indent }, + { "industry" , fa::industry }, + { "info" , fa::info }, + { "infocircle" , fa::infocircle }, + { "inr" , fa::inr }, + { "instagram" , fa::instagram }, + { "institution" , fa::institution }, + { "internetexplorer" , fa::internetexplorer }, + { "intersex" , fa::intersex }, + { "ioxhost" , fa::ioxhost }, + { "italic" , fa::italic }, + { "joomla" , fa::joomla }, + { "jpy" , fa::jpy }, + { "jsfiddle" , fa::jsfiddle }, + { "key" , fa::key }, + { "keyboardo" , fa::keyboardo }, + { "krw" , fa::krw }, + { "language" , fa::language }, + { "laptop" , fa::laptop }, + { "lastfm" , fa::lastfm }, + { "lastfmsquare" , fa::lastfmsquare }, + { "leaf" , fa::leaf }, + { "leanpub" , fa::leanpub }, + { "legal" , fa::legal }, + { "lemono" , fa::lemono }, + { "leveldown" , fa::leveldown }, + { "levelup" , fa::levelup }, + { "lifebouy" , fa::lifebouy }, + { "lifebuoy" , fa::lifebuoy }, + { "lifering" , fa::lifering }, + { "lifesaver" , fa::lifesaver }, + { "lightbulbo" , fa::lightbulbo }, + { "linechart" , fa::linechart }, + { "link" , fa::link }, + { "linkedin" , fa::linkedin }, + { "linkedinsquare" , fa::linkedinsquare }, + { "linode" , fa::linode }, + { "fa_linux" , fa::fa_linux }, + { "list" , fa::list }, + { "listalt" , fa::listalt }, + { "listol" , fa::listol }, + { "listul" , fa::listul }, + { "locationarrow" , fa::locationarrow }, + { "lock" , fa::lock }, + { "longarrowdown" , fa::longarrowdown }, + { "longarrowleft" , fa::longarrowleft }, + { "longarrowright" , fa::longarrowright }, + { "longarrowup" , fa::longarrowup }, + { "lowvision" , fa::lowvision }, + { "magic" , fa::magic }, + { "magnet" , fa::magnet }, + { "mailforward" , fa::mailforward }, + { "mailreply" , fa::mailreply }, + { "mailreplyall" , fa::mailreplyall }, + { "male" , fa::male }, + { "map" , fa::map }, + { "mapmarker" , fa::mapmarker }, + { "mapo" , fa::mapo }, + { "mappin" , fa::mappin }, + { "mapsigns" , fa::mapsigns }, + { "mars" , fa::mars }, + { "marsdouble" , fa::marsdouble }, + { "marsstroke" , fa::marsstroke }, + { "marsstrokeh" , fa::marsstrokeh }, + { "marsstrokev" , fa::marsstrokev }, + { "maxcdn" , fa::maxcdn }, + { "meanpath" , fa::meanpath }, + { "medium" , fa::medium }, + { "medkit" , fa::medkit }, + { "meetup" , fa::meetup }, + { "meho" , fa::meho }, + { "mercury" , fa::mercury }, + { "microchip" , fa::microchip }, + { "microphone" , fa::microphone }, + { "microphoneslash" , fa::microphoneslash }, + { "minus" , fa::minus }, + { "minuscircle" , fa::minuscircle }, + { "minussquare" , fa::minussquare }, + { "minussquareo" , fa::minussquareo }, + { "mixcloud" , fa::mixcloud }, + { "mobile" , fa::mobile }, + { "mobilephone" , fa::mobilephone }, + { "modx" , fa::modx }, + { "money" , fa::money }, + { "moono" , fa::moono }, + { "mortarboard" , fa::mortarboard }, + { "motorcycle" , fa::motorcycle }, + { "mousepointer" , fa::mousepointer }, + { "music" , fa::music }, + { "navicon" , fa::navicon }, + { "neuter" , fa::neuter }, + { "newspapero" , fa::newspapero }, + { "objectgroup" , fa::objectgroup }, + { "objectungroup" , fa::objectungroup }, + { "odnoklassniki" , fa::odnoklassniki }, + { "odnoklassnikisquare" , fa::odnoklassnikisquare }, + { "opencart" , fa::opencart }, + { "openid" , fa::openid }, + { "opera" , fa::opera }, + { "optinmonster" , fa::optinmonster }, + { "outdent" , fa::outdent }, + { "pagelines" , fa::pagelines }, + { "paintbrush" , fa::paintbrush }, + { "paperplane" , fa::paperplane }, + { "paperplaneo" , fa::paperplaneo }, + { "paperclip" , fa::paperclip }, + { "paragraph" , fa::paragraph }, + { "paste" , fa::paste }, + { "pause" , fa::pause }, + { "pausecircle" , fa::pausecircle }, + { "pausecircleo" , fa::pausecircleo }, + { "paw" , fa::paw }, + { "paypal" , fa::paypal }, + { "pencil" , fa::pencil }, + { "pencilsquare" , fa::pencilsquare }, + { "pencilsquareo" , fa::pencilsquareo }, + { "percent" , fa::percent }, + { "phone" , fa::phone }, + { "phonesquare" , fa::phonesquare }, + { "photo" , fa::photo }, + { "pictureo" , fa::pictureo }, + { "piechart" , fa::piechart }, + { "piedpiper" , fa::piedpiper }, + { "piedpiperalt" , fa::piedpiperalt }, + { "piedpiperpp" , fa::piedpiperpp }, + { "pinterest" , fa::pinterest }, + { "pinterestp" , fa::pinterestp }, + { "pinterestsquare" , fa::pinterestsquare }, + { "plane" , fa::plane }, + { "play" , fa::play }, + { "playcircle" , fa::playcircle }, + { "playcircleo" , fa::playcircleo }, + { "plug" , fa::plug }, + { "plus" , fa::plus }, + { "pluscircle" , fa::pluscircle }, + { "plussquare" , fa::plussquare }, + { "plussquareo" , fa::plussquareo }, + { "podcast" , fa::podcast }, + { "poweroff" , fa::poweroff }, + { "print" , fa::print }, + { "producthunt" , fa::producthunt }, + { "puzzlepiece" , fa::puzzlepiece }, + { "qq" , fa::qq }, + { "qrcode" , fa::qrcode }, + { "question" , fa::question }, + { "questioncircle" , fa::questioncircle }, + { "questioncircleo" , fa::questioncircleo }, + { "quora" , fa::quora }, + { "quoteleft" , fa::quoteleft }, + { "quoteright" , fa::quoteright }, + { "ra" , fa::ra }, + { "random" , fa::random }, + { "ravelry" , fa::ravelry }, + { "rebel" , fa::rebel }, + { "recycle" , fa::recycle }, + { "reddit" , fa::reddit }, + { "redditalien" , fa::redditalien }, + { "redditsquare" , fa::redditsquare }, + { "refresh" , fa::refresh }, + { "registered" , fa::registered }, + { "remove" , fa::remove }, + { "renren" , fa::renren }, + { "reorder" , fa::reorder }, + { "repeat" , fa::repeat }, + { "reply" , fa::reply }, + { "replyall" , fa::replyall }, + { "resistance" , fa::resistance }, + { "retweet" , fa::retweet }, + { "rmb" , fa::rmb }, + { "road" , fa::road }, + { "rocket" , fa::rocket }, + { "rotateleft" , fa::rotateleft }, + { "rotateright" , fa::rotateright }, + { "rouble" , fa::rouble }, + { "rss" , fa::rss }, + { "rsssquare" , fa::rsssquare }, + { "rub" , fa::rub }, + { "ruble" , fa::ruble }, + { "rupee" , fa::rupee }, + { "s15" , fa::s15 }, + { "safari" , fa::safari }, + { "save" , fa::save }, + { "scissors" , fa::scissors }, + { "scribd" , fa::scribd }, + { "search" , fa::search }, + { "searchminus" , fa::searchminus }, + { "searchplus" , fa::searchplus }, + { "sellsy" , fa::sellsy }, + { "send" , fa::send }, + { "sendo" , fa::sendo }, + { "server" , fa::server }, + { "share" , fa::share }, + { "sharealt" , fa::sharealt }, + { "sharealtsquare" , fa::sharealtsquare }, + { "sharesquare" , fa::sharesquare }, + { "sharesquareo" , fa::sharesquareo }, + { "shekel" , fa::shekel }, + { "sheqel" , fa::sheqel }, + { "shield" , fa::shield }, + { "ship" , fa::ship }, + { "shirtsinbulk" , fa::shirtsinbulk }, + { "shoppingbag" , fa::shoppingbag }, + { "shoppingbasket" , fa::shoppingbasket }, + { "shoppingcart" , fa::shoppingcart }, + { "shower" , fa::shower }, + { "signin" , fa::signin }, + { "signlanguage" , fa::signlanguage }, + { "signout" , fa::signout }, + { "signal" , fa::signal }, + { "signing" , fa::signing }, + { "simplybuilt" , fa::simplybuilt }, + { "sitemap" , fa::sitemap }, + { "skyatlas" , fa::skyatlas }, + { "skype" , fa::skype }, + { "slack" , fa::slack }, + { "sliders" , fa::sliders }, + { "slideshare" , fa::slideshare }, + { "smileo" , fa::smileo }, + { "snapchat" , fa::snapchat }, + { "snapchatghost" , fa::snapchatghost }, + { "snapchatsquare" , fa::snapchatsquare }, + { "snowflakeo" , fa::snowflakeo }, + { "soccerballo" , fa::soccerballo }, + { "sort" , fa::sort }, + { "sortalphaasc" , fa::sortalphaasc }, + { "sortalphadesc" , fa::sortalphadesc }, + { "sortamountasc" , fa::sortamountasc }, + { "sortamountdesc" , fa::sortamountdesc }, + { "sortasc" , fa::sortasc }, + { "sortdesc" , fa::sortdesc }, + { "sortdown" , fa::sortdown }, + { "sortnumericasc" , fa::sortnumericasc }, + { "sortnumericdesc" , fa::sortnumericdesc }, + { "sortup" , fa::sortup }, + { "soundcloud" , fa::soundcloud }, + { "spaceshuttle" , fa::spaceshuttle }, + { "spinner" , fa::spinner }, + { "spoon" , fa::spoon }, + { "spotify" , fa::spotify }, + { "square" , fa::square }, + { "squareo" , fa::squareo }, + { "stackexchange" , fa::stackexchange }, + { "stackoverflow" , fa::stackoverflow }, + { "star" , fa::star }, + { "starhalf" , fa::starhalf }, + { "starhalfempty" , fa::starhalfempty }, + { "starhalffull" , fa::starhalffull }, + { "starhalfo" , fa::starhalfo }, + { "staro" , fa::staro }, + { "steam" , fa::steam }, + { "steamsquare" , fa::steamsquare }, + { "stepbackward" , fa::stepbackward }, + { "stepforward" , fa::stepforward }, + { "stethoscope" , fa::stethoscope }, + { "stickynote" , fa::stickynote }, + { "stickynoteo" , fa::stickynoteo }, + { "stop" , fa::stop }, + { "stopcircle" , fa::stopcircle }, + { "stopcircleo" , fa::stopcircleo }, + { "streetview" , fa::streetview }, + { "strikethrough" , fa::strikethrough }, + { "stumbleupon" , fa::stumbleupon }, + { "stumbleuponcircle" , fa::stumbleuponcircle }, + { "subscript" , fa::subscript }, + { "subway" , fa::subway }, + { "suitcase" , fa::suitcase }, + { "suno" , fa::suno }, + { "superpowers" , fa::superpowers }, + { "superscript" , fa::superscript }, + { "support" , fa::support }, + { "table" , fa::table }, + { "tablet" , fa::tablet }, + { "tachometer" , fa::tachometer }, + { "tag" , fa::tag }, + { "tags" , fa::tags }, + { "tasks" , fa::tasks }, + { "taxi" , fa::taxi }, + { "telegram" , fa::telegram }, + { "television" , fa::television }, + { "tencentweibo" , fa::tencentweibo }, + { "terminal" , fa::terminal }, + { "textheight" , fa::textheight }, + { "textwidth" , fa::textwidth }, + { "th" , fa::th }, + { "thlarge" , fa::thlarge }, + { "thlist" , fa::thlist }, + { "themeisle" , fa::themeisle }, + { "thermometer" , fa::thermometer }, + { "thermometer0" , fa::thermometer0 }, + { "thermometer1" , fa::thermometer1 }, + { "thermometer2" , fa::thermometer2 }, + { "thermometer3" , fa::thermometer3 }, + { "thermometer4" , fa::thermometer4 }, + { "thermometerempty" , fa::thermometerempty }, + { "thermometerfull" , fa::thermometerfull }, + { "thermometerhalf" , fa::thermometerhalf }, + { "thermometerquarter" , fa::thermometerquarter }, + { "thermometerthreequarters" , fa::thermometerthreequarters }, + { "thumbtack" , fa::thumbtack }, + { "thumbsdown" , fa::thumbsdown }, + { "thumbsodown" , fa::thumbsodown }, + { "thumbsoup" , fa::thumbsoup }, + { "thumbsup" , fa::thumbsup }, + { "ticket" , fa::ticket }, + { "times" , fa::times }, + { "timescircle" , fa::timescircle }, + { "timescircleo" , fa::timescircleo }, + { "timesrectangle" , fa::timesrectangle }, + { "timesrectangleo" , fa::timesrectangleo }, + { "tint" , fa::tint }, + { "toggledown" , fa::toggledown }, + { "toggleleft" , fa::toggleleft }, + { "toggleoff" , fa::toggleoff }, + { "toggleon" , fa::toggleon }, + { "toggleright" , fa::toggleright }, + { "toggleup" , fa::toggleup }, + { "trademark" , fa::trademark }, + { "train" , fa::train }, + { "transgender" , fa::transgender }, + { "transgenderalt" , fa::transgenderalt }, + { "trash" , fa::trash }, + { "trasho" , fa::trasho }, + { "tree" , fa::tree }, + { "trello" , fa::trello }, + { "tripadvisor" , fa::tripadvisor }, + { "trophy" , fa::trophy }, + { "truck" , fa::truck }, + { "fa_try" , fa::fa_try }, + { "tty" , fa::tty }, + { "tumblr" , fa::tumblr }, + { "tumblrsquare" , fa::tumblrsquare }, + { "turkishlira" , fa::turkishlira }, + { "tv" , fa::tv }, + { "twitch" , fa::twitch }, + { "twitter" , fa::twitter }, + { "twittersquare" , fa::twittersquare }, + { "umbrella" , fa::umbrella }, + { "underline" , fa::underline }, + { "undo" , fa::undo }, + { "universalaccess" , fa::universalaccess }, + { "university" , fa::university }, + { "unlink" , fa::unlink }, + { "unlock" , fa::unlock }, + { "unlockalt" , fa::unlockalt }, + { "unsorted" , fa::unsorted }, + { "upload" , fa::upload }, + { "usb" , fa::usb }, + { "usd" , fa::usd }, + { "user" , fa::user }, + { "usercircle" , fa::usercircle }, + { "usercircleo" , fa::usercircleo }, + { "usermd" , fa::usermd }, + { "usero" , fa::usero }, + { "userplus" , fa::userplus }, + { "usersecret" , fa::usersecret }, + { "usertimes" , fa::usertimes }, + { "users" , fa::users }, + { "vcard" , fa::vcard }, + { "vcardo" , fa::vcardo }, + { "venus" , fa::venus }, + { "venusdouble" , fa::venusdouble }, + { "venusmars" , fa::venusmars }, + { "viacoin" , fa::viacoin }, + { "viadeo" , fa::viadeo }, + { "viadeosquare" , fa::viadeosquare }, + { "videocamera" , fa::videocamera }, + { "vimeo" , fa::vimeo }, + { "vimeosquare" , fa::vimeosquare }, + { "vine" , fa::vine }, + { "vk" , fa::vk }, + { "volumecontrolphone" , fa::volumecontrolphone }, + { "volumedown" , fa::volumedown }, + { "volumeoff" , fa::volumeoff }, + { "volumeup" , fa::volumeup }, + { "warning" , fa::warning }, + { "wechat" , fa::wechat }, + { "weibo" , fa::weibo }, + { "weixin" , fa::weixin }, + { "whatsapp" , fa::whatsapp }, + { "wheelchair" , fa::wheelchair }, + { "wheelchairalt" , fa::wheelchairalt }, + { "wifi" , fa::wifi }, + { "wikipediaw" , fa::wikipediaw }, + { "windowclose" , fa::windowclose }, + { "windowcloseo" , fa::windowcloseo }, + { "windowmaximize" , fa::windowmaximize }, + { "windowminimize" , fa::windowminimize }, + { "windowrestore" , fa::windowrestore }, + { "windows" , fa::windows }, + { "won" , fa::won }, + { "wordpress" , fa::wordpress }, + { "wpbeginner" , fa::wpbeginner }, + { "wpexplorer" , fa::wpexplorer }, + { "wpforms" , fa::wpforms }, + { "wrench" , fa::wrench }, + { "xing" , fa::xing }, + { "xingsquare" , fa::xingsquare }, + { "ycombinator" , fa::ycombinator }, + { "ycombinatorsquare" , fa::ycombinatorsquare }, + { "yahoo" , fa::yahoo }, + { "yc" , fa::yc }, + { "ycsquare" , fa::ycsquare }, + { "yelp" , fa::yelp }, + { "yen" , fa::yen }, + { "yoast" , fa::yoast }, + { "youtube" , fa::youtube }, + { "youtubeplay" , fa::youtubeplay }, + { "youtubesquare" , fa::youtubesquare } +}; + + +/// a specialized init function so font-awesome is loaded and initialized +/// this method return true on success, it will return false if the fnot cannot be initialized +/// To initialize QtAwesome with font-awesome you need to call this method +bool QtAwesome::initFontAwesome( ) +{ + static int fontAwesomeFontId = -1; + + // only load font-awesome once + if( fontAwesomeFontId < 0 ) { + + // The macro below internally calls "qInitResources_QtAwesome()". this initializes + // the resource system. For a .pri project this isn't required, but when building and using a + // static library the resource need to initialized first. + /// + // I've checked th qInitResource_* code and calling this method mutliple times shouldn't be any problem + // (More info about this subject: http://qt-project.org/wiki/QtResources) + Q_INIT_RESOURCE(QtAwesome); + + // load the font file + QFile res(":/fonts/fontawesome-4.7.0.ttf"); + if(!res.open(QIODevice::ReadOnly)) { + qDebug() << "Font awesome font could not be loaded!"; + return false; + } + QByteArray fontData( res.readAll() ); + res.close(); + + // fetch the given font + fontAwesomeFontId = QFontDatabase::addApplicationFontFromData(fontData); + } + + QStringList loadedFontFamilies = QFontDatabase::applicationFontFamilies(fontAwesomeFontId); + if( !loadedFontFamilies.empty() ) { + fontName_= loadedFontFamilies.at(0); + } else { + qDebug() << "Font awesome font is empty?!"; + fontAwesomeFontId = -1; // restore the font-awesome id + return false; + } + + // intialize the map + QHash& m = namedCodepoints_; + for (unsigned i = 0; i < sizeof(faNameIconArray)/sizeof(FANameIcon); ++i) { + m.insert(faNameIconArray[i].name, faNameIconArray[i].icon); + } + + return true; +} + +void QtAwesome::addNamedCodepoint( const QString& name, int codePoint) +{ + namedCodepoints_.insert( name, codePoint); +} + + +/// Sets a default option. These options are passed on to the icon painters +void QtAwesome::setDefaultOption(const QString& name, const QVariant& value) +{ + defaultOptions_.insert( name, value ); +} + + +/// Returns the default option for the given name +QVariant QtAwesome::defaultOption(const QString& name) +{ + return defaultOptions_.value( name ); +} + + +// internal helper method to merge to option amps +static QVariantMap mergeOptions( const QVariantMap& defaults, const QVariantMap& override ) +{ + QVariantMap result= defaults; + if( !override.isEmpty() ) { + QMapIterator itr(override); + while( itr.hasNext() ) { + itr.next(); + result.insert( itr.key(), itr.value() ); + } + } + return result; +} + + +/// Creates an icon with the given code-point +/// +/// awesome->icon( icon_group ) +/// +QIcon QtAwesome::icon(int character, const QVariantMap &options) +{ + // create a merged QVariantMap to have default options and icon-specific options + QVariantMap optionMap = mergeOptions( defaultOptions_, options ); + optionMap.insert("text", QString( QChar(static_cast(character)) ) ); + + return icon( fontIconPainter_, optionMap ); +} + + + +/// Creates an icon with the given name +/// +/// You can use the icon names as defined on http://fortawesome.github.io/Font-Awesome/design.html withour the 'icon-' prefix +/// @param name the name of the icon +/// @param options extra option to pass to the icon renderer +QIcon QtAwesome::icon(const QString& name, const QVariantMap& options) +{ + // when it's a named codepoint + if( namedCodepoints_.count(name) ) { + return icon( namedCodepoints_.value(name), options ); + } + + + // create a merged QVariantMap to have default options and icon-specific options + QVariantMap optionMap = mergeOptions( defaultOptions_, options ); + + // this method first tries to retrieve the icon + QtAwesomeIconPainter* painter = painterMap_.value(name); + if( !painter ) { + return QIcon(); + } + + return icon( painter, optionMap ); +} + +/// Create a dynamic icon by simlpy supplying a painter object +/// The ownership of the painter is NOT transfered. +/// @param painter a dynamic painter that is going to paint the icon +/// @param optionmap the options to pass to the painter +QIcon QtAwesome::icon(QtAwesomeIconPainter* painter, const QVariantMap& optionMap ) +{ + // Warning, when you use memoryleak detection. You should turn it of for the next call + // QIcon's placed in gui items are often cached and not deleted when my memory-leak detection checks for leaks. + // I'm not sure if it's a Qt bug or something I do wrong + QtAwesomeIconPainterIconEngine* engine = new QtAwesomeIconPainterIconEngine( this, painter, optionMap ); + return QIcon( engine ); +} + +/// Adds a named icon-painter to the QtAwesome icon map +/// As the name applies the ownership is passed over to QtAwesome +/// +/// @param name the name of the icon +/// @param painter the icon painter to add for this name +void QtAwesome::give(const QString& name, QtAwesomeIconPainter* painter) +{ + delete painterMap_.value( name ); // delete the old one + painterMap_.insert( name, painter ); +} + +/// Creates/Gets the icon font with a given size in pixels. This can be usefull to use a label for displaying icons +/// Example: +/// +/// QLabel* label = new QLabel( QChar( icon_group ) ); +/// label->setFont( awesome->font(16) ) +QFont QtAwesome::font( int size ) +{ + QFont font( fontName_); + font.setPixelSize(size); + return font; +} diff --git a/thirdparty/QtAwesome/QtAwesome/QtAwesome.h b/thirdparty/QtAwesome/QtAwesome/QtAwesome.h new file mode 100755 index 00000000..5aabd1bf --- /dev/null +++ b/thirdparty/QtAwesome/QtAwesome/QtAwesome.h @@ -0,0 +1,876 @@ +/** + * QtAwesome - use font-awesome (or other font icons) in your c++ / Qt Application + * + * MIT Licensed + * + * Copyright 2013-2015 - Reliable Bits Software by Blommers IT. All Rights Reserved. + * Author Rick Blommers + */ + +#ifndef QTAWESOME_H +#define QTAWESOME_H + +#include "QtAwesomeAnim.h" + +#include +#include +#include +#include +#include + + +/// A list of all icon-names with the codepoint (unicode-value) on the right +/// You can use the names on the page http://fortawesome.github.io/Font-Awesome/design.html +namespace fa { + enum icon { + fa_500px = 0xf26e, + addressbook = 0xf2b9, + addressbooko = 0xf2ba, + addresscard = 0xf2bb, + addresscardo = 0xf2bc, + adjust = 0xf042, + adn = 0xf170, + aligncenter = 0xf037, + alignjustify = 0xf039, + alignleft = 0xf036, + alignright = 0xf038, + amazon = 0xf270, + ambulance = 0xf0f9, + americansignlanguageinterpreting = 0xf2a3, + anchor = 0xf13d, + android = 0xf17b, + angellist = 0xf209, + angledoubledown = 0xf103, + angledoubleleft = 0xf100, + angledoubleright = 0xf101, + angledoubleup = 0xf102, + angledown = 0xf107, + angleleft = 0xf104, + angleright = 0xf105, + angleup = 0xf106, + apple = 0xf179, + archive = 0xf187, + areachart = 0xf1fe, + arrowcircledown = 0xf0ab, + arrowcircleleft = 0xf0a8, + arrowcircleodown = 0xf01a, + arrowcircleoleft = 0xf190, + arrowcircleoright = 0xf18e, + arrowcircleoup = 0xf01b, + arrowcircleright = 0xf0a9, + arrowcircleup = 0xf0aa, + arrowdown = 0xf063, + arrowleft = 0xf060, + arrowright = 0xf061, + arrowup = 0xf062, + arrows = 0xf047, + arrowsalt = 0xf0b2, + arrowsh = 0xf07e, + arrowsv = 0xf07d, + aslinterpreting = 0xf2a3, + assistivelisteningsystems = 0xf2a2, + asterisk = 0xf069, + at = 0xf1fa, + audiodescription = 0xf29e, + automobile = 0xf1b9, + backward = 0xf04a, + balancescale = 0xf24e, + ban = 0xf05e, + bandcamp = 0xf2d5, + bank = 0xf19c, + barchart = 0xf080, + barcharto = 0xf080, + barcode = 0xf02a, + bars = 0xf0c9, + bath = 0xf2cd, + bathtub = 0xf2cd, + battery = 0xf240, + battery0 = 0xf244, + battery1 = 0xf243, + battery2 = 0xf242, + battery3 = 0xf241, + battery4 = 0xf240, + batteryempty = 0xf244, + batteryfull = 0xf240, + batteryhalf = 0xf242, + batteryquarter = 0xf243, + batterythreequarters = 0xf241, + bed = 0xf236, + beer = 0xf0fc, + behance = 0xf1b4, + behancesquare = 0xf1b5, + bell = 0xf0f3, + bello = 0xf0a2, + bellslash = 0xf1f6, + bellslasho = 0xf1f7, + bicycle = 0xf206, + binoculars = 0xf1e5, + birthdaycake = 0xf1fd, + bitbucket = 0xf171, + bitbucketsquare = 0xf172, + bitcoin = 0xf15a, + blacktie = 0xf27e, + blind = 0xf29d, + bluetooth = 0xf293, + bluetoothb = 0xf294, + bold = 0xf032, + bolt = 0xf0e7, + bomb = 0xf1e2, + book = 0xf02d, + bookmark = 0xf02e, + bookmarko = 0xf097, + braille = 0xf2a1, + briefcase = 0xf0b1, + btc = 0xf15a, + bug = 0xf188, + building = 0xf1ad, + buildingo = 0xf0f7, + bullhorn = 0xf0a1, + bullseye = 0xf140, + bus = 0xf207, + buysellads = 0xf20d, + cab = 0xf1ba, + calculator = 0xf1ec, + calendar = 0xf073, + calendarchecko = 0xf274, + calendarminuso = 0xf272, + calendaro = 0xf133, + calendarpluso = 0xf271, + calendartimeso = 0xf273, + camera = 0xf030, + cameraretro = 0xf083, + car = 0xf1b9, + caretdown = 0xf0d7, + caretleft = 0xf0d9, + caretright = 0xf0da, + caretsquareodown = 0xf150, + caretsquareoleft = 0xf191, + caretsquareoright = 0xf152, + caretsquareoup = 0xf151, + caretup = 0xf0d8, + cartarrowdown = 0xf218, + cartplus = 0xf217, + cc = 0xf20a, + ccamex = 0xf1f3, + ccdinersclub = 0xf24c, + ccdiscover = 0xf1f2, + ccjcb = 0xf24b, + ccmastercard = 0xf1f1, + ccpaypal = 0xf1f4, + ccstripe = 0xf1f5, + ccvisa = 0xf1f0, + certificate = 0xf0a3, + chain = 0xf0c1, + chainbroken = 0xf127, + check = 0xf00c, + checkcircle = 0xf058, + checkcircleo = 0xf05d, + checksquare = 0xf14a, + checksquareo = 0xf046, + chevroncircledown = 0xf13a, + chevroncircleleft = 0xf137, + chevroncircleright = 0xf138, + chevroncircleup = 0xf139, + chevrondown = 0xf078, + chevronleft = 0xf053, + chevronright = 0xf054, + chevronup = 0xf077, + child = 0xf1ae, + chrome = 0xf268, + circle = 0xf111, + circleo = 0xf10c, + circleonotch = 0xf1ce, + circlethin = 0xf1db, + clipboard = 0xf0ea, + clocko = 0xf017, + clone = 0xf24d, + close = 0xf00d, + cloud = 0xf0c2, + clouddownload = 0xf0ed, + cloudupload = 0xf0ee, + cny = 0xf157, + code = 0xf121, + codefork = 0xf126, + codepen = 0xf1cb, + codiepie = 0xf284, + coffee = 0xf0f4, + cog = 0xf013, + cogs = 0xf085, + columns = 0xf0db, + comment = 0xf075, + commento = 0xf0e5, + commenting = 0xf27a, + commentingo = 0xf27b, + comments = 0xf086, + commentso = 0xf0e6, + compass = 0xf14e, + compress = 0xf066, + connectdevelop = 0xf20e, + contao = 0xf26d, + copy = 0xf0c5, + copyright = 0xf1f9, + creativecommons = 0xf25e, + creditcard = 0xf09d, + creditcardalt = 0xf283, + crop = 0xf125, + crosshairs = 0xf05b, + css3 = 0xf13c, + cube = 0xf1b2, + cubes = 0xf1b3, + cut = 0xf0c4, + cutlery = 0xf0f5, + dashboard = 0xf0e4, + dashcube = 0xf210, + database = 0xf1c0, + deaf = 0xf2a4, + deafness = 0xf2a4, + dedent = 0xf03b, + delicious = 0xf1a5, + desktop = 0xf108, + deviantart = 0xf1bd, + diamond = 0xf219, + digg = 0xf1a6, + dollar = 0xf155, + dotcircleo = 0xf192, + download = 0xf019, + dribbble = 0xf17d, + driverslicense = 0xf2c2, + driverslicenseo = 0xf2c3, + dropbox = 0xf16b, + drupal = 0xf1a9, + edge = 0xf282, + edit = 0xf044, + eercast = 0xf2da, + eject = 0xf052, + ellipsish = 0xf141, + ellipsisv = 0xf142, + empire = 0xf1d1, + envelope = 0xf0e0, + envelopeo = 0xf003, + envelopeopen = 0xf2b6, + envelopeopeno = 0xf2b7, + envelopesquare = 0xf199, + envira = 0xf299, + eraser = 0xf12d, + etsy = 0xf2d7, + eur = 0xf153, + euro = 0xf153, + exchange = 0xf0ec, + exclamation = 0xf12a, + exclamationcircle = 0xf06a, + exclamationtriangle = 0xf071, + expand = 0xf065, + expeditedssl = 0xf23e, + externallink = 0xf08e, + externallinksquare = 0xf14c, + eye = 0xf06e, + eyeslash = 0xf070, + eyedropper = 0xf1fb, + fa = 0xf2b4, + facebook = 0xf09a, + facebookf = 0xf09a, + facebookofficial = 0xf230, + facebooksquare = 0xf082, + fastbackward = 0xf049, + fastforward = 0xf050, + fax = 0xf1ac, + feed = 0xf09e, + female = 0xf182, + fighterjet = 0xf0fb, + file = 0xf15b, + filearchiveo = 0xf1c6, + fileaudioo = 0xf1c7, + filecodeo = 0xf1c9, + fileexcelo = 0xf1c3, + fileimageo = 0xf1c5, + filemovieo = 0xf1c8, + fileo = 0xf016, + filepdfo = 0xf1c1, + filephotoo = 0xf1c5, + filepictureo = 0xf1c5, + filepowerpointo = 0xf1c4, + filesoundo = 0xf1c7, + filetext = 0xf15c, + filetexto = 0xf0f6, + filevideoo = 0xf1c8, + filewordo = 0xf1c2, + filezipo = 0xf1c6, + fileso = 0xf0c5, + film = 0xf008, + filter = 0xf0b0, + fire = 0xf06d, + fireextinguisher = 0xf134, + firefox = 0xf269, + firstorder = 0xf2b0, + flag = 0xf024, + flagcheckered = 0xf11e, + flago = 0xf11d, + flash = 0xf0e7, + flask = 0xf0c3, + flickr = 0xf16e, + floppyo = 0xf0c7, + folder = 0xf07b, + foldero = 0xf114, + folderopen = 0xf07c, + folderopeno = 0xf115, + font = 0xf031, + fontawesome = 0xf2b4, + fonticons = 0xf280, + fortawesome = 0xf286, + forumbee = 0xf211, + forward = 0xf04e, + foursquare = 0xf180, + freecodecamp = 0xf2c5, + frowno = 0xf119, + futbolo = 0xf1e3, + gamepad = 0xf11b, + gavel = 0xf0e3, + gbp = 0xf154, + ge = 0xf1d1, + gear = 0xf013, + gears = 0xf085, + genderless = 0xf22d, + getpocket = 0xf265, + gg = 0xf260, + ggcircle = 0xf261, + gift = 0xf06b, + git = 0xf1d3, + gitsquare = 0xf1d2, + github = 0xf09b, + githubalt = 0xf113, + githubsquare = 0xf092, + gitlab = 0xf296, + gittip = 0xf184, + glass = 0xf000, + glide = 0xf2a5, + glideg = 0xf2a6, + globe = 0xf0ac, + google = 0xf1a0, + googleplus = 0xf0d5, + googlepluscircle = 0xf2b3, + googleplusofficial = 0xf2b3, + googleplussquare = 0xf0d4, + googlewallet = 0xf1ee, + graduationcap = 0xf19d, + gratipay = 0xf184, + grav = 0xf2d6, + group = 0xf0c0, + hsquare = 0xf0fd, + hackernews = 0xf1d4, + handgrabo = 0xf255, + handlizardo = 0xf258, + handodown = 0xf0a7, + handoleft = 0xf0a5, + handoright = 0xf0a4, + handoup = 0xf0a6, + handpapero = 0xf256, + handpeaceo = 0xf25b, + handpointero = 0xf25a, + handrocko = 0xf255, + handscissorso = 0xf257, + handspocko = 0xf259, + handstopo = 0xf256, + handshakeo = 0xf2b5, + hardofhearing = 0xf2a4, + hashtag = 0xf292, + hddo = 0xf0a0, + header = 0xf1dc, + headphones = 0xf025, + heart = 0xf004, + hearto = 0xf08a, + heartbeat = 0xf21e, + history = 0xf1da, + home = 0xf015, + hospitalo = 0xf0f8, + hotel = 0xf236, + hourglass = 0xf254, + hourglass1 = 0xf251, + hourglass2 = 0xf252, + hourglass3 = 0xf253, + hourglassend = 0xf253, + hourglasshalf = 0xf252, + hourglasso = 0xf250, + hourglassstart = 0xf251, + houzz = 0xf27c, + html5 = 0xf13b, + icursor = 0xf246, + idbadge = 0xf2c1, + idcard = 0xf2c2, + idcardo = 0xf2c3, + ils = 0xf20b, + image = 0xf03e, + imdb = 0xf2d8, + inbox = 0xf01c, + indent = 0xf03c, + industry = 0xf275, + info = 0xf129, + infocircle = 0xf05a, + inr = 0xf156, + instagram = 0xf16d, + institution = 0xf19c, + internetexplorer = 0xf26b, + intersex = 0xf224, + ioxhost = 0xf208, + italic = 0xf033, + joomla = 0xf1aa, + jpy = 0xf157, + jsfiddle = 0xf1cc, + key = 0xf084, + keyboardo = 0xf11c, + krw = 0xf159, + language = 0xf1ab, + laptop = 0xf109, + lastfm = 0xf202, + lastfmsquare = 0xf203, + leaf = 0xf06c, + leanpub = 0xf212, + legal = 0xf0e3, + lemono = 0xf094, + leveldown = 0xf149, + levelup = 0xf148, + lifebouy = 0xf1cd, + lifebuoy = 0xf1cd, + lifering = 0xf1cd, + lifesaver = 0xf1cd, + lightbulbo = 0xf0eb, + linechart = 0xf201, + link = 0xf0c1, + linkedin = 0xf0e1, + linkedinsquare = 0xf08c, + linode = 0xf2b8, + fa_linux = 0xf17c, + list = 0xf03a, + listalt = 0xf022, + listol = 0xf0cb, + listul = 0xf0ca, + locationarrow = 0xf124, + lock = 0xf023, + longarrowdown = 0xf175, + longarrowleft = 0xf177, + longarrowright = 0xf178, + longarrowup = 0xf176, + lowvision = 0xf2a8, + magic = 0xf0d0, + magnet = 0xf076, + mailforward = 0xf064, + mailreply = 0xf112, + mailreplyall = 0xf122, + male = 0xf183, + map = 0xf279, + mapmarker = 0xf041, + mapo = 0xf278, + mappin = 0xf276, + mapsigns = 0xf277, + mars = 0xf222, + marsdouble = 0xf227, + marsstroke = 0xf229, + marsstrokeh = 0xf22b, + marsstrokev = 0xf22a, + maxcdn = 0xf136, + meanpath = 0xf20c, + medium = 0xf23a, + medkit = 0xf0fa, + meetup = 0xf2e0, + meho = 0xf11a, + mercury = 0xf223, + microchip = 0xf2db, + microphone = 0xf130, + microphoneslash = 0xf131, + minus = 0xf068, + minuscircle = 0xf056, + minussquare = 0xf146, + minussquareo = 0xf147, + mixcloud = 0xf289, + mobile = 0xf10b, + mobilephone = 0xf10b, + modx = 0xf285, + money = 0xf0d6, + moono = 0xf186, + mortarboard = 0xf19d, + motorcycle = 0xf21c, + mousepointer = 0xf245, + music = 0xf001, + navicon = 0xf0c9, + neuter = 0xf22c, + newspapero = 0xf1ea, + objectgroup = 0xf247, + objectungroup = 0xf248, + odnoklassniki = 0xf263, + odnoklassnikisquare = 0xf264, + opencart = 0xf23d, + openid = 0xf19b, + opera = 0xf26a, + optinmonster = 0xf23c, + outdent = 0xf03b, + pagelines = 0xf18c, + paintbrush = 0xf1fc, + paperplane = 0xf1d8, + paperplaneo = 0xf1d9, + paperclip = 0xf0c6, + paragraph = 0xf1dd, + paste = 0xf0ea, + pause = 0xf04c, + pausecircle = 0xf28b, + pausecircleo = 0xf28c, + paw = 0xf1b0, + paypal = 0xf1ed, + pencil = 0xf040, + pencilsquare = 0xf14b, + pencilsquareo = 0xf044, + percent = 0xf295, + phone = 0xf095, + phonesquare = 0xf098, + photo = 0xf03e, + pictureo = 0xf03e, + piechart = 0xf200, + piedpiper = 0xf2ae, + piedpiperalt = 0xf1a8, + piedpiperpp = 0xf1a7, + pinterest = 0xf0d2, + pinterestp = 0xf231, + pinterestsquare = 0xf0d3, + plane = 0xf072, + play = 0xf04b, + playcircle = 0xf144, + playcircleo = 0xf01d, + plug = 0xf1e6, + plus = 0xf067, + pluscircle = 0xf055, + plussquare = 0xf0fe, + plussquareo = 0xf196, + podcast = 0xf2ce, + poweroff = 0xf011, + print = 0xf02f, + producthunt = 0xf288, + puzzlepiece = 0xf12e, + qq = 0xf1d6, + qrcode = 0xf029, + question = 0xf128, + questioncircle = 0xf059, + questioncircleo = 0xf29c, + quora = 0xf2c4, + quoteleft = 0xf10d, + quoteright = 0xf10e, + ra = 0xf1d0, + random = 0xf074, + ravelry = 0xf2d9, + rebel = 0xf1d0, + recycle = 0xf1b8, + reddit = 0xf1a1, + redditalien = 0xf281, + redditsquare = 0xf1a2, + refresh = 0xf021, + registered = 0xf25d, + remove = 0xf00d, + renren = 0xf18b, + reorder = 0xf0c9, + repeat = 0xf01e, + reply = 0xf112, + replyall = 0xf122, + resistance = 0xf1d0, + retweet = 0xf079, + rmb = 0xf157, + road = 0xf018, + rocket = 0xf135, + rotateleft = 0xf0e2, + rotateright = 0xf01e, + rouble = 0xf158, + rss = 0xf09e, + rsssquare = 0xf143, + rub = 0xf158, + ruble = 0xf158, + rupee = 0xf156, + s15 = 0xf2cd, + safari = 0xf267, + save = 0xf0c7, + scissors = 0xf0c4, + scribd = 0xf28a, + search = 0xf002, + searchminus = 0xf010, + searchplus = 0xf00e, + sellsy = 0xf213, + send = 0xf1d8, + sendo = 0xf1d9, + server = 0xf233, + share = 0xf064, + sharealt = 0xf1e0, + sharealtsquare = 0xf1e1, + sharesquare = 0xf14d, + sharesquareo = 0xf045, + shekel = 0xf20b, + sheqel = 0xf20b, + shield = 0xf132, + ship = 0xf21a, + shirtsinbulk = 0xf214, + shoppingbag = 0xf290, + shoppingbasket = 0xf291, + shoppingcart = 0xf07a, + shower = 0xf2cc, + signin = 0xf090, + signlanguage = 0xf2a7, + signout = 0xf08b, + signal = 0xf012, + signing = 0xf2a7, + simplybuilt = 0xf215, + sitemap = 0xf0e8, + skyatlas = 0xf216, + skype = 0xf17e, + slack = 0xf198, + sliders = 0xf1de, + slideshare = 0xf1e7, + smileo = 0xf118, + snapchat = 0xf2ab, + snapchatghost = 0xf2ac, + snapchatsquare = 0xf2ad, + snowflakeo = 0xf2dc, + soccerballo = 0xf1e3, + sort = 0xf0dc, + sortalphaasc = 0xf15d, + sortalphadesc = 0xf15e, + sortamountasc = 0xf160, + sortamountdesc = 0xf161, + sortasc = 0xf0de, + sortdesc = 0xf0dd, + sortdown = 0xf0dd, + sortnumericasc = 0xf162, + sortnumericdesc = 0xf163, + sortup = 0xf0de, + soundcloud = 0xf1be, + spaceshuttle = 0xf197, + spinner = 0xf110, + spoon = 0xf1b1, + spotify = 0xf1bc, + square = 0xf0c8, + squareo = 0xf096, + stackexchange = 0xf18d, + stackoverflow = 0xf16c, + star = 0xf005, + starhalf = 0xf089, + starhalfempty = 0xf123, + starhalffull = 0xf123, + starhalfo = 0xf123, + staro = 0xf006, + steam = 0xf1b6, + steamsquare = 0xf1b7, + stepbackward = 0xf048, + stepforward = 0xf051, + stethoscope = 0xf0f1, + stickynote = 0xf249, + stickynoteo = 0xf24a, + stop = 0xf04d, + stopcircle = 0xf28d, + stopcircleo = 0xf28e, + streetview = 0xf21d, + strikethrough = 0xf0cc, + stumbleupon = 0xf1a4, + stumbleuponcircle = 0xf1a3, + subscript = 0xf12c, + subway = 0xf239, + suitcase = 0xf0f2, + suno = 0xf185, + superpowers = 0xf2dd, + superscript = 0xf12b, + support = 0xf1cd, + table = 0xf0ce, + tablet = 0xf10a, + tachometer = 0xf0e4, + tag = 0xf02b, + tags = 0xf02c, + tasks = 0xf0ae, + taxi = 0xf1ba, + telegram = 0xf2c6, + television = 0xf26c, + tencentweibo = 0xf1d5, + terminal = 0xf120, + textheight = 0xf034, + textwidth = 0xf035, + th = 0xf00a, + thlarge = 0xf009, + thlist = 0xf00b, + themeisle = 0xf2b2, + thermometer = 0xf2c7, + thermometer0 = 0xf2cb, + thermometer1 = 0xf2ca, + thermometer2 = 0xf2c9, + thermometer3 = 0xf2c8, + thermometer4 = 0xf2c7, + thermometerempty = 0xf2cb, + thermometerfull = 0xf2c7, + thermometerhalf = 0xf2c9, + thermometerquarter = 0xf2ca, + thermometerthreequarters = 0xf2c8, + thumbtack = 0xf08d, + thumbsdown = 0xf165, + thumbsodown = 0xf088, + thumbsoup = 0xf087, + thumbsup = 0xf164, + ticket = 0xf145, + times = 0xf00d, + timescircle = 0xf057, + timescircleo = 0xf05c, + timesrectangle = 0xf2d3, + timesrectangleo = 0xf2d4, + tint = 0xf043, + toggledown = 0xf150, + toggleleft = 0xf191, + toggleoff = 0xf204, + toggleon = 0xf205, + toggleright = 0xf152, + toggleup = 0xf151, + trademark = 0xf25c, + train = 0xf238, + transgender = 0xf224, + transgenderalt = 0xf225, + trash = 0xf1f8, + trasho = 0xf014, + tree = 0xf1bb, + trello = 0xf181, + tripadvisor = 0xf262, + trophy = 0xf091, + truck = 0xf0d1, + fa_try = 0xf195, + tty = 0xf1e4, + tumblr = 0xf173, + tumblrsquare = 0xf174, + turkishlira = 0xf195, + tv = 0xf26c, + twitch = 0xf1e8, + twitter = 0xf099, + twittersquare = 0xf081, + umbrella = 0xf0e9, + underline = 0xf0cd, + undo = 0xf0e2, + universalaccess = 0xf29a, + university = 0xf19c, + unlink = 0xf127, + unlock = 0xf09c, + unlockalt = 0xf13e, + unsorted = 0xf0dc, + upload = 0xf093, + usb = 0xf287, + usd = 0xf155, + user = 0xf007, + usercircle = 0xf2bd, + usercircleo = 0xf2be, + usermd = 0xf0f0, + usero = 0xf2c0, + userplus = 0xf234, + usersecret = 0xf21b, + usertimes = 0xf235, + users = 0xf0c0, + vcard = 0xf2bb, + vcardo = 0xf2bc, + venus = 0xf221, + venusdouble = 0xf226, + venusmars = 0xf228, + viacoin = 0xf237, + viadeo = 0xf2a9, + viadeosquare = 0xf2aa, + videocamera = 0xf03d, + vimeo = 0xf27d, + vimeosquare = 0xf194, + vine = 0xf1ca, + vk = 0xf189, + volumecontrolphone = 0xf2a0, + volumedown = 0xf027, + volumeoff = 0xf026, + volumeup = 0xf028, + warning = 0xf071, + wechat = 0xf1d7, + weibo = 0xf18a, + weixin = 0xf1d7, + whatsapp = 0xf232, + wheelchair = 0xf193, + wheelchairalt = 0xf29b, + wifi = 0xf1eb, + wikipediaw = 0xf266, + windowclose = 0xf2d3, + windowcloseo = 0xf2d4, + windowmaximize = 0xf2d0, + windowminimize = 0xf2d1, + windowrestore = 0xf2d2, + windows = 0xf17a, + won = 0xf159, + wordpress = 0xf19a, + wpbeginner = 0xf297, + wpexplorer = 0xf2de, + wpforms = 0xf298, + wrench = 0xf0ad, + xing = 0xf168, + xingsquare = 0xf169, + ycombinator = 0xf23b, + ycombinatorsquare = 0xf1d4, + yahoo = 0xf19e, + yc = 0xf23b, + ycsquare = 0xf1d4, + yelp = 0xf1e9, + yen = 0xf157, + yoast = 0xf2b1, + youtube = 0xf167, + youtubeplay = 0xf16a, + youtubesquare = 0xf166 + }; +} + + + +//--------------------------------------------------------------------------------------- + +class QtAwesomeIconPainter; + +/// The main class for managing icons +/// This class requires a 2-phase construction. You must first create the class and then initialize it via an init* method +class QtAwesome : public QObject +{ +Q_OBJECT + +public: + + explicit QtAwesome(QObject *parent = 0); + virtual ~QtAwesome(); + + void init( const QString& fontname ); + bool initFontAwesome(); + + void addNamedCodepoint( const QString& name, int codePoint ); + QHash namedCodePoints() { return namedCodepoints_; } + + void setDefaultOption( const QString& name, const QVariant& value ); + QVariant defaultOption( const QString& name ); + + QIcon icon( int character, const QVariantMap& options = QVariantMap() ); + QIcon icon( const QString& name, const QVariantMap& options = QVariantMap() ); + QIcon icon(QtAwesomeIconPainter* painter, const QVariantMap& optionMap = QVariantMap() ); + + void give( const QString& name, QtAwesomeIconPainter* painter ); + + QFont font( int size ); + + /// Returns the font-name that is used as icon-map + QString fontName() { return fontName_ ; } + +private: + QString fontName_; ///< The font name used for this map + QHash namedCodepoints_; ///< A map with names mapped to code-points + + QHash painterMap_; ///< A map of custom painters + QVariantMap defaultOptions_; ///< The default icon options + QtAwesomeIconPainter* fontIconPainter_; ///< A special painter fo painting codepoints +}; + + +//--------------------------------------------------------------------------------------- + + +/// The QtAwesomeIconPainter is a specialized painter for painting icons +/// your can implement an iconpainter to create custom font-icon code +class QtAwesomeIconPainter +{ +public: + virtual ~QtAwesomeIconPainter() {} + virtual void paint( QtAwesome* awesome, QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state, const QVariantMap& options ) = 0; +}; + +Q_DECLARE_METATYPE(QtAwesomeAnimation*) + +#endif // QTAWESOME_H diff --git a/thirdparty/QtAwesome/QtAwesome/QtAwesome.pri b/thirdparty/QtAwesome/QtAwesome/QtAwesome.pri new file mode 100755 index 00000000..caef09b4 --- /dev/null +++ b/thirdparty/QtAwesome/QtAwesome/QtAwesome.pri @@ -0,0 +1,12 @@ + +INCLUDEPATH += $$PWD + +SOURCES += $$PWD/QtAwesome.cpp \ + $$PWD/QtAwesomeAnim.cpp + +HEADERS += $$PWD/QtAwesome.h \ + $$PWD/QtAwesomeAnim.h + +RESOURCES += $$PWD/QtAwesome.qrc + + diff --git a/thirdparty/QtAwesome/QtAwesome/QtAwesome.pro b/thirdparty/QtAwesome/QtAwesome/QtAwesome.pro new file mode 100755 index 00000000..4bf64d64 --- /dev/null +++ b/thirdparty/QtAwesome/QtAwesome/QtAwesome.pro @@ -0,0 +1,29 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-04-18T13:28:42 +# +#------------------------------------------------- + +TARGET = QtAwesome +TEMPLATE = lib +CONFIG += staticlib c++11 +QT += widgets + +SOURCES += QtAwesome.cpp QtAwesomeAnim.cpp +HEADERS += QtAwesome.h QtAwesomeAnim.h + +isEmpty(PREFIX) { + unix { + PREFIX = /usr + } else { + PREFIX = $$[QT_INSTALL_PREFIX] + } +} + +install_headers.files = QtAwesome.h QtAwesomeAnim.h +install_headers.path = $$PREFIX/include +target.path = $$PREFIX/lib +INSTALLS += install_headers target + +RESOURCES += \ + QtAwesome.qrc diff --git a/thirdparty/QtAwesome/QtAwesome/QtAwesome.qrc b/thirdparty/QtAwesome/QtAwesome/QtAwesome.qrc new file mode 100755 index 00000000..db80a8e1 --- /dev/null +++ b/thirdparty/QtAwesome/QtAwesome/QtAwesome.qrc @@ -0,0 +1,5 @@ + + + fonts/fontawesome-4.7.0.ttf + + diff --git a/thirdparty/QtAwesome/QtAwesome/QtAwesomeAnim.cpp b/thirdparty/QtAwesome/QtAwesome/QtAwesomeAnim.cpp new file mode 100755 index 00000000..8ad44b68 --- /dev/null +++ b/thirdparty/QtAwesome/QtAwesome/QtAwesomeAnim.cpp @@ -0,0 +1,46 @@ +#include "QtAwesomeAnim.h" + +#include +#include +#include +#include +#include + + +QtAwesomeAnimation::QtAwesomeAnimation(QWidget *parentWidget, int interval, int step) + : parentWidgetRef_( parentWidget ) + , timer_( 0 ) + , interval_( interval ) + , step_( step ) + , angle_( 0.0f ) +{ + +} + +void QtAwesomeAnimation::setup( QPainter &painter, const QRect &rect) +{ + // first time set the timer + if( !timer_ ) + { + timer_ = new QTimer(); + connect(timer_,SIGNAL(timeout()), this, SLOT(update()) ); + timer_->start(interval_); + } + else + { + //timer, angle, self.step = self.info[self.parent_widget] + float x_center = rect.width() * 0.5; + float y_center = rect.height() * 0.5; + painter.translate(x_center, y_center); + painter.rotate(angle_); + painter.translate(-x_center, -y_center); + } +} + + +void QtAwesomeAnimation::update() +{ + angle_ += step_; + angle_ = std::fmod( angle_, 360); + parentWidgetRef_->update(); +} diff --git a/thirdparty/QtAwesome/QtAwesome/QtAwesomeAnim.h b/thirdparty/QtAwesome/QtAwesome/QtAwesomeAnim.h new file mode 100755 index 00000000..5e3d11d6 --- /dev/null +++ b/thirdparty/QtAwesome/QtAwesome/QtAwesomeAnim.h @@ -0,0 +1,36 @@ +#ifndef QTAWESOMEANIMATION_H +#define QTAWESOMEANIMATION_H + +#include + +class QPainter; +class QRect; +class QTimer; +class QWidget; + +/// +/// Basic Animation Support for QtAwesome (Inspired by https://github.com/spyder-ide/qtawesome) +/// +class QtAwesomeAnimation : public QObject +{ +Q_OBJECT + +public: + QtAwesomeAnimation( QWidget* parentWidget, int interval=10, int step=1); + + void setup( QPainter& painter, const QRect& rect ); + +public slots: + void update(); + +private: + QWidget* parentWidgetRef_; + QTimer* timer_; + int interval_; + int step_; + float angle_; + +}; + + +#endif // QTAWESOMEANIMATION_H diff --git a/thirdparty/QtAwesome/QtAwesome/fonts/fontawesome-4.7.0.ttf b/thirdparty/QtAwesome/QtAwesome/fonts/fontawesome-4.7.0.ttf new file mode 100755 index 00000000..35acda2f Binary files /dev/null and b/thirdparty/QtAwesome/QtAwesome/fonts/fontawesome-4.7.0.ttf differ diff --git a/thirdparty/QtAwesome/QtAwesomeSample/QtAwesomeSample.pro b/thirdparty/QtAwesome/QtAwesomeSample/QtAwesomeSample.pro new file mode 100755 index 00000000..3f9082a3 --- /dev/null +++ b/thirdparty/QtAwesome/QtAwesomeSample/QtAwesomeSample.pro @@ -0,0 +1,20 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2013-04-18T15:05:07 +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = QtAwesomeSample +TEMPLATE = app + + +SOURCES += main.cpp + +HEADERS += + + +include(../QtAwesome/QtAwesome.pri) diff --git a/thirdparty/QtAwesome/QtAwesomeSample/main.cpp b/thirdparty/QtAwesome/QtAwesomeSample/main.cpp new file mode 100755 index 00000000..1c4be1d8 --- /dev/null +++ b/thirdparty/QtAwesome/QtAwesomeSample/main.cpp @@ -0,0 +1,63 @@ +/** + * MIT Licensed + * + * Copyright 2011-2015 - Reliable Bits Software by Blommers IT. All Rights Reserved. + * Author Rick Blommers + */ + +#include "QtAwesome.h" + +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QMainWindow w; + + QtAwesome* awesome = new QtAwesome(&w); + awesome->initFontAwesome(); + + QVBoxLayout* layout = new QVBoxLayout(); + + // a simple beer button + //===================== + { + QPushButton* beerButton = new QPushButton( "Cheers!"); + + QVariantMap options; + options.insert("anim", qVariantFromValue( new QtAwesomeAnimation(beerButton) ) ); + beerButton->setIcon( awesome->icon( fa::beer, options ) ); + + layout->addWidget(beerButton); + } + + // a simple beer checkbox button + //============================== + { + QPushButton* toggleButton = new QPushButton("Toggle Me"); + toggleButton->setCheckable(true); + + QVariantMap options; + options.insert("color", QColor(Qt::green) ); + options.insert("text-off", QString(fa::squareo) ); + options.insert("color-off", QColor(Qt::red) ); + toggleButton->setIcon( awesome->icon( fa::checksquareo, options )); + + + layout->addWidget(toggleButton); + } + + + // add the samples + QWidget* samples = new QWidget(); + samples->setLayout(layout); + w.setCentralWidget(samples); + + w.show(); + + return app.exec(); +} diff --git a/thirdparty/QtAwesome/README.md b/thirdparty/QtAwesome/README.md new file mode 100755 index 00000000..8f892b92 --- /dev/null +++ b/thirdparty/QtAwesome/README.md @@ -0,0 +1,230 @@ +QtAwesome - Font Awesome support for Qt applications +==================================================== + +Description +----------- + +QtAwesome is a simple library that can be used to add [Font Awesome](http://fortawesome.github.io/Font-Awesome/) icons to your [Qt application](http://qt-project.org/). + +NOTE: Though the name is QtAwesome and currently it's very Font Awesome based, you can use every other icon/glyph font you want. + +The class can also be used to manage your own dynamic code-drawn icons, by adding named icon-painters. + + +Updated to FontAwesome 4.7.0 +---------------------------- + +This library has been updated to Font Awesome version **4.7.0**. + +* In the 4.5.0 version the _linux name has been changed to fa_linux. (Makes the naming of conflicting/invalid names more consistent, like fa_try and fa_500px) +* You can find the previous FontAwesome 4 c++11 library in the [c++11 branch](https://github.com/gamecreature/QtAwesome/tree/c++11). +* You can find the previous FontAwesome 3 library in the [fontawesome-3 branch](https://github.com/gamecreature/QtAwesome/tree/fontawesome-3). + + +**Note about previous c++11** + +I removed the C++11 requirement. And moved the c++11 code to a c++11 branch. +It's not that I don't like c++11, but the typed enum made the code less flexible then it is now. +Just integers it is. Simpler is better. + + + + +Installation +------------ + +The easiest way to include QtAweome in your project is to copy the QtAwesome directory to your +project tree and add the following `include()` to your Qt project file: + + include(QtAwesome/QtAwesome.pri) + +Now you are good to go! + + +Usage +----- + +You probably want to create a single QtAwesome object for your whole application: + +```` + QtAwesome* awesome = new QtAwesome( qApp ) + awesome->initFontAwesome(); // This line is important as it loads the font and initializes the named icon map + +```` + +* Add an accessor to this object (i.e. a global function, member of your application object, or whatever you like). +* Use an icon name from the [Font Awesome Cheatsheet](http://fortawesome.github.io/Font-Awesome/cheatsheet/). + + +Example +-------- + +```c++ +// You should create a single object of QtAwesome. +QtAwesome* awesome = new QtAwesome( qApp ); +awesome->initFontAwesome(); + +// Next create your icon with the help of the icon-enumeration (no dashes): +QPushButton* beerButton new QPushButton( awesome->icon( fa::beer ), "Cheers!" ); + +// You can also use 'string' names to access the icons. (The string version omits the 'fa-' or 'icon-' prefix and has no dashes ) +QPushButton* coffeeButton new QPushButton( awesome->icon( "coffee" ), "Black please!" ); + +// When you create an icon you can supply some options for your icons: +// The available options can be found at the "Default options"-section + +QVariantMap options; +options.insert( "color" , QColor(255,0,0) ); +QPushButton* musicButton = new QPushButton( awesome->icon( fa::music, options ), "Music" ); + +// You can also change the default options. +// for example if you always would like to have green icons you could call) +awesome->setDefaultOption( "color-disabled", QColor(0,255,0) ); + +// You can also directly render a label with this font +QLabel* label = new QLabel( QChar( fa::group ) ); +label->setFont( awesome->font(16) ); + +``` + +Example custom painter +---------------------- + +This example registers a custom painter for supporting a duplicate icon (it draws 2 "plus marks"): + +```c++ +class DuplicateIconPainter : public QtAwesomeIconPainter +{ +public: + virtual void paint( QtAwesome* awesome, QPainter* painter, const QRect& rectIn, QIcon::Mode mode, QIcon::State state, const QVariantMap& options ) + { + int drawSize = qRound(rectIn.height()*0.5); + int offset = rectIn.height() / 4; + QChar chr = QChar( static_cast(fa::plus) ); + + painter->setFont( awesome->font( drawSize ) ); + + painter->setPen( QColor(100,100,100) ); + painter->drawText( QRect( QPoint(offset*2, offset*2), QSize(drawSize, drawSize) ), chr , QTextOption( Qt::AlignCenter|Qt::AlignVCenter ) ); + + painter->setPen( QColor(50,50,50) ); + painter->drawText( QRect( QPoint(rectIn.width()-drawSize-offset, rectIn.height()-drawSize-offset), QSize(drawSize, drawSize) ), chr , QTextOption( Qt::AlignCenter|Qt::AlignVCenter ) ); + + } +}; + +awesome->give("duplicate", new DuplicateIconPainter() ); +``` + + +Default options: +---------------- + + The following options are default in the QtAwesome class. + +```c++ +setDefaultOption( "color", QColor(50,50,50) ); +setDefaultOption( "color-disabled", QColor(70,70,70,60)); +setDefaultOption( "color-active", QColor(10,10,10)); +setDefaultOption( "color-selected", QColor(10,10,10)); + +setDefaultOption( "text", QString() ); // internal option +setDefaultOption( "text-disabled", QString() ); +setDefaultOption( "text-active", QString() ); +setDefaultOption( "text-selected", QString() ); + +setDefaultOption( "scale-factor", 0.9 ); +``` + + When creating an icon, it first populates the options-map with the default options from the QtAwesome object. + After that the options are expanded/overwritten by the options supplied to the icon. + + It is possible to use another glyph per icon-state. For example to make an icon-unlock symbol switch to locked when selected, + you could supply the following option: + +```c++ + options.insert("text-selected", QString( fa::lock ) ); +``` + +Color and text options have the following structure: +`keyname-iconmode-iconstate` + +Where iconmode normal is empty +And iconstate On is off. + +So the list of items used is: + +- color +- color-disabled +- color-active +- color-selected +- color-off +- color-disabled-off +- color-active-off +- color-selected-off +- text +- text-disabled +- text-active +- text-selected +- text-off +- text-disabled-off +- text-active-off +- text-selected-off + + +License +------- + +MIT License. Copyright 2013 - Reliable Bits Software by Blommers IT. [http://blommersit.nl/](http://blommersit.nl) + +The Font Awesome font is licensed under the SIL Open Font License - [http://scripts.sil.org/OFL](http://scripts.sil.org/OFL) +The Font Awesome pictograms are licensed under the CC BY 3.0 License - [http://creativecommons.org/licenses/by/3.0/](http://creativecommons.org/licenses/by/3.0/) +"Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome" + +Contact +------- + +* email: +* twitter: [https://twitter.com/gamecreature](https://twitter.com/gamecreature) +* website: [http://blommersit.nl](http://blommersit.nl) (warning Dutch content ahead) +* github: [https://github.com/gamecreature/QtAwesome](https://github.com/gamecreature/QtAwesome) + + +Known issues and workarounds +---------------------------- + +On Mac OS X, placing an qtAwesome icon in QMainWindow menu, doesn't work directly. +See the following issue: [https://github.com/gamecreature/QtAwesome/issues/10] + +A workaround for this problem is converting it to a Pixmap icon like this: + +```c++ +QAction* menuAction = new QAction("test"); +menuAction->setIcon( awesome->icon(fa::beer).pixmap(32,32) ); +``` + + +Remarks +------- + +I've created this project because I needed some nice icons for my own Qt project. After doing a lot of +css/html5 work and being spoiled by the ease of twitter bootstrap with Font Awesome, +I thought it would be nice to be able to use these icons for my Qt project. + +I've slightly changed the code from the original, added some more documentation, but it's still +a work in progress. So feel free to drop me an e-mail for your suggestions and improvements! + +There are still some things todo, like: + + * document the usage of another icon font + * add some tests + * do some code cleanup + +Thanks go to the contributors of this project! + +And of course last but not least, + +Many thanks go to Dave Gandy an the other Font Awesome contributors!! [http://fortawesome.github.com/Font-Awesome](http://fortawesome.github.com/Font-Awesome) +And of course to the Qt team/contributors for supplying this great cross-platform c++ library. + +Contributions are welcome! Feel free to fork and send a pull request through Github.