524 lines
14 KiB
C++
524 lines
14 KiB
C++
/*
|
|
QGL SceneTree
|
|
Ivan Pelipenko peri4ko@yandex.ru
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "scene_tree.h"
|
|
#include "ui_scene_tree.h"
|
|
#include "glcamera.h"
|
|
#include "qglview.h"
|
|
#include <QTreeWidget>
|
|
#include <QScrollBar>
|
|
#include <QAction>
|
|
#include <QEvent>
|
|
|
|
enum Column {
|
|
cName,
|
|
cVis,
|
|
cMaterial
|
|
};
|
|
enum ItemRole {
|
|
irObject = Qt::UserRole,
|
|
irType = Qt::UserRole + 1,
|
|
};
|
|
|
|
enum ObjectType {
|
|
otNode = 1,
|
|
otMesh = 2,
|
|
otLight = 4,
|
|
otCamera = 8,
|
|
};
|
|
|
|
|
|
QAction * newSeparator() {
|
|
QAction * s = new QAction();
|
|
s->setSeparator(true);
|
|
return s;
|
|
}
|
|
|
|
|
|
SceneTree::SceneTree(QWidget * parent): QWidget(parent) {
|
|
ui = new Ui::SceneTree();
|
|
ui->setupUi(this);
|
|
ui->treeObjects->header()->setSectionResizeMode(cVis, QHeaderView::ResizeToContents);
|
|
ui->treeObjects->header()->setSectionsMovable(false);
|
|
ui->treeObjects->header()->swapSections(cName, cVis);
|
|
ui->treeObjects->setItemDelegateForColumn(1, new NoEditDelegate(this));
|
|
ui->treeObjects->setItemDelegateForColumn(2, new NoEditDelegate(this));
|
|
icon_empty = QIcon(":/icons/type-empty.png");
|
|
icon_geo = QIcon(":/icons/type-geo.png");
|
|
icon_camera = QIcon(":/icons/type-camera.png");
|
|
icon_light = QIcon(":/icons/type-light.png");
|
|
icon_vis[0] = QIcon(":/icons/layer-visible-off.png");
|
|
icon_vis[1] = QIcon(":/icons/layer-visible-on.png");
|
|
ui->treeObjects->addActions(actionsSelection());
|
|
ui->treeObjects->addAction (newSeparator());
|
|
ui->treeObjects->addActions(actionsAdd());
|
|
ui->buttonFilter->addActions(QList<QAction*>() << ui->actionFilter_node << ui->actionFilter_mesh << ui->actionFilter_light << ui->actionFilter_camera);
|
|
view = 0;
|
|
hidden_by_filter = obj_count = 0;
|
|
block_tree = false;
|
|
connect(ui->treeObjects->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(treeObjects_selectionCnahged()));
|
|
}
|
|
|
|
|
|
SceneTree::~SceneTree() {
|
|
delete ui;
|
|
}
|
|
|
|
|
|
void SceneTree::assignQGLView(QGLView * v) {
|
|
view = v;
|
|
objectsTreeChanged();
|
|
if (!view) return;
|
|
connect(view, SIGNAL(selectionChanged()), this, SLOT(selectionChanged()));
|
|
connect(view, SIGNAL(materialsChanged()), this, SLOT(materialsChanged()));
|
|
connect(view->scene(), SIGNAL(treeChanged()), this, SLOT(objectsTreeChanged()));
|
|
connect(view->scene(), SIGNAL(__objectDeleted(ObjectBase*)), this, SLOT(__objectDeleted(ObjectBase*)));
|
|
view->setContextActions(actionsSelection());
|
|
checkActions();
|
|
}
|
|
|
|
|
|
QList<QAction *> SceneTree::actionsAdd() {
|
|
QList<QAction *> ret;
|
|
ret << ui->actionAdd_node << ui->actionAdd_light << ui->actionAdd_camera;
|
|
return ret;
|
|
}
|
|
|
|
|
|
QList<QAction *> SceneTree::actionsSelection() {
|
|
QList<QAction *> ret;
|
|
ret << ui->actionFocus << newSeparator()
|
|
<< ui->actionGroup << ui->actionClone << newSeparator()
|
|
<< ui->actionSelect_parent << ui->actionSelect_by_mesh << ui->actionSelect_by_material << newSeparator()
|
|
<< ui->actionTransfer_transform_to_children << newSeparator()
|
|
<< ui->actionActive_camera << ui->actionDefault_camera << newSeparator()
|
|
<< ui->actionRemove;
|
|
return ret;
|
|
}
|
|
|
|
|
|
void SceneTree::expandItems() {
|
|
ui->treeObjects->expandAll();
|
|
}
|
|
|
|
|
|
void SceneTree::changeEvent(QEvent * e) {
|
|
QWidget::changeEvent(e);
|
|
if (e->type() == QEvent::LanguageChange) {
|
|
ui->retranslateUi(this);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
void SceneTree::rememberExpanded(QTreeWidgetItem * ti) {
|
|
for (int i = 0; i < ti->childCount(); ++i) {
|
|
QTreeWidgetItem * ci = ti->child(i);
|
|
if (ci->isExpanded())
|
|
expanded_ << itemObject(ci);
|
|
rememberExpanded(ci);
|
|
}
|
|
}
|
|
|
|
|
|
void SceneTree::restoreExpanded(QTreeWidgetItem * ti) {
|
|
for (int i = 0; i < ti->childCount(); ++i) {
|
|
QTreeWidgetItem * ci = ti->child(i);
|
|
ci->setExpanded(expanded_.contains(itemObject(ci)));
|
|
restoreExpanded(ci);
|
|
}
|
|
}
|
|
|
|
|
|
void SceneTree::makeObjetTree(ObjectBase * o, QTreeWidgetItem * ti) {
|
|
++obj_count;
|
|
for (int i = 0; i < o->childCount(); ++i) {
|
|
ObjectBase * co = o->child(i);
|
|
QTreeWidgetItem * ci = new QTreeWidgetItem(ti);
|
|
ci->setText(cName, co->name());
|
|
ci->setCheckState(cVis, co->isVisible() ? Qt::Checked : Qt::Unchecked);
|
|
ci->setIcon(cVis, icon_vis[co->isVisible(true)]);
|
|
if (co->material())
|
|
ci->setText(cMaterial, co->material()->name);
|
|
ci->setFlags(ci->flags() | Qt::ItemIsEditable);
|
|
ObjectType t = otNode;
|
|
switch (co->type()) {
|
|
case ObjectBase::glMesh:
|
|
if (co->mesh()) {
|
|
t = otMesh;
|
|
ci->setIcon(cName, icon_geo);
|
|
geo_items << ci;
|
|
} else {
|
|
ci->setIcon(cName, icon_empty);
|
|
}
|
|
break;
|
|
case ObjectBase::glLight:
|
|
t = otLight;
|
|
ci->setIcon(cName, icon_light);
|
|
break;
|
|
case ObjectBase::glCamera:
|
|
t = otCamera;
|
|
ci->setIcon(cName, icon_camera);
|
|
ci->setText(cVis, (co == view->camera()) ? "*" : "");
|
|
cam_items << ci;
|
|
break;
|
|
default: break;
|
|
}
|
|
ci->setData(cName, irObject, quintptr(co));
|
|
ci->setData(cName, irType, int(t));
|
|
ci->setSelected(co->isSelected());
|
|
makeObjetTree(co, ci);
|
|
}
|
|
}
|
|
|
|
|
|
ObjectBase * SceneTree::itemObject(QTreeWidgetItem * item) const {
|
|
if (!item) return 0;
|
|
return (ObjectBase*)(item->data(cName, irObject).toULongLong());
|
|
}
|
|
|
|
|
|
int SceneTree::itemType(QTreeWidgetItem * item) const {
|
|
if (!item) return otNode;
|
|
return item->data(cName, irType).toInt();
|
|
}
|
|
|
|
|
|
void SceneTree::selectionChanged() {
|
|
if (block_tree) return;
|
|
block_tree = true;
|
|
QList<QTreeWidgetItem*> il = ui->treeObjects->findItems("", Qt::MatchContains | Qt::MatchRecursive);
|
|
const ObjectBase * fo = 0;
|
|
if (view->selectedObjects().size() == 1)
|
|
fo = view->selectedObject();
|
|
foreach (QTreeWidgetItem * i, il) {
|
|
ObjectBase * o = itemObject(i);
|
|
i->setSelected(o->isSelected());
|
|
if (fo && (fo == o)) {
|
|
ui->treeObjects->setCurrentItem(i);
|
|
}
|
|
}
|
|
block_tree = false;
|
|
checkActions();
|
|
}
|
|
|
|
|
|
void SceneTree::materialsChanged() {
|
|
foreach (QTreeWidgetItem * i, geo_items) {
|
|
ObjectBase * o = itemObject(i);
|
|
if (!o) continue;
|
|
if (o->material())
|
|
i->setText(cMaterial, o->material()->name);
|
|
}
|
|
}
|
|
|
|
|
|
void SceneTree::cameraChanged() {
|
|
if (!view) return;
|
|
foreach (QTreeWidgetItem * i, cam_items) {
|
|
ObjectBase * o = itemObject(i);
|
|
if (!o) continue;
|
|
i->setText(cVis, (o == view->camera()) ? "*" : "");
|
|
}
|
|
}
|
|
|
|
|
|
bool SceneTree::filterTree(QTreeWidgetItem * ti, const QString & filter, int types) {
|
|
bool ret = false;
|
|
for (int i = 0; i < ti->childCount(); ++i) {
|
|
QTreeWidgetItem * ci = ti->child(i);
|
|
QString cit = ci->text(cName);
|
|
int t = itemType(ci);
|
|
if (ci->childCount() > 0) {
|
|
if (!filterTree(ci, filter, types)) {
|
|
ci->setHidden(true);
|
|
++hidden_by_filter;
|
|
continue;
|
|
}
|
|
ci->setHidden(false);
|
|
ret = true;
|
|
} else {
|
|
bool f = false;
|
|
if (filter.isEmpty()) {
|
|
f = true;
|
|
} else {
|
|
f = f || cit.contains(filter, Qt::CaseInsensitive);
|
|
}
|
|
if ((types & t) != t)
|
|
f = false;
|
|
ci->setHidden(!f);
|
|
if (f) ret = true;
|
|
else ++hidden_by_filter;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
void SceneTree::checkActions() {
|
|
bool has_1 = false, has_m = false;
|
|
bool has_cam = false;
|
|
bool has_mesh = false;
|
|
bool is_def_cam = false;
|
|
if (view) {
|
|
is_def_cam = view->isDefaultCamera();
|
|
ObjectBaseList slo = view->selectedObjects();
|
|
has_1 = !slo.isEmpty();
|
|
has_m = slo.size() > 1;
|
|
for (ObjectBase * o : slo) {
|
|
if (o->type() == ObjectBase::glCamera) has_cam = (slo.size() == 1);
|
|
if (o->type() == ObjectBase::glMesh) has_mesh = true;
|
|
}
|
|
}
|
|
|
|
ui->actionFocus ->setEnabled(has_mesh);
|
|
ui->actionRemove->setEnabled(has_1);
|
|
ui->actionClone ->setEnabled(has_1);
|
|
ui->actionGroup->setEnabled(has_m);
|
|
ui->actionTransfer_transform_to_children->setEnabled(has_1);
|
|
ui->actionSelect_parent->setEnabled(has_1);
|
|
ui->actionSelect_by_mesh->setEnabled(has_mesh);
|
|
ui->actionSelect_by_material->setEnabled(has_mesh);
|
|
ui->actionActive_camera->setEnabled(has_cam);
|
|
ui->actionDefault_camera->setEnabled(!is_def_cam);
|
|
}
|
|
|
|
|
|
void SceneTree::treeObjects_selectionCnahged() {
|
|
if (block_tree || !view) return;
|
|
block_tree = true;
|
|
view->scene()->clearSelection();
|
|
ObjectBaseList sol;
|
|
QList<QTreeWidgetItem*> til = ui->treeObjects->selectedItems();
|
|
foreach (QTreeWidgetItem * i, til)
|
|
sol << itemObject(i);
|
|
view->scene()->selectObjects(sol);
|
|
block_tree = false;
|
|
checkActions();
|
|
}
|
|
|
|
|
|
void SceneTree::filter() {
|
|
int types = 0;
|
|
if (ui->actionFilter_node ->isChecked()) types |= otNode ;
|
|
if (ui->actionFilter_mesh ->isChecked()) types |= otMesh ;
|
|
if (ui->actionFilter_light ->isChecked()) types |= otLight ;
|
|
if (ui->actionFilter_camera->isChecked()) types |= otCamera;
|
|
if (types == 0) types = 0xFF;
|
|
hidden_by_filter = 0;
|
|
filterTree(ui->treeObjects->invisibleRootItem(), ui->lineFilter->text(), types);
|
|
ui->treeObjects->invisibleRootItem()->setHidden(false);
|
|
ui->labelCounts->setText(tr("%1 objects, %2 hide by filter").arg(obj_count).arg(hidden_by_filter));
|
|
}
|
|
|
|
|
|
void SceneTree::__objectDeleted(ObjectBase * o) {
|
|
for (int i = 0; i < geo_items.size(); ++i)
|
|
if (itemObject(geo_items[i]) == o) {
|
|
geo_items.removeAt(i);
|
|
--i;
|
|
}
|
|
for (int i = 0; i < cam_items.size(); ++i)
|
|
if (itemObject(cam_items[i]) == o) {
|
|
cam_items.removeAt(i);
|
|
--i;
|
|
}
|
|
}
|
|
|
|
|
|
void SceneTree::on_treeObjects_itemChanged(QTreeWidgetItem * item, int column) {
|
|
if (block_tree) return;
|
|
if (column == cName) {
|
|
ObjectBase * o = itemObject(item);
|
|
if (o) o->setName(item->text(cName));
|
|
}
|
|
if (column == cVis) {
|
|
bool vis = item->checkState(cVis) == Qt::Checked;
|
|
QList<QTreeWidgetItem*> til = ui->treeObjects->selectedItems();
|
|
if (!til.contains(item)) {
|
|
til.clear();
|
|
til << item;
|
|
}
|
|
foreach (QTreeWidgetItem * ti, til) {
|
|
//itemObject(ti)->setVisible(vis);
|
|
itemObject(ti)->setVisible(vis);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void SceneTree::on_treeObjects_itemMoved(QTreeWidgetItem * item, QTreeWidgetItem * new_parent) {
|
|
ObjectBase * co = itemObject(item);
|
|
if (!co->hasParent()) return;
|
|
//co->parent()->removeChild(co);
|
|
if (new_parent == ui->treeObjects->invisibleRootItem()) {
|
|
view->scene()->rootObject()->addChild(co);
|
|
} else {
|
|
ObjectBase * po = itemObject(new_parent);
|
|
if (po)
|
|
po->addChild(co);
|
|
}
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionAdd_node_triggered() {
|
|
if (!view) return;
|
|
ObjectBase * no = new ObjectBase();
|
|
view->scene()->addObject(no);
|
|
view->scene()->selectObject(no);
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionAdd_light_triggered() {
|
|
if (!view) return;
|
|
ObjectBase * no = new Light();
|
|
view->scene()->addObject(no);
|
|
view->scene()->selectObject(no);
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionAdd_camera_triggered() {
|
|
if (!view) return;
|
|
ObjectBase * no = new Camera();
|
|
view->scene()->addObject(no);
|
|
view->scene()->selectObject(no);
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionClone_triggered() {
|
|
if (!view) return;
|
|
QList<QTreeWidgetItem*> sil = ui->treeObjects->selectedItems();
|
|
ObjectBaseList col;
|
|
foreach (QTreeWidgetItem * i, sil) {
|
|
ObjectBase * o = itemObject(i);
|
|
if (!o) continue;
|
|
ObjectBase * no = o->clone();
|
|
o->parent()->addChild(no);
|
|
col << no;
|
|
}
|
|
view->scene()->selectObjects(col);
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionGroup_triggered() {
|
|
if (!view) return;
|
|
ObjectBaseList sol = view->scene()->selectedObjects(true);
|
|
ObjectBase * cp = sol[0]->parent();
|
|
ObjectBase * nr = new ObjectBase();
|
|
cp->addChild(nr);
|
|
foreach (ObjectBase * o, sol)
|
|
nr->addChild(o);
|
|
view->scene()->selectObject(nr);
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionTransfer_transform_to_children_triggered() {
|
|
if (!view) return;
|
|
ObjectBaseList sol = view->scene()->selectedObjects(true);
|
|
foreach (ObjectBase * o, sol)
|
|
o->transferTransformToChildren();
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionActive_camera_triggered() {
|
|
if (!view) return;
|
|
ObjectBase * o = view->scene()->selectedObject();
|
|
if (!o) return;
|
|
if (o->type() != ObjectBase::glCamera) return;
|
|
view->setCamera((Camera *)o);
|
|
cameraChanged();
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionDefault_camera_triggered() {
|
|
if (!view) return;
|
|
view->setDefaultCamera();
|
|
cameraChanged();
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionSelect_parent_triggered() {
|
|
if (!view) return;
|
|
ObjectBaseList sol = view->scene()->selectedObjects(true);
|
|
QSet<ObjectBase*> nsl;
|
|
foreach (ObjectBase * o, sol) {
|
|
ObjectBase * po = o->parent();
|
|
if (po != view->scene()->rootObject())
|
|
o = po;
|
|
nsl << o;
|
|
}
|
|
view->scene()->selectObjects(nsl.values());
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionSelect_by_mesh_triggered() {
|
|
view->scene()->selectObjectsByMesh();
|
|
}
|
|
|
|
|
|
void SceneTree::on_actionSelect_by_material_triggered() {
|
|
view->scene()->selectObjectsByMaterial();
|
|
}
|
|
|
|
|
|
void SceneTree::removeObjects() {
|
|
if (!view) return;
|
|
QList<QTreeWidgetItem*> sil = ui->treeObjects->selectedItems();
|
|
foreach (QTreeWidgetItem * i, sil) {
|
|
ObjectBase * o = itemObject(i);
|
|
if (o->isSelected(true)) view->scene()->clearSelection();
|
|
if (o) delete o;
|
|
}
|
|
qDeleteAll(sil);
|
|
}
|
|
|
|
|
|
void SceneTree::focusObjects() {
|
|
if (!view) return;
|
|
if (!view->camera()) return;
|
|
Box3D bb;
|
|
ObjectBaseList ol = view->selectedObjects();
|
|
foreach (ObjectBase * o, ol) {
|
|
o->calculateBoundingBox();
|
|
bb |= o->boundingBox();
|
|
}
|
|
view->focusOn(bb);
|
|
}
|
|
|
|
|
|
void SceneTree::objectsTreeChanged() {
|
|
int vpos = ui->treeObjects->verticalScrollBar()->value();
|
|
expanded_.clear();
|
|
geo_items.clear();
|
|
cam_items.clear();
|
|
rememberExpanded(ui->treeObjects->invisibleRootItem());
|
|
block_tree = true;
|
|
ui->treeObjects->clear();
|
|
block_tree = false;
|
|
if (!view) return;
|
|
block_tree = true;
|
|
obj_count = 0;
|
|
makeObjetTree(view->scene()->rootObject(), ui->treeObjects->invisibleRootItem());
|
|
--obj_count;
|
|
restoreExpanded(ui->treeObjects->invisibleRootItem());
|
|
block_tree = false;
|
|
filter();
|
|
QApplication::processEvents();
|
|
ui->treeObjects->verticalScrollBar()->setValue(vpos);
|
|
}
|