337 lines
10 KiB
C++
337 lines
10 KiB
C++
/*
|
|
Copyright (C) 2001-2006, William Joseph.
|
|
All Rights Reserved.
|
|
|
|
This file is part of GtkRadiant.
|
|
|
|
GtkRadiant is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
GtkRadiant 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 General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GtkRadiant; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "treemodel.h"
|
|
|
|
#include "debugging/debugging.h"
|
|
|
|
#include <map>
|
|
|
|
#include <QAbstractItemModel>
|
|
#include <QBrush>
|
|
#include <QApplication>
|
|
#include <QPalette>
|
|
|
|
#include "iscenegraph.h"
|
|
#include "nameable.h"
|
|
|
|
#include "generic/callback.h"
|
|
#include "scenelib.h"
|
|
#include "string/string.h"
|
|
#include "generic/reference.h"
|
|
|
|
|
|
inline Nameable* Node_getNameable( scene::Node& node ){
|
|
return NodeTypeCast<Nameable>::cast( node );
|
|
}
|
|
|
|
const char* node_get_name( scene::Node& node ){
|
|
Nameable* nameable = Node_getNameable( node );
|
|
return ( nameable != 0 )
|
|
? nameable->name()
|
|
: "node";
|
|
}
|
|
|
|
const char* node_get_name_safe( scene::Node& node ){
|
|
volatile intptr_t n = (intptr_t)&node; // see the comment on line 650
|
|
if ( n == 0 ) {
|
|
return "";
|
|
}
|
|
return node_get_name( node );
|
|
}
|
|
|
|
void node_attach_name_changed_callback( scene::Node& node, const NameCallback& callback ){
|
|
volatile intptr_t n = (intptr_t)&node; // see the comment on line 650
|
|
if ( n != 0 ) {
|
|
Nameable* nameable = Node_getNameable( node );
|
|
if ( nameable != 0 ) {
|
|
nameable->attach( callback );
|
|
}
|
|
}
|
|
}
|
|
void node_detach_name_changed_callback( scene::Node& node, const NameCallback& callback ){
|
|
volatile intptr_t n = (intptr_t)&node; // see the comment on line 650
|
|
if ( n != 0 ) {
|
|
Nameable* nameable = Node_getNameable( node );
|
|
if ( nameable != 0 ) {
|
|
nameable->detach( callback );
|
|
}
|
|
}
|
|
}
|
|
|
|
void graph_tree_model_set_name( const scene::Instance& instance, const char* name );
|
|
|
|
|
|
#include "iselection.h"
|
|
|
|
namespace
|
|
{
|
|
scene::Node* g_null_node = 0;
|
|
}
|
|
|
|
class NullInstance : public scene::Instance
|
|
{
|
|
public:
|
|
NullInstance() : scene::Instance( scene::Path( makeReference( *g_null_node ) ), 0, 0, Static<InstanceTypeCastTable>::instance() ){
|
|
}
|
|
};
|
|
|
|
namespace
|
|
{
|
|
NullInstance g_null_instance;
|
|
}
|
|
|
|
|
|
class GraphTreeNode
|
|
{
|
|
private:
|
|
struct Compare{
|
|
bool operator()( const GraphTreeNode *one, const GraphTreeNode *other ) const {
|
|
const int n = string_compare( one->m_name.c_str(), other->m_name.c_str() );
|
|
return n != 0? n < 0 : one->m_node < other->m_node;
|
|
}
|
|
};
|
|
typedef std::vector<GraphTreeNode*> ChildNodes;
|
|
ChildNodes m_childnodes;
|
|
public:
|
|
Reference<scene::Instance> m_instance;
|
|
GraphTreeNode* m_parent;
|
|
CopiedString m_name;
|
|
scene::Node* m_node;
|
|
|
|
typedef ChildNodes::iterator iterator;
|
|
|
|
GraphTreeNode( scene::Instance& instance, const char *name, scene::Node* node ) :
|
|
m_instance( instance ), m_parent( 0 ), m_name( name ), m_node( node ){
|
|
}
|
|
~GraphTreeNode(){
|
|
ASSERT_MESSAGE( empty(), "GraphTreeNode::~GraphTreeNode: memory leak" );
|
|
}
|
|
GraphTreeNode( GraphTreeNode&& ) noexcept = delete;
|
|
|
|
iterator begin(){
|
|
return m_childnodes.begin();
|
|
}
|
|
iterator end(){
|
|
return m_childnodes.end();
|
|
}
|
|
|
|
std::size_t size() const {
|
|
return m_childnodes.size();
|
|
}
|
|
bool empty() const {
|
|
return m_childnodes.empty();
|
|
}
|
|
// may not be called on the root node!
|
|
int getIndex() const {
|
|
return getIndex( m_parent->find( m_name.c_str(), m_node ) );
|
|
}
|
|
// iterator in parent's children list!
|
|
int getIndex( iterator it ) const {
|
|
return std::distance( m_parent->begin(), it );
|
|
}
|
|
GraphTreeNode *child( int row ) const {
|
|
if( row < 0 || size_t( row ) >= m_childnodes.size() )
|
|
return nullptr;
|
|
return m_childnodes[row];
|
|
}
|
|
|
|
iterator insert( GraphTreeNode* value ){
|
|
value->m_parent = this;
|
|
return m_childnodes.insert( std::lower_bound( begin(), end(), value, Compare() ), value );
|
|
//. ASSERT_MESSAGE( inserted, "GraphTreeNode::insert: already added" );
|
|
}
|
|
void erase( iterator i ){
|
|
m_childnodes.erase( i );
|
|
}
|
|
iterator find( const char *name, const scene::Node* node ){
|
|
return std::lower_bound( begin(), end(), name, [node]( const GraphTreeNode *other, const char *name ){
|
|
const int n = string_compare( other->m_name.c_str(), name );
|
|
return n != 0? n < 0 : other->m_node < node;
|
|
} );
|
|
}
|
|
// find index of future insertion
|
|
int lower_bound( const char *name, const scene::Node* node ){
|
|
return std::distance( begin(), find( name, node ) );
|
|
}
|
|
};
|
|
|
|
|
|
class GraphTreeModel : public QAbstractItemModel
|
|
{
|
|
public:
|
|
GraphTreeModel(){
|
|
rootItem = new GraphTreeNode( g_null_instance, "", nullptr );
|
|
}
|
|
~GraphTreeModel(){
|
|
delete rootItem;
|
|
}
|
|
QVariant data( const QModelIndex &index, int role ) const override {
|
|
if ( index.isValid() ){
|
|
GraphTreeNode *item = getNode( index );
|
|
if ( role == Qt::ItemDataRole::DisplayRole )
|
|
return item->m_name.c_str();
|
|
else if( role == c_ItemDataRole_Instance )
|
|
return QVariant::fromValue( static_cast<void*>( item->m_instance.get_pointer() ) );
|
|
else if( role == c_ItemDataRole_Node )
|
|
return QVariant::fromValue( static_cast<void*>( item->m_node ) );
|
|
else if( role == Qt::ItemDataRole::BackgroundRole ){
|
|
const QColor color = item->m_instance.get().isSelected()
|
|
? qApp->palette().color( QPalette::ColorRole::Highlight ).darker( 200 )
|
|
: item->m_instance.get().childSelected()
|
|
? qApp->palette().color( QPalette::ColorRole::Highlight ).darker( 300 )
|
|
: qApp->palette().color( QPalette::ColorRole::Base );
|
|
return QBrush( color );
|
|
}
|
|
}
|
|
return QVariant();
|
|
|
|
}
|
|
Qt::ItemFlags flags( const QModelIndex &index ) const override {
|
|
if ( !index.isValid() )
|
|
return Qt::NoItemFlags;
|
|
return QAbstractItemModel::flags( index );
|
|
}
|
|
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override {
|
|
return QVariant();
|
|
}
|
|
GraphTreeNode* getNode( const QModelIndex &index ) const {
|
|
return static_cast<GraphTreeNode*>( index.internalPointer() );
|
|
}
|
|
std::pair<GraphTreeNode*, QModelIndex> findParents( const scene::Path& path ) const {
|
|
GraphTreeNode* parent = rootItem;
|
|
QModelIndex index;
|
|
for ( scene::Path::const_iterator i = path.begin(); i != path.end() - 1; ++i )
|
|
{
|
|
GraphTreeNode::iterator child = parent->find( node_get_name_safe( ( *i ).get() ), ( *i ).get_pointer() );
|
|
ASSERT_MESSAGE( child != parent->end(), "ERROR" );
|
|
parent = *child;
|
|
index = this->index( ( *child )->getIndex( child ), 0, index );
|
|
ASSERT_MESSAGE( index.isValid(), "index.isValid()" );
|
|
}
|
|
return { parent, index };
|
|
}
|
|
QModelIndex index( int row, int column, const QModelIndex &parent ) const override {
|
|
if ( !hasIndex( row, column, parent ) )
|
|
return QModelIndex();
|
|
|
|
GraphTreeNode *parentItem = parent.isValid()? getNode( parent ) : rootItem;
|
|
GraphTreeNode *childItem = parentItem->child( row );
|
|
if ( childItem )
|
|
return createIndex( row, column, childItem );
|
|
return QModelIndex();
|
|
}
|
|
QModelIndex parent( const QModelIndex &index ) const override {
|
|
if ( !index.isValid() )
|
|
return QModelIndex();
|
|
|
|
GraphTreeNode *childItem = getNode( index );
|
|
GraphTreeNode *parentItem = childItem->m_parent;
|
|
|
|
if ( parentItem == rootItem )
|
|
return QModelIndex();
|
|
|
|
return createIndex( parentItem->getIndex(), 0, parentItem );
|
|
}
|
|
int rowCount( const QModelIndex &parent ) const override {
|
|
if ( parent.column() > 0 )
|
|
return 0;
|
|
|
|
GraphTreeNode *parentItem = parent.isValid()? getNode( parent ) : rootItem;
|
|
return parentItem->size();
|
|
}
|
|
int columnCount( const QModelIndex &parent = QModelIndex() ) const override {
|
|
return 1;
|
|
}
|
|
void insert( const scene::Instance& instance ) {
|
|
auto [parent, index] = findParents( instance.path() );
|
|
|
|
const int n = parent->lower_bound( node_get_name_safe( instance.path().top().get() ), instance.path().top().get_pointer() );
|
|
|
|
beginInsertRows( index, n, n );
|
|
parent->insert( new GraphTreeNode( const_cast<scene::Instance&>( instance ), node_get_name_safe( instance.path().top().get() ), instance.path().top().get_pointer() ) );
|
|
endInsertRows();
|
|
|
|
node_attach_name_changed_callback( instance.path().top(), ConstReferenceCaller1<scene::Instance, const char*, graph_tree_model_set_name>( instance ) );
|
|
}
|
|
void remove( const scene::Instance& instance ) {
|
|
node_detach_name_changed_callback( instance.path().top(), ConstReferenceCaller1<scene::Instance, const char*, graph_tree_model_set_name>( instance ) );
|
|
|
|
auto [parent, index] = findParents( instance.path() );
|
|
|
|
GraphTreeNode::iterator i = parent->find( node_get_name_safe( instance.path().top().get() ), instance.path().top().get_pointer() );
|
|
|
|
const int n = ( *i )->getIndex( i );
|
|
|
|
beginRemoveRows( index, n, n );
|
|
GraphTreeNode* node( *i );
|
|
parent->erase( i );
|
|
delete node;
|
|
endRemoveRows();
|
|
}
|
|
void rename( const scene::Instance& instance, const char* name ){
|
|
auto [parent, index] = findParents( instance.path() );
|
|
|
|
GraphTreeNode::iterator i = parent->find( node_get_name_safe( instance.path().top().get() ), instance.path().top().get_pointer() );
|
|
|
|
const int n = ( *i )->getIndex( i );
|
|
const int n2 = parent->lower_bound( name, instance.path().top().get_pointer() );
|
|
|
|
if( ( n2 - n ) == 0 || ( n2 - n ) == 1 ){ //rename
|
|
( *i )->m_name = name;
|
|
dataChanged( this->index( n, 0, index ), this->index( n, 0, index ), { Qt::ItemDataRole::DisplayRole } );
|
|
}
|
|
else{ // move
|
|
beginMoveRows( index, n, n, index, n2 );
|
|
GraphTreeNode* node( *i );
|
|
parent->erase( i );
|
|
node->m_name = name;
|
|
parent->insert( node );
|
|
endMoveRows();
|
|
}
|
|
}
|
|
private:
|
|
GraphTreeNode *rootItem;
|
|
};
|
|
|
|
GraphTreeModel* graph_tree_model_new(){
|
|
return new GraphTreeModel;
|
|
}
|
|
|
|
void graph_tree_model_delete( GraphTreeModel* model ){
|
|
delete model;
|
|
}
|
|
|
|
void graph_tree_model_insert( GraphTreeModel* model, const scene::Instance& instance ){
|
|
model->insert( instance );
|
|
}
|
|
|
|
void graph_tree_model_erase( GraphTreeModel* model, const scene::Instance& instance ){
|
|
model->remove( instance );
|
|
}
|
|
|
|
|
|
GraphTreeModel* scene_graph_get_tree_model(); // temp hack
|
|
void graph_tree_model_set_name( const scene::Instance& instance, const char* name ){
|
|
scene_graph_get_tree_model()->rename( instance, name );
|
|
}
|
|
|