diff --git a/radiant/gtkdlgs.cpp b/radiant/gtkdlgs.cpp index 412cb3b2..a3b31c55 100644 --- a/radiant/gtkdlgs.cpp +++ b/radiant/gtkdlgs.cpp @@ -455,6 +455,148 @@ void DoShaderInfoDlg( const char* name, const char* filename, const char* title qt_MessageBox( MainFrame_getWindow(), text.c_str(), title ); } +// ============================================================================= +// Install dev files dialog +#include +#include +#include +#include + +void DoInstallDevFilesDlg( const char *enginePath ){ + std::vector files; // relative source files paths + const auto sourceBase = std::filesystem::path( g_pGameDescription->mGameToolsPath.c_str() ) / "install/"; + const auto targetBase = std::filesystem::path( enginePath ) / basegame_get(); + QString description; + { + std::error_code err; + std::filesystem::recursive_directory_iterator dirIter( sourceBase, err ); + if( err ){ + globalErrorStream() << err.message().c_str() << ' ' << sourceBase.string().c_str() << '\n'; + return; + } + for( const auto& dirEntry : dirIter ) { + if( err ){ + globalErrorStream() << err.message().c_str() << '\n'; + break; + } + if( dirEntry.is_regular_file( err ) && !err ){ + if( dirIter.depth() == 0 && dirEntry.path().filename() == ".description" ){ + if( QFile f( QString::fromStdString( dirEntry.path().string() ) ); f.open( QIODevice::ReadOnly | QIODevice::Text ) ) + description = QTextStream( &f ).readAll(); + } + else{ + files.push_back( std::filesystem::relative( dirEntry.path(), sourceBase, err ) ); + } + } + } + } + if( !files.empty() ){ + QDialog dialog( nullptr, Qt::Window ); + dialog.setWindowTitle( "Install Map Developer's Files" ); + { + auto *box = new QVBoxLayout( &dialog ); + { + auto *label = new QLabel( "Would you like to install following files recommended for fluent map development\nto " + QString::fromStdString( targetBase.string() ) + "?" ); + label->setAlignment( Qt::AlignmentFlag::AlignHCenter ); + box->addWidget( label ); + } + QListWidget *listWidget; + { + listWidget = new QListWidget; + listWidget->setSelectionMode( QAbstractItemView::SelectionMode::NoSelection ); + box->addWidget( listWidget, 0 ); + for( const auto& file : files ){ + listWidget->addItem( QString::fromStdString( file.string() ) ); + } + } + if( !description.isEmpty() ){ + box->addWidget( new QLabel( ".description" ) ); + auto *text = new QPlainTextEdit( description ); + text->setSizePolicy( QSizePolicy::Policy::MinimumExpanding, QSizePolicy::Policy::MinimumExpanding ); + text->setLineWrapMode( QPlainTextEdit::LineWrapMode::NoWrap ); + text->setReadOnly( true ); + // set minimal size to fit text to avoid the need to resize window/scroll + const auto rect = text->fontMetrics().boundingRect( QRect(), 0, description ); + text->setMinimumSize( rect.width() + text->contentsMargins().left() + text->contentsMargins().right() + + text->document()->documentMargin() * 2 + text->verticalScrollBar()->sizeHint().width(), + rect.height() + text->contentsMargins().top() + text->contentsMargins().bottom() + + text->document()->documentMargin() * 2 + text->horizontalScrollBar()->sizeHint().height() ); + + box->addWidget( text, 0 ); + } + const auto doCopy = [&](){ + QMessageBox::StandardButton overwrite = QMessageBox::StandardButton::Yes; + size_t copiedN = 0; + for( size_t i = 0; i < files.size(); ++i ){ + const auto source = sourceBase / files[i]; + const auto target = targetBase / files[i]; + std::error_code err; + if( ( std::filesystem::exists( target, err ) || err ) && overwrite != QMessageBox::StandardButton::YesToAll ){ + if( overwrite == QMessageBox::StandardButton::NoToAll ) continue; + overwrite = (QMessageBox::StandardButton)QMessageBox( QMessageBox::Icon::Question, "File exists", + QString( "File \"" ) + QString::fromStdString( target.string() ) + "\" exists.\nOverwrite it?", + QMessageBox::StandardButton::Yes | + QMessageBox::StandardButton::YesToAll | + QMessageBox::StandardButton::No | + QMessageBox::StandardButton::NoToAll | + QMessageBox::StandardButton::Abort, &dialog ).exec(); + if( overwrite == QMessageBox::StandardButton::Abort ) break; + if( overwrite == QMessageBox::StandardButton::NoToAll || overwrite == QMessageBox::StandardButton::No ) continue; + } + + const auto copy_file = [&](){ + if( std::filesystem::exists( target, err ) ){ + if( !std::filesystem::remove( target, err ) ){ + return false; + } + } + else if( err ){ + return false; + } + std::filesystem::create_directories( target.parent_path(), err ); + if( err ) + return false; + // std::filesystem::copy_options::overwrite_existing is broken in libstdc++ on windows, thus using std::filesystem::remove + return std::filesystem::copy_file( source, target, std::filesystem::copy_options::none, err ); + }; +retry: + if( !copy_file() ){ + const auto ret = (QMessageBox::StandardButton)QMessageBox( QMessageBox::Icon::Question, "Fail", + "Failed to write \"" + QString::fromStdString( target.string() ) + "\"\n" + err.message().c_str(), + QMessageBox::StandardButton::Retry | + QMessageBox::StandardButton::Ignore | + QMessageBox::StandardButton::Abort, &dialog ).exec(); + if( ret == QMessageBox::StandardButton::Retry ) goto retry; + if( ret == QMessageBox::StandardButton::Ignore ) continue; + if( ret == QMessageBox::StandardButton::Abort ) break; + } + auto *item = listWidget->item( i ); + item->setCheckState( Qt::CheckState::Checked ); + listWidget->scrollToItem( item ); + QCoreApplication::processEvents( QEventLoop::ProcessEventsFlag::ExcludeUserInputEvents ); + ++copiedN; + } + + if( copiedN == files.size() ) + qt_MessageBox( &dialog, "All files have been copied.", "Great Success!" ); + else if( copiedN != 0 ) + qt_MessageBox( &dialog, StringOutputStream( 64 )( copiedN, '/', files.size(), " files have been copied." ), "Moderate Success!" ); + else + qt_MessageBox( &dialog, "No files have been copied.", "Boo!" ); + + dialog.accept(); + }; + { + auto *buttons = new QDialogButtonBox( QDialogButtonBox::StandardButton::Ok | QDialogButtonBox::StandardButton::Cancel ); + box->addWidget( buttons ); + QObject::connect( buttons, &QDialogButtonBox::accepted, doCopy ); + QObject::connect( buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject ); + } + } + dialog.exec(); + } +} + // ============================================================================= // Shader Editor diff --git a/radiant/gtkdlgs.h b/radiant/gtkdlgs.h index e3da4af4..58144747 100644 --- a/radiant/gtkdlgs.h +++ b/radiant/gtkdlgs.h @@ -35,6 +35,7 @@ bool DoLightIntensityDlg( int *intensity ); void DoShaderInfoDlg( const char* name, const char* filename, const char* title ); void DoShaderView( const char *shaderFileName, const char *shaderName, bool external_editor ); +void DoInstallDevFilesDlg( const char *enginePath ); void Game_constructPreferences( class PreferencesPage& page ); diff --git a/radiant/mainframe.cpp b/radiant/mainframe.cpp index 6bf026f2..c3abf333 100644 --- a/radiant/mainframe.cpp +++ b/radiant/mainframe.cpp @@ -319,6 +319,16 @@ void EnginePath_Unrealise(){ } } +static CopiedString g_installedDevFilesPath; // track last engine path, where dev files installation occured, to prompt again when changed + +static void installDevFiles( const CopiedString& enginePath ){ + if( !path_equal( enginePath.c_str(), g_installedDevFilesPath.c_str() ) ){ + ASSERT_MESSAGE( g_enginepath_unrealised != 0, "installDevFiles: engine path realised" ); + DoInstallDevFilesDlg( enginePath.c_str() ); + g_installedDevFilesPath = enginePath; + } +} + void setEnginePath( CopiedString& self, const char* value ){ const auto buffer = StringOutputStream( 256 )( DirectoryCleaned( value ) ); if ( !path_equal( buffer.c_str(), self.c_str() ) ) { @@ -342,6 +352,8 @@ void setEnginePath( CopiedString& self, const char* value ){ self = buffer.c_str(); + installDevFiles( self ); + EnginePath_Realise(); } } @@ -469,10 +481,12 @@ static bool g_strEnginePath_was_empty_1st_start = false; void EnginePath_verify(){ if ( !file_exists( g_strEnginePath.c_str() ) || g_strEnginePath_was_empty_1st_start ) { + g_installedDevFilesPath = ""; // trigger install for non existing engine path case g_PathsDialog.Create( nullptr ); g_PathsDialog.DoModal(); g_PathsDialog.Destroy(); } + installDevFiles( g_strEnginePath ); // try this anytime, as engine path may be set via command line or -gamedetect } namespace @@ -2084,6 +2098,7 @@ void MainFrame_Construct(){ GlobalPreferenceSystem().registerPreference( "ExtraResoucePath", CopiedStringImportStringCaller( g_strExtraResourcePath ), CopiedStringExportStringCaller( g_strExtraResourcePath ) ); GlobalPreferenceSystem().registerPreference( "EnginePath", CopiedStringImportStringCaller( g_strEnginePath ), CopiedStringExportStringCaller( g_strEnginePath ) ); + GlobalPreferenceSystem().registerPreference( "InstalledDevFilesPath", CopiedStringImportStringCaller( g_installedDevFilesPath ), CopiedStringExportStringCaller( g_installedDevFilesPath ) ); if ( g_strEnginePath.empty() ) { g_strEnginePath_was_empty_1st_start = true; diff --git a/radiant/mainframe.h b/radiant/mainframe.h index 208cc4cc..ce835e26 100644 --- a/radiant/mainframe.h +++ b/radiant/mainframe.h @@ -177,10 +177,8 @@ void Radiant_detachEnginePathObserver( ModuleObserver& observer ); void Radiant_attachGameToolsPathObserver( ModuleObserver& observer ); void Radiant_detachGameToolsPathObserver( ModuleObserver& observer ); -extern CopiedString g_strEnginePath; void EnginePath_verify(); const char* EnginePath_get(); -const char* QERApp_GetGamePath(); extern CopiedString g_strExtraResourcePath; const char* ExtraResourcePath_get();