* build menu.customize.build variables: are editable

This commit is contained in:
Garux
2025-08-30 16:21:40 +05:00
parent c2cb339c25
commit 581dd79acb
4 changed files with 238 additions and 100 deletions
+1
View File
@@ -1132,6 +1132,7 @@ misc...
* shader editor: make Copy and SelectAll binds work in read only mode
* build menu.customize: drag and drop items; ctrl + drag = copy
* build menu.customize: remove command line item when set empty
* build menu.customize.build variables: are editable
+235 -96
View File
@@ -27,6 +27,7 @@
#include "stream/stringstream.h"
#include "versionlib.h"
#include "qe3.h"
#include "mainframe.h"
typedef std::map<CopiedString, CopiedString> Variables;
@@ -55,12 +56,12 @@ const char* build_get_variable( const char* name ){
class Evaluatable
{
public:
virtual ~Evaluatable(){}
virtual void evaluate( StringBuffer& output ) const = 0;
virtual Evaluatable* clone() const = 0;
virtual StringBuffer evaluate() const = 0;
virtual void exportXML( XMLImporter& importer ) const = 0;
};
class VariableString : public Evaluatable
class VariableString final : public Evaluatable
{
CopiedString m_string;
public:
@@ -68,14 +69,17 @@ public:
}
VariableString( const char* string ) : m_string( string ){
}
Evaluatable* clone() const override {
return new VariableString( *this );
}
const char* c_str() const {
return m_string.c_str();
}
void setString( const char* string ){
m_string = string;
}
void evaluate( StringBuffer& output ) const override {
StringBuffer variable;
StringBuffer evaluate() const override {
StringBuffer variable( 32 ), output( 256 );
bool in_variable = false;
for ( const char* i = m_string.c_str(); *i != '\0'; ++i )
{
@@ -105,65 +109,64 @@ public:
}
}
}
return output;
}
void exportXML( XMLImporter& importer ) const override {
importer << c_str();
}
};
class Conditional : public Evaluatable
class Conditional final : public Evaluatable
{
VariableString* m_test;
VariableString m_test;
public:
Evaluatable* m_result;
Conditional( VariableString* test ) : m_test( test ){
VariableString m_result;
Conditional( VariableString&& test ) : m_test( test ){
}
~Conditional(){
delete m_test;
delete m_result;
Evaluatable* clone() const override {
return new Conditional( *this );
}
void evaluate( StringBuffer& output ) const override {
StringBuffer buffer;
m_test->evaluate( buffer );
if ( !buffer.empty() ) {
m_result->evaluate( output );
StringBuffer evaluate() const override {
if ( !m_test.evaluate().empty() ) {
return m_result.evaluate();
}
return StringBuffer();
}
void exportXML( XMLImporter& importer ) const override {
StaticElement conditionElement( "cond" );
conditionElement.insertAttribute( "value", m_test->c_str() );
conditionElement.insertAttribute( "value", m_test.c_str() );
importer.pushElement( conditionElement );
m_result->exportXML( importer );
m_result.exportXML( importer );
importer.popElement( conditionElement.name() );
}
};
typedef std::vector<Evaluatable*> Evaluatables;
typedef std::vector<std::unique_ptr<Evaluatable>> Evaluatables;
class Tool : public Evaluatable
class Tool
{
Evaluatables m_evaluatables;
public:
~Tool(){
for ( auto* e : m_evaluatables )
{
delete ( e );
}
Tool() = default;
Tool( const Tool& other ){
m_evaluatables.reserve( other.m_evaluatables.size() );
for ( const auto& e : other.m_evaluatables )
push_back( e->clone() );
}
Tool( Tool&& other ) noexcept = default;
Tool& operator=( Tool&& other ) noexcept = default;
void push_back( Evaluatable* evaluatable ){
m_evaluatables.push_back( evaluatable );
m_evaluatables.emplace_back( evaluatable );
}
void evaluate( StringBuffer& output ) const override {
for ( const auto* e : m_evaluatables )
{
e->evaluate( output );
}
StringBuffer evaluate() const {
StringBuffer output( 256 );
for ( const auto& e : m_evaluatables )
output.push_string( e->evaluate().c_str() );
return output;
}
void exportXML( XMLImporter& importer ) const override {
for ( const auto* e : m_evaluatables )
{
void exportXML( XMLImporter& importer ) const {
for ( const auto& e : m_evaluatables )
e->exportXML( importer );
}
}
};
@@ -178,7 +181,7 @@ public:
class VariableStringXMLConstructor final : public XMLElementParser
{
StringBuffer m_buffer;
StringBuffer m_buffer{ 256 };
VariableString& m_variableString;
public:
VariableStringXMLConstructor( VariableString& variableString ) : m_variableString( variableString ){
@@ -186,43 +189,43 @@ public:
~VariableStringXMLConstructor(){
m_variableString.setString( m_buffer.c_str() );
}
std::size_t write( const char* buffer, std::size_t length ){
std::size_t write( const char* buffer, std::size_t length ) override {
m_buffer.push_range( buffer, buffer + length );
return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
XMLElementParser& pushElement( const XMLElement& element ) override {
ERROR_MESSAGE( "parse error: invalid element " << makeQuoted( element.name() ) );
return *this;
}
void popElement( const char* name ){
void popElement( const char* name ) override {
}
};
class ConditionalXMLConstructor final : public XMLElementParser
{
StringBuffer m_buffer;
StringBuffer m_buffer{ 256 };
Conditional& m_conditional;
public:
ConditionalXMLConstructor( Conditional& conditional ) : m_conditional( conditional ){
}
~ConditionalXMLConstructor(){
m_conditional.m_result = new VariableString( m_buffer.c_str() );
m_conditional.m_result.setString( m_buffer.c_str() );
}
std::size_t write( const char* buffer, std::size_t length ){
std::size_t write( const char* buffer, std::size_t length ) override {
m_buffer.push_range( buffer, buffer + length );
return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
XMLElementParser& pushElement( const XMLElement& element ) override {
ERROR_MESSAGE( "parse error: invalid element " << makeQuoted( element.name() ) );
return *this;
}
void popElement( const char* name ){
void popElement( const char* name ) override {
}
};
class ToolXMLConstructor final : public XMLElementParser
{
StringBuffer m_buffer;
StringBuffer m_buffer{ 256 };
Tool& m_tool;
ConditionalXMLConstructor* m_conditional;
public:
@@ -231,14 +234,14 @@ public:
~ToolXMLConstructor(){
flush();
}
std::size_t write( const char* buffer, std::size_t length ){
std::size_t write( const char* buffer, std::size_t length ) override {
m_buffer.push_range( buffer, buffer + length );
return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
XMLElementParser& pushElement( const XMLElement& element ) override {
if ( string_equal( element.name(), "cond" ) ) {
flush();
Conditional* conditional = new Conditional( new VariableString( element.attribute( "value" ) ) );
Conditional* conditional = new Conditional( VariableString( element.attribute( "value" ) ) );
m_tool.push_back( conditional );
m_conditional = new ConditionalXMLConstructor( *conditional );
return *m_conditional;
@@ -249,7 +252,7 @@ public:
return *this;
}
}
void popElement( const char* name ){
void popElement( const char* name ) override {
if ( string_equal( name, "cond" ) ) {
delete m_conditional;
}
@@ -278,10 +281,10 @@ class BuildXMLConstructor final : public XMLElementParser
public:
BuildXMLConstructor( Build& build ) : m_build( build ){
}
std::size_t write( const char* buffer, std::size_t length ){
std::size_t write( const char* buffer, std::size_t length ) override {
return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
XMLElementParser& pushElement( const XMLElement& element ) override {
if ( string_equal( element.name(), "command" ) ) {
m_build.push_back( BuildCommand() );
m_variableString = new VariableStringXMLConstructor( m_build.back() );
@@ -293,7 +296,7 @@ public:
return *this;
}
}
void popElement( const char* name ){
void popElement( const char* name ) override {
delete m_variableString;
}
};
@@ -350,10 +353,10 @@ class ProjectXMLConstructor : public XMLElementParser
public:
ProjectXMLConstructor( Project& project, Tools& tools ) : m_project( project ), m_tools( tools ){
}
std::size_t write( const char* buffer, std::size_t length ){
std::size_t write( const char* buffer, std::size_t length ) override {
return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
XMLElementParser& pushElement( const XMLElement& element ) override {
if ( string_equal( element.name(), "var" ) ) {
Tools::iterator i = m_tools.insert( Tools::value_type( element.attribute( "name" ), Tool() ) ).first;
m_tool = new ToolXMLConstructor( ( *i ).second );
@@ -374,7 +377,7 @@ public:
return *this;
}
}
void popElement( const char* name ){
void popElement( const char* name ) override {
if ( string_equal( name, "var" ) ) {
delete m_tool;
}
@@ -387,13 +390,13 @@ public:
class SkipAllParser : public XMLElementParser
{
public:
std::size_t write( const char* buffer, std::size_t length ){
std::size_t write( const char* buffer, std::size_t length ) override {
return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
XMLElementParser& pushElement( const XMLElement& element ) override {
return *this;
}
void popElement( const char* name ){
void popElement( const char* name ) override {
}
};
@@ -411,10 +414,10 @@ public:
m_version( version_parse( version ) ),
m_compatible( false ){
}
std::size_t write( const char* buffer, std::size_t length ){
std::size_t write( const char* buffer, std::size_t length ) override {
return length;
}
XMLElementParser& pushElement( const XMLElement& element ){
XMLElementParser& pushElement( const XMLElement& element ) override {
if ( string_equal( element.name(), m_elementName.c_str() ) ) {
Version dataVersion( version_parse( element.attribute( "version" ) ) );
if ( version_compatible( m_version, dataVersion ) ) {
@@ -432,7 +435,7 @@ public:
return *this;
}
}
void popElement( const char* name ){
void popElement( const char* name ) override {
}
bool versionCompatible() const {
@@ -443,9 +446,10 @@ public:
namespace
{
Project g_build_project;
bool g_build_changed = false;
Project::const_iterator g_lastExecutedBuild;
Tools g_build_tools;
bool g_build_changed = false;
bool g_tools_changed = false;
}
void build_error_undefined_tool( const char* build, const char* tool ){
@@ -468,13 +472,16 @@ void project_verify( Project& project, Tools& tools ){
#endif
}
std::vector<CopiedString> build_construct_commands( size_t buildIdx ){
void build_init_tools(){
for ( const auto& [ name, tool ] : g_build_tools )
{
StringBuffer output;
tool.evaluate( output );
build_set_variable( name.c_str(), output.c_str() );
build_set_variable( name.c_str(), tool.evaluate().c_str() );
}
}
std::vector<CopiedString> build_construct_commands( size_t buildIdx ){
build_init_variables();
build_init_tools();
std::vector<CopiedString> commands;
@@ -483,9 +490,7 @@ std::vector<CopiedString> build_construct_commands( size_t buildIdx ){
g_lastExecutedBuild = buildIt;
for ( const auto& command : buildIt->second )
{
StringBuffer output;
command.evaluate( output );
if ( !output.empty() )
if ( const StringBuffer output = command.evaluate(); !output.empty() )
commands.emplace_back( output.c_str() );
}
}
@@ -507,13 +512,13 @@ public:
XMLParser( XMLElementParser& parser ){
m_stack.push_back( &parser );
}
std::size_t write( const char* buffer, std::size_t length ){
std::size_t write( const char* buffer, std::size_t length ) override {
return m_stack.back()->write( buffer, length );
}
void pushElement( const XMLElement& element ){
void pushElement( const XMLElement& element ) override {
m_stack.push_back( &m_stack.back()->pushElement( element ) );
}
void popElement( const char* name ){
void popElement( const char* name ) override {
m_stack.pop_back();
m_stack.back()->popElement( name );
}
@@ -651,7 +656,7 @@ inline QTreeWidgetItem* new_item( const char *text ){
return item;
}
#define LAST_ITER_STRING "+"
const char LAST_ITER_STRING[] = "+";
inline void last_iter_append( QTreeWidget* tree ){
QTreeWidgetItem *item = new_item( LAST_ITER_STRING );
item->setFlags( c_itemFlags );
@@ -762,7 +767,7 @@ void commands_cell_edited( QTreeWidgetItem *item ){
}
else if ( !new_text.isEmpty() && !string_equal( new_text, LAST_ITER_STRING ) ) { // add new
g_build_changed = true;
build.push_back( Build::value_type( VariableString( new_text ) ) );
build.push_back( VariableString( new_text ) );
item->setFlags( c_itemFlagsDraggable ); // note: calls this function again with upper condition
last_iter_append( item->treeWidget() );
@@ -941,8 +946,91 @@ protected:
}
};
#include "qe3.h"
#include "gtkutil/guisettings.h"
#include <QTableWidget>
#include <QStyledItemDelegate>
#include <QLineEdit>
#include <QApplication>
#include <QClipboard>
#include <QToolTip>
#include "stream/memstream.h"
#include "gtkutil/image.h"
class CustomDelegate0 : public QStyledItemDelegate
{
public:
void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const override {
if ( QLineEdit *lineEdit = qobject_cast<QLineEdit*>( editor ) ) {
const auto text = lineEdit->text().toLatin1();
if( text != LAST_ITER_STRING && !g_build_tools.contains( text.constData() ) ){ // somethig new
const auto oldtext = index.data().toString().toLatin1();
if( oldtext == LAST_ITER_STRING ){ // new tool
g_build_tools[ text.constData() ]; // insert
g_tools_changed = true;
model->setData( index, lineEdit->text(), Qt::ItemDataRole::DisplayRole );
// add new last row
model->insertRow( model->rowCount() );
model->setData( model->index( model->rowCount() - 1, 0 ), LAST_ITER_STRING, Qt::ItemDataRole::DisplayRole );
}
else{ // new name
auto tool = g_build_tools.extract( oldtext.constData() );
tool.key() = text.constData();
g_build_tools.insert( std::move( tool ) );
g_tools_changed = true;
model->setData( index, lineEdit->text(), Qt::ItemDataRole::DisplayRole );
}
}
}
}
};
// Custom delegate to handle different display and edit text
class CustomDelegate1 : public QStyledItemDelegate
{
public:
QWidget* createEditor( QWidget *parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const override {
return index.siblingAtColumn( 0 ).data().toString() == LAST_ITER_STRING
? nullptr // disable editing of special field
: QStyledItemDelegate::createEditor( parent, option, index );
}
// Set the editor's data (xml from Qt::UserRole)
void setEditorData( QWidget *editor, const QModelIndex &index ) const override {
if ( QLineEdit *lineEdit = qobject_cast<QLineEdit*>( editor ) ) {
lineEdit->setText( index.data( Qt::ItemDataRole::UserRole ).toString() );
}
}
// Save the edited data back to Qt::UserRole and update Qt::DisplayRole
void setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const override {
if ( QLineEdit *lineEdit = qobject_cast<QLineEdit*>( editor ) ) {
model->setData( index, lineEdit->text(), Qt::ItemDataRole::UserRole );
// Optionally update display text based on edit text
const auto text = ( "<var name=\"DUMMY\">" + lineEdit->text() + "</var>" ).toLatin1();
BufferInputStream projectLine( text, text.length() );
XMLStreamParser parser( projectLine );
Project project;
Tools tools;
ProjectXMLConstructor constructor( project, tools );
XMLParser importer( constructor );
parser.exportXML( importer );
build_init_variables();
const auto thisname = index.siblingAtColumn( 0 ).data().toString().toLatin1();
for ( const auto& [ name, tool ] : g_build_tools )
{
if( string_equal( name.c_str(), thisname.constData() ) ) // preceding tools are usable inside current one
break;
build_set_variable( name.c_str(), tool.evaluate().c_str() );
}
Tool& newtool = tools.begin()->second;
model->setData( index, newtool.evaluate().c_str(), Qt::ItemDataRole::DisplayRole );
g_build_tools[ thisname.constData() ] = std::move( newtool );
g_tools_changed = true;
build_clear_variables();
}
}
};
EMessageBoxReturn BuildMenuDialog_construct( ProjectList& projectList ){
static auto [dialog, rootLayout] = [](){
@@ -1004,28 +1092,77 @@ EMessageBoxReturn BuildMenuDialog_construct( ProjectList& projectList ){
}
}
{
auto expander = new QGroupBox( "build variables" );
auto expander = new QGroupBox( "Build Variables" );
expander->setFlat( true );
expander->setCheckable( true );
expander->setChecked( false );
grid->addWidget( expander, 2, 0 );
bsp_init();
for ( auto& [ name, tool ] : g_build_tools ){
StringBuffer output;
tool.evaluate( output );
build_set_variable( name.c_str(), output.c_str() );
auto *containerWidget = new QWidget;
( new QVBoxLayout( expander ) )->addWidget( containerWidget );
containerWidget->hide();
QObject::connect( expander, &QGroupBox::clicked, containerWidget, &QWidget::setVisible );
auto *vbox = new QVBoxLayout( containerWidget );
build_init_variables();
{ // immutable variables
QIcon icon = new_local_icon( "copy.png" );
auto *table = new QTableWidget( 0, 2 );
vbox->addWidget( table );
table->setEditTriggers( QAbstractItemView::EditTrigger::NoEditTriggers );
table->setSelectionMode( QAbstractItemView::SelectionMode::SingleSelection );
table->horizontalHeader()->setStretchLastSection( true );
table->horizontalHeader()->setSectionResizeMode( 0, QHeaderView::ResizeMode::ResizeToContents );
table->verticalHeader()->setSectionResizeMode( QHeaderView::ResizeMode::ResizeToContents );
table->setSizeAdjustPolicy( QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents );
table->setVerticalScrollBarPolicy( Qt::ScrollBarPolicy::ScrollBarAlwaysOff );
table->horizontalHeader()->hide();
table->verticalHeader()->hide();
QObject::connect( table, &QTableWidget::itemClicked, []( QTableWidgetItem *item ){
if( item->column() == 0 ){
QApplication::clipboard()->setText( item->text() );
QTableWidget *table = item->tableWidget();
QToolTip::showText( table->mapToGlobal( table->visualItemRect( item ).bottomLeft() ), "Copied to clipboard.", table, QRect(), 666 );
}
} );
for( const auto& [ name, var ] : g_build_variables ){
table->insertRow( table->rowCount() );
table->setItem( table->rowCount() - 1, 0, new QTableWidgetItem( icon, '[' + QString( name.c_str() ) + ']' ) );
table->setItem( table->rowCount() - 1, 1, new QTableWidgetItem( var.c_str() ) );
}
}
StringOutputStream stream( 256 );
for( const auto& [ name, var ] : g_build_variables ){
stream << '[' << name << "] = " << var << '\n';
build_init_tools();
{ // mutable 'Tool' variables
auto *table = new QTableWidget( 0, 2 );
vbox->addWidget( table );
table->setHorizontalHeaderLabels( { "Name", "Variable" } );
table->setSelectionMode( QAbstractItemView::SelectionMode::SingleSelection );
table->horizontalHeader()->setStretchLastSection( true );
table->setWordWrap( true );
table->verticalHeader()->setSectionResizeMode( QHeaderView::ResizeMode::ResizeToContents );
table->setItemDelegateForColumn( 0, new CustomDelegate0 );
table->setItemDelegateForColumn( 1, new CustomDelegate1 );
for ( auto& [ name, tool ] : g_build_tools ){
table->insertRow( table->rowCount() );
table->setItem( table->rowCount() - 1, 0, new QTableWidgetItem( name.c_str() ) );
auto *item = new QTableWidgetItem( g_build_variables[ name ].c_str() );
table->setItem( table->rowCount() - 1, 1, item );
StringOutputStream stream( 256 ); // store xml representation
{
XMLStreamWriter writer( stream ); // destructor dumps to stream
tool.exportXML( writer );
}
item->setData( Qt::ItemDataRole::UserRole, strchr( stream, '>' ) + 1 ); // skip xml header
}
table->insertRow( table->rowCount() );
table->setItem( table->rowCount() - 1, 0, new QTableWidgetItem( LAST_ITER_STRING ) );
}
build_clear_variables();
auto label = new QLabel( stream.c_str() );
label->hide();
( new QHBoxLayout( expander ) )->addWidget( label );
QObject::connect( expander, &QGroupBox::clicked, label, &QWidget::setVisible );
}
}
@@ -1040,15 +1177,17 @@ void LoadBuildMenu();
void DoBuildMenu(){
ProjectList projectList( g_build_project );
const Project bakproj = g_build_project;
const size_t baklast = Project_find( g_build_project, g_lastExecutedBuild );
Project bakProj = g_build_project;
const size_t bakLastIdx = Project_find( g_build_project, g_lastExecutedBuild );
Tools bakTools( g_build_tools );
const EMessageBoxReturn ret = BuildMenuDialog_construct( projectList );
if ( ret == eIDCANCEL || ret == 0 ) {
if ( projectList.m_changed || g_build_changed ){
g_build_project = bakproj;
g_lastExecutedBuild = std::next( g_build_project.cbegin(), baklast );
if ( projectList.m_changed || g_build_changed || g_tools_changed ){
g_build_project.swap( bakProj );
g_lastExecutedBuild = std::next( g_build_project.cbegin(), bakLastIdx );
g_build_tools.swap( bakTools );
Build_refreshMenu( g_bsp_menu );
}
}
@@ -1058,7 +1197,7 @@ void DoBuildMenu(){
Build_refreshMenu( g_bsp_menu );
}
else if ( projectList.m_changed ) {
else if ( projectList.m_changed || g_tools_changed ) {
g_build_changed = true;
}
}
+1 -3
View File
@@ -154,7 +154,7 @@ bool ConfirmModified( const char* title ){
return true; // QMessageBox::StandardButton::Discard
}
void bsp_init(){
void build_init_variables(){
StringOutputStream stream( 256 );
build_set_variable( "RadiantPath", AppPath_get() );
@@ -226,8 +226,6 @@ void RunBSP( size_t buildIdx ){
Pointfile_Delete();
bsp_init();
const std::vector<CopiedString> commands = build_construct_commands( buildIdx );
const bool monitor = std::any_of( commands.cbegin(), commands.cend(), []( const CopiedString& command ){
return strstr( command.c_str(), RADIANT_MONITOR_ADDRESS ) != 0;
+1 -1
View File
@@ -61,4 +61,4 @@ extern SimpleCounter g_brushCount;
extern SimpleCounter g_patchCount;
extern SimpleCounter g_entityCount;
void bsp_init();
void build_init_variables();