Beginning impementation of CGizmo; loading assets + rendering are implemented as well as toggling modes and scaling it on the UI (plus minor renderer bug fixes)

This commit is contained in:
parax0 2015-08-16 00:29:37 -04:00
parent 084ddeadee
commit 44d0900125
11 changed files with 501 additions and 11 deletions

View File

@ -279,6 +279,12 @@ const float& CVector3f::operator[](long index) const
const CVector3f CVector3f::skZero = CVector3f(0.f);
const CVector3f CVector3f::skOne = CVector3f(1.f);
const CVector3f CVector3f::skInfinite = CVector3f(FLT_MAX);
const CVector3f CVector3f::skForward = CVector3f(0.f, 1.f, 0.f);
const CVector3f CVector3f::skBack = CVector3f(0.f, -1.f, 0.f);
const CVector3f CVector3f::skRight = CVector3f( 1.f, 0.f, 0.f);
const CVector3f CVector3f::skLeft = CVector3f(-1.f, 0.f, 0.f);
const CVector3f CVector3f::skUp = CVector3f(0.f, 0.f, 1.f);
const CVector3f CVector3f::skDown = CVector3f(0.f, 0.f, -1.f);
// ************ OTHER ************
std::ostream& operator<<(std::ostream& o, const CVector3f& Vector)

View File

@ -75,6 +75,12 @@ public:
static const CVector3f skZero;
static const CVector3f skOne;
static const CVector3f skInfinite;
static const CVector3f skForward;
static const CVector3f skBack;
static const CVector3f skRight;
static const CVector3f skLeft;
static const CVector3f skUp;
static const CVector3f skDown;
// Other
friend std::ostream& operator<<(std::ostream& o, const CVector3f& Vector);

View File

@ -125,6 +125,7 @@ void CRenderer::SetViewportSize(u32 Width, u32 Height)
void CRenderer::RenderBuckets(CCamera& Camera)
{
if (!mInitialized) Init();
mSceneFramebuffer.Bind();
// Set backface culling
if (mOptions & eEnableBackfaceCull) glEnable(GL_CULL_FACE);
@ -141,6 +142,7 @@ void CRenderer::RenderBuckets(CCamera& Camera)
mTransparentBucket.Clear();
// Clear depth buffer to enable more rendering passes
glDepthMask(GL_TRUE);
glClear(GL_DEPTH_BUFFER_BIT);
}
@ -172,6 +174,7 @@ void CRenderer::RenderBloom()
glViewport(0, 0, BloomWidth, BloomHeight);
glClearColor(0.f, 0.f, 0.f, 0.f);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_FALSE);
CGraphics::SetIdentityMVP();
CGraphics::UpdateMVPBlock();
@ -308,10 +311,6 @@ void CRenderer::BeginFrame()
void CRenderer::EndFrame()
{
// Post-processing
if (mBloomMode != eNoBloom)
RenderBloom();
// Render result to screen
glBindFramebuffer(GL_FRAMEBUFFER, mDefaultFramebuffer);
InitFramebuffer();

View File

@ -13,8 +13,8 @@ public:
IRenderable() {}
virtual ~IRenderable() {}
virtual void AddToRenderer(CRenderer *pRenderer) = 0;
virtual void Draw(ERenderOptions options) = 0;
virtual void DrawAsset(ERenderOptions, u32) {}
virtual void Draw(ERenderOptions options) {}
virtual void DrawAsset(ERenderOptions options, u32 asset) {}
virtual void DrawSelection() {}
};

BIN
EditorAssets/SelectMode.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -27,5 +27,6 @@
<file>EditorAssets/Modify.png</file>
<file>EditorAssets/Unlink.png</file>
<file>EditorAssets/World.png</file>
<file>EditorAssets/SelectMode.png</file>
</qresource>
</RCC>

221
UI/CGizmo.cpp Normal file
View File

@ -0,0 +1,221 @@
#include "CGizmo.h"
#include <Core/CRenderer.h>
#include <Core/CResCache.h>
CGizmo::CGizmo()
{
LoadModels();
mMode = eRotate;
mSelectedAxes = eNone;
mGizmoSize = 1.f;
mCameraDist = 0.f;
mPosition = CVector3f::skZero;
mRotation = CQuaternion::skIdentity;
mScale = CVector3f::skOne;
mDeltaPosition = CVector3f::skZero;
mDeltaRotation = CQuaternion::skIdentity;
mDeltaScale = CVector3f::skOne;
mFlipScaleX = false;
mFlipScaleY = false;
mFlipScaleZ = false;
}
CGizmo::~CGizmo()
{
}
void CGizmo::AddToRenderer(CRenderer *pRenderer)
{
// Transform is updated every frame even if the user doesn't modify the gizmo
// in order to account for scale changes based on camera distance
UpdateTransform();
// Determine which SModelPart array to use
SModelPart *pParts;
u32 numParts;
if (mMode == eTranslate) {
pParts = smTranslateModels;
numParts = 6;
} else if (mMode == eRotate) {
pParts = smRotateModels;
numParts = 4;
} else if (mMode == eScale) {
pParts = smScaleModels;
numParts = 7;
}
// Add all parts to renderer
for (u32 iPart = 0; iPart < numParts; iPart++)
{
CModel *pModel = pParts->pModel;
// Determine whether to use the mat set for regular (0) or highlight (1)
bool isHighlighted = (mSelectedAxes & pParts->modelAxes) == pParts->modelAxes;
u32 setID = (isHighlighted ? 1 : 0);
// Add to renderer...
if (pModel->HasTransparency(setID))
pRenderer->AddTransparentMesh(this, iPart, pModel->AABox().Transformed(mTransform), eDrawAsset);
else
pRenderer->AddOpaqueMesh(this, iPart, pModel->AABox().Transformed(mTransform), eDrawAsset);
pParts++;
}
}
void CGizmo::DrawAsset(ERenderOptions options, u32 asset)
{
CGraphics::sMVPBlock.ModelMatrix = mTransform.ToMatrix4f();
CGraphics::UpdateMVPBlock();
// Determine which SModelPart array to use
SModelPart *pParts;
u32 numParts;
if (mMode == eTranslate) {
pParts = smTranslateModels;
numParts = 6;
} else if (mMode == eRotate) {
pParts = smRotateModels;
numParts = 4;
} else if (mMode == eScale) {
pParts = smScaleModels;
numParts = 7;
}
if (asset >= numParts) return;
// Draw model
bool isHighlighted = (mSelectedAxes & pParts[asset].modelAxes) == pParts[asset].modelAxes;
u32 setID = (isHighlighted ? 1 : 0);
pParts[asset].pModel->Draw((ERenderOptions) 0, setID);
}
void CGizmo::DrawRotationOutline()
{
CGraphics::sMVPBlock.ModelMatrix = mBillboardTransform.ToMatrix4f();
CGraphics::UpdateMVPBlock();
smRotateClipOutline.pModel->Draw((ERenderOptions) 0, 0);
}
void CGizmo::IncrementSize()
{
static const float skIncAmount = 1.3f;
static const float skMaxSize = powf(skIncAmount, 4);
mGizmoSize *= skIncAmount;
if (mGizmoSize > skMaxSize) mGizmoSize = skMaxSize;
}
void CGizmo::DecrementSize()
{
static const float skDecAmount = (1.f / 1.3f);
static const float skMinSize = powf(skDecAmount, 4);
mGizmoSize *= skDecAmount;
if (mGizmoSize < skMinSize) mGizmoSize = skMinSize;
}
void CGizmo::UpdateForCamera(const CCamera &camera)
{
CVector3f camPos = camera.Position();
mCameraDist = mPosition.Distance(camPos);
mFlipScaleX = camPos.x < mPosition.x;
mFlipScaleY = camPos.y < mPosition.y;
mFlipScaleZ = camPos.z < mPosition.z;
// todo: make this cleaner...
CVector3f billDir = (mPosition - camPos).Normalized();
CVector3f axis = CVector3f::skForward.Cross(billDir);
float angle = acos(CVector3f::skForward.Dot(billDir));
angle = 180 + (angle * 180 / 3.14159265358979323846f);
mBillboardRotation = CQuaternion::FromAxisAngle(angle, axis);
}
CGizmo::EGizmoMode CGizmo::Mode()
{
return mMode;
}
void CGizmo::SetMode(EGizmoMode mode)
{
mMode = mode;
}
void CGizmo::SetPosition(const CVector3f& position)
{
mPosition = position;
}
// ************ PRIVATE STATIC ************
void CGizmo::LoadModels()
{
if (!smModelsLoaded)
{
smTranslateModels[CGIZMO_TRANSLATE_X] = SModelPart(eX, (CModel*) gResCache.GetResource("../resources/editor/TranslateGizmoX.CMDL"));
smTranslateModels[CGIZMO_TRANSLATE_Y] = SModelPart(eY, (CModel*) gResCache.GetResource("../resources/editor/TranslateGizmoY.CMDL"));
smTranslateModels[CGIZMO_TRANSLATE_Z] = SModelPart(eZ, (CModel*) gResCache.GetResource("../resources/editor/TranslateGizmoZ.CMDL"));
smTranslateModels[CGIZMO_TRANSLATE_XY] = SModelPart(eXY, (CModel*) gResCache.GetResource("../resources/editor/TranslateGizmoXY.CMDL"));
smTranslateModels[CGIZMO_TRANSLATE_XZ] = SModelPart(eXZ, (CModel*) gResCache.GetResource("../resources/editor/TranslateGizmoXZ.CMDL"));
smTranslateModels[CGIZMO_TRANSLATE_YZ] = SModelPart(eYZ, (CModel*) gResCache.GetResource("../resources/editor/TranslateGizmoYZ.CMDL"));
smRotateModels[CGIZMO_ROTATE_X] = SModelPart(eX, (CModel*) gResCache.GetResource("../resources/editor/RotateGizmoX.CMDL"));
smRotateModels[CGIZMO_ROTATE_Y] = SModelPart(eY, (CModel*) gResCache.GetResource("../resources/editor/RotateGizmoY.CMDL"));
smRotateModels[CGIZMO_ROTATE_Z] = SModelPart(eZ, (CModel*) gResCache.GetResource("../resources/editor/RotateGizmoZ.CMDL"));
smRotateModels[CGIZMO_ROTATE_XYZ] = SModelPart(eXYZ, (CModel*) gResCache.GetResource("../resources/editor/RotateGizmoXYZ.CMDL"));
smRotateClipOutline = SModelPart(eNone, (CModel*) gResCache.GetResource("../resources/editor/RotateGizmoClipOutline.CMDL"));
smScaleModels[CGIZMO_SCALE_X] = SModelPart(eX, (CModel*) gResCache.GetResource("../resources/editor/ScaleGizmoX.CMDL"));
smScaleModels[CGIZMO_SCALE_Y] = SModelPart(eY, (CModel*) gResCache.GetResource("../resources/editor/ScaleGizmoY.CMDL"));
smScaleModels[CGIZMO_SCALE_Z] = SModelPart(eZ, (CModel*) gResCache.GetResource("../resources/editor/ScaleGizmoZ.CMDL"));
smScaleModels[CGIZMO_SCALE_XY] = SModelPart(eXY, (CModel*) gResCache.GetResource("../resources/editor/ScaleGizmoXY.CMDL"));
smScaleModels[CGIZMO_SCALE_XZ] = SModelPart(eXZ, (CModel*) gResCache.GetResource("../resources/editor/ScaleGizmoXZ.CMDL"));
smScaleModels[CGIZMO_SCALE_YZ] = SModelPart(eYZ, (CModel*) gResCache.GetResource("../resources/editor/ScaleGizmoYZ.CMDL"));
smScaleModels[CGIZMO_SCALE_XYZ] = SModelPart(eXYZ, (CModel*) gResCache.GetResource("../resources/editor/ScaleGizmoXYZ.CMDL"));
smModelsLoaded = true;
}
}
// ************ PROTECTED ************
void CGizmo::UpdateTransform()
{
// Scale is recalculated every frame because it changes frequently, based on camera distance
// Rotation and position values are just saved directly
mScale = mGizmoSize * (mCameraDist / 10.f);
// Scale also factors in delta scale + axis flip if mode is Scale.
if (mMode == eScale)
{
mScale *= mDeltaScale;
if (mFlipScaleX) mScale.x = -mScale.x;
if (mFlipScaleY) mScale.y = -mScale.y;
if (mFlipScaleZ) mScale.z = -mScale.z;
}
// Create transform
mTransform = CTransform4f::skIdentity;
mTransform.Scale(mScale);
mTransform.Rotate(mRotation);
mTransform.Translate(mPosition);
// Create billboard transform for rotation gizmo
if (mMode == eRotate)
{
mBillboardTransform = CTransform4f::skIdentity;
mBillboardTransform.Scale(mScale);
mBillboardTransform.Rotate(mBillboardRotation);
mBillboardTransform.Translate(mPosition);
}
}
// ************ STATIC MEMBER INITIALIZATION ************
bool CGizmo::smModelsLoaded = false;
CGizmo::SModelPart CGizmo::smTranslateModels[6];
CGizmo::SModelPart CGizmo::smRotateModels[4];
CGizmo::SModelPart CGizmo::smScaleModels[7];
CGizmo::SModelPart CGizmo::smRotateClipOutline;

113
UI/CGizmo.h Normal file
View File

@ -0,0 +1,113 @@
#ifndef CGIZMO_H
#define CGIZMO_H
#include <Common/CVector3f.h>
#include <Common/CQuaternion.h>
#include <Common/EnumUtil.h>
#include <Core/CCamera.h>
#include <Core/CToken.h>
#include <Core/IRenderable.h>
#include <Resource/model/CModel.h>
#define CGIZMO_TRANSLATE_X 0
#define CGIZMO_TRANSLATE_Y 1
#define CGIZMO_TRANSLATE_Z 2
#define CGIZMO_TRANSLATE_XY 3
#define CGIZMO_TRANSLATE_XZ 4
#define CGIZMO_TRANSLATE_YZ 5
#define CGIZMO_ROTATE_X 0
#define CGIZMO_ROTATE_Y 1
#define CGIZMO_ROTATE_Z 2
#define CGIZMO_ROTATE_XYZ 3
#define CGIZMO_SCALE_X 0
#define CGIZMO_SCALE_Y 1
#define CGIZMO_SCALE_Z 2
#define CGIZMO_SCALE_XY 3
#define CGIZMO_SCALE_XZ 4
#define CGIZMO_SCALE_YZ 5
#define CGIZMO_SCALE_XYZ 6
class CGizmo : public IRenderable
{
public:
enum EGizmoMode
{
eTranslate, eRotate, eScale
};
enum EGizmoAxes
{
eNone = 0x0,
eX = 0x1,
eY = 0x2,
eZ = 0x4,
eXY = eX | eY,
eXZ = eX | eZ,
eYZ = eY | eZ,
eXYZ = eX | eY | eZ
};
private:
EGizmoMode mMode;
EGizmoAxes mSelectedAxes;
CTransform4f mBillboardTransform;
CQuaternion mBillboardRotation;
float mGizmoSize;
float mCameraDist;
CTransform4f mTransform;
CVector3f mPosition;
CQuaternion mRotation;
CVector3f mScale;
CVector3f mDeltaPosition;
CQuaternion mDeltaRotation;
CVector3f mDeltaScale;
bool mFlipScaleX;
bool mFlipScaleY;
bool mFlipScaleZ;
// Static
struct SModelPart
{
EGizmoAxes modelAxes;
CModel *pModel;
CToken modelToken;
SModelPart() {}
SModelPart(EGizmoAxes axes, CModel *_pModel) :
modelAxes(axes), pModel(_pModel), modelToken(_pModel) {}
};
static bool smModelsLoaded;
static SModelPart smTranslateModels[6];
static SModelPart smRotateModels[4];
static SModelPart smScaleModels[7];
static SModelPart smRotateClipOutline;
public:
CGizmo();
~CGizmo();
void AddToRenderer(CRenderer *pRenderer);
void DrawAsset(ERenderOptions options, u32 asset);
void DrawRotationOutline();
void IncrementSize();
void DecrementSize();
void UpdateForCamera(const CCamera& camera);
bool IntersectsRay(const CRay& ray);
EGizmoMode Mode();
void SetMode(EGizmoMode mode);
void SetPosition(const CVector3f& position);
// Protected
protected:
void UpdateTransform();
// Private Static
private:
static void LoadModels();
};
DEFINE_ENUM_FLAGS(CGizmo::EGizmoAxes)
#endif // CGIZMO_H

View File

@ -31,6 +31,7 @@ CWorldEditor::CWorldEditor(QWidget *parent) :
mpWorld = nullptr;
mpHoverNode = nullptr;
mDrawSky = true;
mShowGizmo = false;
mFrameCount = 0;
mFPSTimer.Start();
@ -54,6 +55,10 @@ CWorldEditor::CWorldEditor(QWidget *parent) :
ui->CamSpeedSpinBox->SetDefaultValue(1.0);
ResetHover();
// Initialize offscreen actions
addAction(ui->ActionIncrementGizmo);
addAction(ui->ActionDecrementGizmo);
// Connect signals and slots
connect(ui->CamSpeedSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnCameraSpeedChange(double)));
connect(ui->MainViewport, SIGNAL(PreRender()), this, SLOT(ViewportPreRender()));
@ -283,6 +288,19 @@ void CWorldEditor::ViewportRender(CCamera& Camera)
mpRenderer->RenderBuckets(Camera);
mpRenderer->RenderBloom();
if (mShowGizmo && (mSelectedNodes.size() > 0))
{
Camera.LoadMatrices();
mGizmo.UpdateForCamera(Camera);
mGizmo.AddToRenderer(mpRenderer);
if (mGizmo.Mode() == CGizmo::eRotate)
mGizmo.DrawRotationOutline();
mpRenderer->RenderBuckets(Camera);
}
mpRenderer->EndFrame();
mFrameTimer.Stop();
mFrameCount++;
@ -373,6 +391,9 @@ void CWorldEditor::UpdateSelectionUI()
ui->XSpinBox->setValue(pos.x);
ui->YSpinBox->setValue(pos.y);
ui->ZSpinBox->setValue(pos.z);
// Update gizmo
mGizmo.SetPosition(pos);
}
// ************ ACTIONS ************
@ -522,3 +543,52 @@ void CWorldEditor::on_ActionEditLayers_triggered()
Editor.SetArea(mpArea);
Editor.exec();
}
void CWorldEditor::on_ActionSelectObjects_triggered()
{
mShowGizmo = false;
ui->ActionSelectObjects->setChecked(true);
ui->ActionTranslate->setChecked(false);
ui->ActionRotate->setChecked(false);
ui->ActionScale->setChecked(false);
}
void CWorldEditor::on_ActionTranslate_triggered()
{
mShowGizmo = true;
mGizmo.SetMode(CGizmo::eTranslate);
ui->ActionSelectObjects->setChecked(false);
ui->ActionTranslate->setChecked(true);
ui->ActionRotate->setChecked(false);
ui->ActionScale->setChecked(false);
}
void CWorldEditor::on_ActionRotate_triggered()
{
mShowGizmo = true;
mGizmo.SetMode(CGizmo::eRotate);
ui->ActionSelectObjects->setChecked(false);
ui->ActionTranslate->setChecked(false);
ui->ActionRotate->setChecked(true);
ui->ActionScale->setChecked(false);
}
void CWorldEditor::on_ActionScale_triggered()
{
mShowGizmo = true;
mGizmo.SetMode(CGizmo::eScale);
ui->ActionSelectObjects->setChecked(false);
ui->ActionTranslate->setChecked(false);
ui->ActionRotate->setChecked(false);
ui->ActionScale->setChecked(true);
}
void CWorldEditor::on_ActionIncrementGizmo_triggered()
{
mGizmo.IncrementSize();
}
void CWorldEditor::on_ActionDecrementGizmo_triggered()
{
mGizmo.DecrementSize();
}

View File

@ -4,6 +4,7 @@
#include <QMainWindow>
#include <QList>
#include "CGizmo.h"
#include <Common/CRay.h>
#include <Common/CTimer.h>
#include <Common/EKeyInputs.h>
@ -23,6 +24,7 @@ class CWorldEditor : public QMainWindow
Q_OBJECT
CRenderer *mpRenderer;
CSceneManager *mpSceneManager;
CGizmo mGizmo;
CCamera mCamera;
CGameArea *mpArea;
CWorld *mpWorld;
@ -30,6 +32,7 @@ class CWorldEditor : public QMainWindow
CToken mWorldToken;
CTimer mFrameTimer;
bool mDrawSky;
bool mShowGizmo;
CVector3f mHoverPoint;
CSceneNode *mpHoverNode;
@ -91,6 +94,12 @@ private slots:
void on_ActionDisableBackfaceCull_triggered();
void on_ActionDisableAlpha_triggered();
void on_ActionEditLayers_triggered();
void on_ActionSelectObjects_triggered();
void on_ActionTranslate_triggered();
void on_ActionRotate_triggered();
void on_ActionScale_triggered();
void on_ActionIncrementGizmo_triggered();
void on_ActionDecrementGizmo_triggered();
};
#endif // CWORLDEDITOR_H

View File

@ -441,6 +441,9 @@
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QToolBar" name="Toolbar">
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
<property name="windowTitle">
<string>toolBar_2</string>
</property>
@ -591,6 +594,9 @@
</widget>
</widget>
<widget class="QToolBar" name="toolBar">
<property name="contextMenuPolicy">
<enum>Qt::NoContextMenu</enum>
</property>
<property name="windowTitle">
<string>toolBar</string>
</property>
@ -603,6 +609,7 @@
<addaction name="ActionLink"/>
<addaction name="ActionUnlink"/>
<addaction name="separator"/>
<addaction name="ActionSelectObjects"/>
<addaction name="ActionTranslate"/>
<addaction name="ActionRotate"/>
<addaction name="ActionScale"/>
@ -631,6 +638,9 @@
<property name="toolTip">
<string>Translate</string>
</property>
<property name="shortcut">
<string>Ctrl+W</string>
</property>
</action>
<action name="ActionRotate">
<property name="checkable">
@ -646,6 +656,9 @@
<property name="toolTip">
<string>Rotate</string>
</property>
<property name="shortcut">
<string>Ctrl+E</string>
</property>
</action>
<action name="ActionScale">
<property name="checkable">
@ -661,6 +674,9 @@
<property name="toolTip">
<string>Scale</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</action>
<action name="actionOpen_model_viewer">
<property name="text">
@ -871,6 +887,55 @@
<string>Fake Bloom</string>
</property>
</action>
<action name="ActionSelectObjects">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../Icons.qrc">
<normaloff>:/icons/EditorAssets/SelectMode.png</normaloff>:/icons/EditorAssets/SelectMode.png</iconset>
</property>
<property name="text">
<string>Select Objects</string>
</property>
<property name="toolTip">
<string>Select Objects</string>
</property>
<property name="shortcut">
<string>Ctrl+Q</string>
</property>
</action>
<action name="ActionIncrementGizmo">
<property name="text">
<string>Increment Gizmo Size</string>
</property>
<property name="toolTip">
<string>Increment Gizmo Size</string>
</property>
<property name="shortcut">
<string>=</string>
</property>
<property name="shortcutContext">
<enum>Qt::WindowShortcut</enum>
</property>
</action>
<action name="ActionDecrementGizmo">
<property name="text">
<string>Decrement Gizmo Size</string>
</property>
<property name="toolTip">
<string>Decrement Gizmo Size</string>
</property>
<property name="shortcut">
<string>-</string>
</property>
<property name="shortcutContext">
<enum>Qt::WindowShortcut</enum>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
@ -879,6 +944,11 @@
<header>CEditorGLWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>WDraggableSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>WDraggableSpinBox.h</header>
</customwidget>
<customwidget>
<class>WModifyTab</class>
<extends>QWidget</extends>
@ -891,11 +961,6 @@
<header>WorldEditor/WInstancesTab.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>WDraggableSpinBox</class>
<extends>QDoubleSpinBox</extends>
<header>WDraggableSpinBox.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>XSpinBox</tabstop>