netradiant-custom/radiant/entitylist.cpp

351 lines
11 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 "entitylist.h"
#include "iselection.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTreeView>
#include <QCheckBox>
#include <QLineEdit>
#include <QHeaderView>
#include <QApplication>
#include <QAction>
#include <QKeyEvent>
#include "string/string.h"
#include "scenelib.h"
#include "nameable.h"
#include "signal/isignal.h"
#include "generic/object.h"
#include "gtkutil/widget.h"
#include "gtkutil/idledraw.h"
#include "gtkutil/accelerator.h"
#include "gtkutil/guisettings.h"
#include "treemodel.h"
#include "mainframe.h"
void RedrawEntityList();
typedef FreeCaller<RedrawEntityList> RedrawEntityListCaller;
class EntityList
{
public:
enum EDirty
{
eDefault,
eSelection,
eInsertRemove,
};
EDirty m_dirty;
IdleDraw m_idleDraw;
QWidget* m_window;
QCheckBox* m_check;
QTreeView* m_tree_view;
QAbstractItemModel* m_tree_model;
bool m_selection_disabled;
bool m_search_from_start;
EntityList() :
m_dirty( EntityList::eDefault ),
m_idleDraw( RedrawEntityListCaller() ),
m_window( 0 ),
m_selection_disabled( false ),
m_search_from_start( false ){
}
bool visible() const {
return m_window->isVisible();
}
};
namespace
{
EntityList* g_EntityList;
inline EntityList& getEntityList(){
ASSERT_NOTNULL( g_EntityList );
return *g_EntityList;
}
}
void entitylist_focusSelected( bool checked ){
if( checked )
FocusAllViews();
}
template<typename Functor>
void item_model_foreach( Functor f, QAbstractItemModel* model, QModelIndex parent = QModelIndex() )
{
if ( !parent.isValid() )
parent = model->index( 0, 0, QModelIndex() );
const int numRows = model->rowCount( parent );
for ( int i = 0; i < numRows; ++i )
item_model_foreach( f, model, model->index( i, 0, parent ) );
f( parent );
}
void EntityList_UpdateSelection( QAbstractItemModel* model, QTreeView* view ){
item_model_foreach( [view]( QModelIndex &index ){
scene::Instance* instance = static_cast<scene::Instance*>( index.data( c_ItemDataRole_Instance ).value<void*>() );
if ( Selectable* selectable = Instance_getSelectable( *instance ) ) {
view->selectionModel()->select( index, selectable->isSelected()
? QItemSelectionModel::SelectionFlag::Select
: QItemSelectionModel::SelectionFlag::Deselect );
}
}, model );
}
void RedrawEntityList(){
switch ( getEntityList().m_dirty )
{
case EntityList::eInsertRemove:
case EntityList::eSelection:
EntityList_UpdateSelection( getEntityList().m_tree_model, getEntityList().m_tree_view );
default:
break;
}
getEntityList().m_dirty = EntityList::eDefault;
}
void entitylist_queue_draw(){
getEntityList().m_idleDraw.queueDraw();
}
void EntityList_SelectionUpdate(){
if( getEntityList().visible() ){
// deselect tree items on deseletion in the scene for some degree of consistency
if( getEntityList().m_tree_view->selectionModel()->hasSelection() ){
QTimer::singleShot( 0, [](){
for( const auto& index : getEntityList().m_tree_view->selectionModel()->selectedRows( 0 ) ){
scene::Instance* instance = static_cast<scene::Instance*>( index.data( c_ItemDataRole_Instance ).value<void*>() );
if ( Selectable* selectable = Instance_getSelectable( *instance ) )
if( !selectable->isSelected() )
getEntityList().m_tree_view->selectionModel()->select( index, QItemSelectionModel::SelectionFlag::Deselect );
}
} );
}
getEntityList().m_tree_view->viewport()->update(); // reads in Qt::ItemDataRole::BackgroundRole of visible items
}
return; //. making actual selection is ULTRA SLOW :F thus using Qt::ItemDataRole::BackgroundRole instead
if ( getEntityList().m_selection_disabled ) {
return;
}
if ( getEntityList().m_dirty < EntityList::eSelection ) {
getEntityList().m_dirty = EntityList::eSelection;
}
entitylist_queue_draw();
}
void EntityList_SelectionChanged( const Selectable& selectable ){
EntityList_SelectionUpdate();
}
void EntityList_SetShown( bool shown ){
getEntityList().m_window->setVisible( shown );
if( shown ){ /* expand map's root node for convenience */
auto index = getEntityList().m_tree_model->index( 0, 0 );
if( index.isValid() && !getEntityList().m_tree_view->isExpanded( index ) )
getEntityList().m_tree_view->expand( index );
}
}
void EntityList_toggleShown(){
EntityList_SetShown( !getEntityList().visible() );
}
class Filter_QLineEdit : public QLineEdit
{
protected:
void enterEvent( QEvent *event ) override {
setFocus();
}
void leaveEvent( QEvent *event ) override {
clearFocus();
}
bool event( QEvent *event ) override {
if( event->type() == QEvent::ShortcutOverride ){
QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
if( keyEvent->key() == Qt::Key_Escape ){
clear();
event->accept();
}
}
else if( event->type() == QEvent::Hide ){
if( !text().isEmpty() )
clear(); // workaround: reset filtering, as modification of tree after setRowHidden() is ULTRA SLOW
}
return QLineEdit::event( event );
}
};
void searchEntrySetModeIcon( QAction *action, bool search_from_start ){
action->setIcon( QApplication::style()->standardIcon(
search_from_start
? QStyle::StandardPixmap::SP_CommandLink
: QStyle::StandardPixmap::SP_FileDialogContentsView ) );
}
/* search */
void tree_view_filter( const char *string, bool from_start ){
const auto traverse = [=]( const auto& self, QAbstractItemModel* model, QTreeView *tree, QModelIndex parent = QModelIndex() ) -> bool {
if ( !parent.isValid() )
parent = model->index( 0, 0, QModelIndex() );
const int numRows = model->rowCount( parent );
bool childVisible = false;
for ( int i = 0; i < numRows; ++i )
childVisible |= self( self, model, tree, model->index( i, 0, parent ) );
const bool visible = childVisible || string_empty( string )
|| ( from_start && string_equal_prefix_nocase( parent.data( Qt::ItemDataRole::DisplayRole ).toString().toLatin1().constData(), string ) )
|| ( !from_start && string_in_string_nocase( parent.data( Qt::ItemDataRole::DisplayRole ).toString().toLatin1().constData(), string ) );
tree->setRowHidden( parent.row(), parent.parent(), !visible );
return childVisible || visible;
};
traverse( traverse, getEntityList().m_tree_model, getEntityList().m_tree_view );
}
extern QAbstractItemModel* scene_graph_get_tree_model();
void AttachEntityTreeModel(){
getEntityList().m_tree_model = scene_graph_get_tree_model();
getEntityList().m_tree_view->setModel( getEntityList().m_tree_model );
QObject::connect( getEntityList().m_tree_view->selectionModel(), &QItemSelectionModel::selectionChanged,
[]( const QItemSelection &selected, const QItemSelection &deselected ){
for( const auto& index : deselected.indexes() ){
scene::Instance* instance = static_cast<scene::Instance*>( index.data( c_ItemDataRole_Instance ).value<void*>() );
if( Selectable* selectable = Instance_getSelectable( *instance ) )
selectable->setSelected( false );
}
for( const auto& index : selected.indexes() ){
scene::Instance* instance = static_cast<scene::Instance*>( index.data( c_ItemDataRole_Instance ).value<void*>() );
if( Selectable* selectable = Instance_getSelectable( *instance ) )
selectable->setSelected( true );
}
if( !selected.empty() && getEntityList().m_check->isChecked() )
FocusAllViews();
} );
}
void DetachEntityTreeModel(){
getEntityList().m_tree_model = 0;
getEntityList().m_tree_view->setModel( nullptr );
}
void EntityList_constructWindow( QWidget* main_window ){
ASSERT_MESSAGE( getEntityList().m_window == 0, "error" );
auto window = getEntityList().m_window = new QWidget( main_window, Qt::Dialog | Qt::WindowCloseButtonHint );
window->setWindowTitle( "Entity List" );
g_guiSettings.addWindow( window, "EntityList/geometry", 350, 500 );
{
auto *vbox = new QVBoxLayout( window );
vbox->setContentsMargins( 4, 0, 4, 4 );
{
auto *tree = getEntityList().m_tree_view = new QTreeView;
tree->setHeaderHidden( true );
tree->setEditTriggers( QAbstractItemView::EditTrigger::NoEditTriggers );
tree->setUniformRowHeights( true ); // optimization
tree->setSizeAdjustPolicy( QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents ); // scroll area will inherit column size
tree->header()->setStretchLastSection( false ); // non greedy column sizing; + QHeaderView::ResizeMode::ResizeToContents = no text elision 🤷‍♀️
tree->header()->setSectionResizeMode( QHeaderView::ResizeMode::ResizeToContents );
tree->setSelectionMode( QAbstractItemView::SelectionMode::ExtendedSelection );
vbox->addWidget( tree );
}
{
auto *hbox = new QHBoxLayout;
vbox->addLayout( hbox );
{
auto *check = getEntityList().m_check = new QCheckBox( "AutoFocus on Selection" );
hbox->addWidget( check );
QObject::connect( check, &QAbstractButton::clicked, entitylist_focusSelected );
}
{ //search entry
QLineEdit *entry = new Filter_QLineEdit;
hbox->addWidget( entry );
entry->setClearButtonEnabled( true );
entry->setFocusPolicy( Qt::FocusPolicy::ClickFocus );
QAction *action = entry->addAction( QApplication::style()->standardIcon( QStyle::StandardPixmap::SP_CommandLink ), QLineEdit::LeadingPosition );
searchEntrySetModeIcon( action, getEntityList().m_search_from_start );
action->setToolTip( "toggle match mode ( start / any position )" );
QObject::connect( entry, &QLineEdit::textChanged, []( const QString& text ){
tree_view_filter( text.toLatin1().constData(), getEntityList().m_search_from_start );
} );
QObject::connect( action, &QAction::triggered, [action, entry](){
getEntityList().m_search_from_start ^= 1;
searchEntrySetModeIcon( action, getEntityList().m_search_from_start );
entry->textChanged( entry->text() ); // trigger filtering update
} );
}
}
}
AttachEntityTreeModel();
}
void EntityList_destroyWindow(){
DetachEntityTreeModel();
delete getEntityList().m_window;
}
#include "preferencesystem.h"
#include "stringio.h"
void EntityList_Construct(){
g_EntityList = new EntityList;
GlobalPreferenceSystem().registerPreference( "EntListSearchFromStart", BoolImportStringCaller( getEntityList().m_search_from_start ), BoolExportStringCaller( getEntityList().m_search_from_start ) );
typedef FreeCaller1<const Selectable&, EntityList_SelectionChanged> EntityListSelectionChangedCaller;
GlobalSelectionSystem().addSelectionChangeCallback( EntityListSelectionChangedCaller() );
}
void EntityList_Destroy(){
delete g_EntityList;
}