auto str = StringOutputStream()(bla) use form was not doing copy elision or move, but copy
874 lines
29 KiB
C++
874 lines
29 KiB
C++
/*
|
|
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
|
|
For a list of contributors, see the accompanying CONTRIBUTORS file.
|
|
|
|
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
|
|
*/
|
|
|
|
//
|
|
// User preferences
|
|
//
|
|
// Leonardo Zide (leo@lokigames.com)
|
|
//
|
|
|
|
#include "preferences.h"
|
|
#include "environment.h"
|
|
|
|
#include "debugging/debugging.h"
|
|
|
|
#include "generic/callback.h"
|
|
#include "math/vector.h"
|
|
#include "string/string.h"
|
|
#include "stream/stringstream.h"
|
|
#include "os/file.h"
|
|
#include "os/path.h"
|
|
#include "os/dir.h"
|
|
#include "gtkutil/filechooser.h"
|
|
#include "gtkutil/messagebox.h"
|
|
#include "commandlib.h"
|
|
|
|
#include "error.h"
|
|
#include "console.h"
|
|
#include "xywindow.h"
|
|
#include "mainframe.h"
|
|
#include "qe3.h"
|
|
#include "gtkdlgs.h"
|
|
|
|
#include <QCoreApplication>
|
|
#include <QGridLayout>
|
|
#include <QDialogButtonBox>
|
|
#include <QPushButton>
|
|
#include <QStackedWidget>
|
|
#include <QTreeView>
|
|
#include <QStandardItemModel>
|
|
#include <QGroupBox>
|
|
#include <QCheckBox>
|
|
#include <QHeaderView>
|
|
|
|
|
|
void Global_constructPreferences( PreferencesPage& page ){
|
|
page.appendCheckBox( "Console", "Enable Logging", g_Console_enableLogging );
|
|
}
|
|
|
|
void Interface_constructPreferences( PreferencesPage& page ){
|
|
page.appendPathEntry( "Shader Editor Command", g_TextEditor_editorCommand, false );
|
|
}
|
|
|
|
/*!
|
|
=========================================================
|
|
Games selection dialog
|
|
=========================================================
|
|
*/
|
|
|
|
inline const char* xmlAttr_getName( xmlAttrPtr attr ){
|
|
return reinterpret_cast<const char*>( attr->name );
|
|
}
|
|
|
|
inline const char* xmlAttr_getValue( xmlAttrPtr attr ){
|
|
return reinterpret_cast<const char*>( attr->children->content );
|
|
}
|
|
|
|
CGameDescription::CGameDescription( xmlDocPtr pDoc, const CopiedString& gameFile ){
|
|
// read the user-friendly game name
|
|
xmlNodePtr pNode = pDoc->children;
|
|
|
|
while ( pNode != 0 && strcmp( (const char*)pNode->name, "game" ) )
|
|
{
|
|
pNode = pNode->next;
|
|
}
|
|
if ( !pNode ) {
|
|
Error( "Didn't find 'game' node in the game description file '%s'\n", pDoc->URL );
|
|
}
|
|
|
|
for ( xmlAttrPtr attr = pNode->properties; attr != 0; attr = attr->next )
|
|
{
|
|
m_gameDescription.insert( GameDescription::value_type( xmlAttr_getName( attr ), xmlAttr_getValue( attr ) ) );
|
|
}
|
|
|
|
mGameToolsPath = StringStream( AppPath_get(), "gamepacks/", gameFile, '/' );
|
|
|
|
ASSERT_MESSAGE( file_exists( mGameToolsPath.c_str() ), "game directory not found: " << makeQuoted( mGameToolsPath ) );
|
|
|
|
mGameFile = gameFile;
|
|
|
|
{
|
|
GameDescription::iterator i = m_gameDescription.find( "type" );
|
|
if ( i == m_gameDescription.end() ) {
|
|
globalWarningStream() << "Warning, 'type' attribute not found in '" << reinterpret_cast<const char*>( pDoc->URL ) << "'\n";
|
|
// default
|
|
mGameType = "q3";
|
|
}
|
|
else
|
|
{
|
|
mGameType = ( *i ).second.c_str();
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGameDescription::Dump(){
|
|
globalOutputStream() << "game description file: " << makeQuoted( mGameFile ) << '\n';
|
|
for ( GameDescription::iterator i = m_gameDescription.begin(); i != m_gameDescription.end(); ++i )
|
|
{
|
|
globalOutputStream() << ( *i ).first << " = " << makeQuoted( ( *i ).second ) << '\n';
|
|
}
|
|
}
|
|
|
|
CGameDescription *g_pGameDescription;
|
|
|
|
|
|
#include "stream/textfilestream.h"
|
|
#include "container/array.h"
|
|
#include "xml/ixml.h"
|
|
#include "xml/xmlparser.h"
|
|
#include "xml/xmlwriter.h"
|
|
|
|
#include "preferencedictionary.h"
|
|
#include "stringio.h"
|
|
|
|
const char* const PREFERENCES_VERSION = "1.0";
|
|
|
|
bool Preferences_Load( PreferenceDictionary& preferences, const char* filename, const char *cmdline_prefix ){
|
|
bool ret = false;
|
|
TextFileInputStream file( filename );
|
|
if ( !file.failed() ) {
|
|
XMLStreamParser parser( file );
|
|
XMLPreferenceDictionaryImporter importer( preferences, PREFERENCES_VERSION );
|
|
parser.exportXML( importer );
|
|
ret = true;
|
|
}
|
|
|
|
int l = strlen( cmdline_prefix );
|
|
for ( int i = 1; i < g_argc - 1; ++i )
|
|
{
|
|
if ( g_argv[i][0] == '-' ) {
|
|
if ( !strncmp( g_argv[i] + 1, cmdline_prefix, l ) ) {
|
|
if ( g_argv[i][l + 1] == '-' ) {
|
|
preferences.importPref( g_argv[i] + l + 2, g_argv[i + 1] );
|
|
}
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool Preferences_Save( PreferenceDictionary& preferences, const char* filename ){
|
|
TextFileOutputStream file( filename );
|
|
if ( !file.failed() ) {
|
|
XMLStreamWriter writer( file );
|
|
XMLPreferenceDictionaryExporter exporter( preferences, PREFERENCES_VERSION );
|
|
exporter.exportXML( writer );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Preferences_Save_Safe( PreferenceDictionary& preferences, const char* filename ){
|
|
const auto tmpName = StringStream( filename, "TMP" );
|
|
return Preferences_Save( preferences, tmpName ) && file_move( tmpName, filename );
|
|
}
|
|
|
|
|
|
void LogConsole_importString( const char* string ){
|
|
g_Console_enableLogging = string_equal( string, "true" );
|
|
Sys_LogFile( g_Console_enableLogging );
|
|
}
|
|
typedef FreeCaller1<const char*, LogConsole_importString> LogConsoleImportStringCaller;
|
|
|
|
|
|
void RegisterGlobalPreferences( PreferenceSystem& preferences ){
|
|
preferences.registerPreference( "gamefile", makeCopiedStringStringImportCallback( LatchedAssignCaller( g_GamesDialog.m_sGameFile ) ), CopiedStringExportStringCaller( g_GamesDialog.m_sGameFile.m_latched ) );
|
|
preferences.registerPreference( "gamePrompt", BoolImportStringCaller( g_GamesDialog.m_bGamePrompt ), BoolExportStringCaller( g_GamesDialog.m_bGamePrompt ) );
|
|
preferences.registerPreference( "log console", LogConsoleImportStringCaller(), BoolExportStringCaller( g_Console_enableLogging ) );
|
|
}
|
|
|
|
|
|
PreferenceDictionary g_global_preferences;
|
|
|
|
void GlobalPreferences_Init(){
|
|
RegisterGlobalPreferences( g_global_preferences );
|
|
}
|
|
|
|
void CGameDialog::LoadPrefs(){
|
|
// load global .pref file
|
|
const auto strGlobalPref = StringStream( g_Preferences.m_global_rc_path, "global.pref" );
|
|
|
|
globalOutputStream() << "loading global preferences from " << makeQuoted( strGlobalPref ) << '\n';
|
|
|
|
if ( !Preferences_Load( g_global_preferences, strGlobalPref, "global" ) ) {
|
|
globalOutputStream() << "failed to load global preferences from " << strGlobalPref << '\n';
|
|
}
|
|
}
|
|
|
|
void CGameDialog::SavePrefs(){
|
|
const auto strGlobalPref = StringStream( g_Preferences.m_global_rc_path, "global.pref" );
|
|
|
|
globalOutputStream() << "saving global preferences to " << strGlobalPref << '\n';
|
|
|
|
if ( !Preferences_Save_Safe( g_global_preferences, strGlobalPref ) ) {
|
|
globalOutputStream() << "failed to save global preferences to " << strGlobalPref << '\n';
|
|
}
|
|
}
|
|
|
|
void CGameDialog::DoGameDialog(){
|
|
// show the UI
|
|
DoModal();
|
|
|
|
// we save the prefs file
|
|
SavePrefs();
|
|
}
|
|
|
|
CGameDescription* CGameDialog::GameDescriptionForComboItem(){
|
|
return ( m_nComboSelect >= 0 && m_nComboSelect < static_cast<int>( mGames.size() ) )?
|
|
*std::next( mGames.begin(), m_nComboSelect )
|
|
: 0; // not found
|
|
}
|
|
|
|
void CGameDialog::GameFileAssign( int value ){
|
|
m_nComboSelect = value;
|
|
// use value to set m_sGameFile
|
|
if( CGameDescription* iGame = GameDescriptionForComboItem() )
|
|
m_sGameFile.assign( iGame->mGameFile );
|
|
}
|
|
|
|
void CGameDialog::GameFileImport( int value ){
|
|
m_nComboSelect = value;
|
|
// use value to set m_sGameFile
|
|
if( CGameDescription* iGame = GameDescriptionForComboItem() )
|
|
m_sGameFile.import( iGame->mGameFile );
|
|
}
|
|
|
|
void CGameDialog::GameFileExport( const IntImportCallback& importCallback ) const {
|
|
// use m_sGameFile to set value
|
|
std::list<CGameDescription *>::const_iterator iGame;
|
|
int i = 0;
|
|
for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame )
|
|
{
|
|
if ( ( *iGame )->mGameFile == m_sGameFile.m_latched ) {
|
|
m_nComboSelect = i;
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
importCallback( m_nComboSelect );
|
|
}
|
|
|
|
void CGameDialog::CreateGlobalFrame( PreferencesPage& page, bool global ){
|
|
std::vector<const char*> games;
|
|
games.reserve( mGames.size() );
|
|
for ( std::list<CGameDescription *>::iterator i = mGames.begin(); i != mGames.end(); ++i )
|
|
{
|
|
games.push_back( ( *i )->getRequiredKeyValue( "name" ) );
|
|
}
|
|
page.appendCombo(
|
|
"Select the game",
|
|
StringArrayRange( games ),
|
|
global?
|
|
IntImportCallback( MemberCaller1<CGameDialog, int, &CGameDialog::GameFileAssign>( *this ) ):
|
|
IntImportCallback( MemberCaller1<CGameDialog, int, &CGameDialog::GameFileImport>( *this ) ),
|
|
ConstMemberCaller1<CGameDialog, const IntImportCallback&, &CGameDialog::GameFileExport>( *this )
|
|
);
|
|
page.appendCheckBox( "Startup", "Show Global Preferences", m_bGamePrompt );
|
|
}
|
|
|
|
void CGameDialog::BuildDialog(){
|
|
GetWidget()->setWindowTitle( "Global Preferences" );
|
|
|
|
auto vbox = new QVBoxLayout( GetWidget() );
|
|
vbox->setSizeConstraint( QLayout::SizeConstraint::SetFixedSize );
|
|
{
|
|
auto frame = new QGroupBox( "Game settings" );
|
|
vbox->addWidget( frame );
|
|
|
|
auto grid = new QGridLayout( frame );
|
|
grid->setAlignment( Qt::AlignmentFlag::AlignTop );
|
|
grid->setColumnStretch( 0, 111 );
|
|
grid->setColumnStretch( 1, 333 );
|
|
{
|
|
PreferencesPage preferencesPage( *this, grid );
|
|
Global_constructPreferences( preferencesPage );
|
|
CreateGlobalFrame( preferencesPage, true );
|
|
}
|
|
}
|
|
{
|
|
auto buttons = new QDialogButtonBox( QDialogButtonBox::StandardButton::Ok );
|
|
vbox->addWidget( buttons );
|
|
QObject::connect( buttons, &QDialogButtonBox::accepted, GetWidget(), &QDialog::accept );
|
|
}
|
|
}
|
|
|
|
class LoadGameFile
|
|
{
|
|
std::list<CGameDescription*>& mGames;
|
|
const char* mPath;
|
|
public:
|
|
LoadGameFile( std::list<CGameDescription*>& games, const char* path ) : mGames( games ), mPath( path ){
|
|
}
|
|
void operator()( const char* name ) const {
|
|
if ( !path_extension_is( name, "game" ) ) {
|
|
return;
|
|
}
|
|
const auto strPath = StringStream( mPath, name );
|
|
globalOutputStream() << strPath << '\n';
|
|
|
|
xmlDocPtr pDoc = xmlParseFile( strPath );
|
|
if ( pDoc ) {
|
|
mGames.push_back( new CGameDescription( pDoc, name ) );
|
|
xmlFreeDoc( pDoc );
|
|
}
|
|
else
|
|
{
|
|
globalErrorStream() << "XML parser failed on '" << strPath << "'\n";
|
|
}
|
|
}
|
|
};
|
|
|
|
void CGameDialog::ScanForGames(){
|
|
const auto path = StringStream( AppPath_get(), "gamepacks/games/" );
|
|
|
|
globalOutputStream() << "Scanning for game description files: " << path << '\n';
|
|
|
|
/*!
|
|
\todo FIXME LINUX:
|
|
do we put game description files below AppPath, or in ~/.radiant
|
|
i.e. read only or read/write?
|
|
my guess .. readonly cause it's an install
|
|
we will probably want to add ~/.radiant/<version>/games/ scanning on top of that for developers
|
|
(if that's really needed)
|
|
*/
|
|
|
|
Directory_forEach( path, LoadGameFile( mGames, path ) );
|
|
}
|
|
|
|
void CGameDialog::InitGlobalPrefPath(){
|
|
g_Preferences.m_global_rc_path = SettingsPath_get();
|
|
}
|
|
|
|
void CGameDialog::Reset(){
|
|
if ( g_Preferences.m_global_rc_path.empty() ) {
|
|
InitGlobalPrefPath();
|
|
}
|
|
|
|
file_remove( StringStream( g_Preferences.m_global_rc_path, "global.pref" ) );
|
|
}
|
|
|
|
void CGameDialog::Init(){
|
|
InitGlobalPrefPath();
|
|
LoadPrefs();
|
|
ScanForGames();
|
|
if ( mGames.empty() ) {
|
|
Error( "Didn't find any valid game file descriptions, aborting\n" );
|
|
}
|
|
else
|
|
{
|
|
mGames.sort( []( const CGameDescription* one, const CGameDescription* another ){
|
|
return one->mGameFile < another->mGameFile;
|
|
} );
|
|
}
|
|
|
|
CGameDescription* currentGameDescription = 0;
|
|
|
|
if ( !m_bGamePrompt ) {
|
|
// search by .game name
|
|
std::list<CGameDescription *>::iterator iGame;
|
|
for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame )
|
|
{
|
|
if ( ( *iGame )->mGameFile == m_sGameFile.m_value ) {
|
|
currentGameDescription = ( *iGame );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( !currentGameDescription ) {
|
|
Create( nullptr );
|
|
DoGameDialog();
|
|
// use m_nComboSelect to identify the game to run as and set the globals
|
|
currentGameDescription = GameDescriptionForComboItem();
|
|
ASSERT_NOTNULL( currentGameDescription );
|
|
}
|
|
g_pGameDescription = currentGameDescription;
|
|
|
|
g_pGameDescription->Dump();
|
|
}
|
|
|
|
CGameDialog::~CGameDialog(){
|
|
// free all the game descriptions
|
|
std::list<CGameDescription *>::iterator iGame;
|
|
for ( iGame = mGames.begin(); iGame != mGames.end(); ++iGame )
|
|
{
|
|
delete ( *iGame );
|
|
*iGame = 0;
|
|
}
|
|
if ( GetWidget() != 0 ) {
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
inline const char* GameDescription_getIdentifier( const CGameDescription& gameDescription ){
|
|
const char* identifier = gameDescription.getKeyValue( "index" );
|
|
if ( string_empty( identifier ) ) {
|
|
identifier = "1";
|
|
}
|
|
return identifier;
|
|
}
|
|
|
|
void CGameDialog::AddPacksURL( StringOutputStream &URL ){
|
|
// add the URLs for the list of game packs installed
|
|
// FIXME: this is kinda hardcoded for now..
|
|
for ( const CGameDescription *iGame : mGames )
|
|
{
|
|
URL << "&Games_dlup%5B%5D=" << GameDescription_getIdentifier( *iGame );
|
|
}
|
|
}
|
|
|
|
CGameDialog g_GamesDialog;
|
|
|
|
|
|
// =============================================================================
|
|
// Widget callbacks for PrefsDlg
|
|
|
|
static void OnButtonClean( PrefsDlg *dlg ){
|
|
// make sure this is what the user wants
|
|
if ( qt_MessageBox( g_Preferences.GetWidget(),
|
|
"This will close Radiant and clean the corresponding registry entries.\n"
|
|
"Next time you start Radiant it will be good as new. Do you wish to continue?",
|
|
"Reset Registry", EMessageBoxType::Warning, eIDYES | eIDNO ) == eIDYES ) {
|
|
dlg->EndModal( QDialog::DialogCode::Rejected );
|
|
|
|
g_preferences_globals.disable_ini = true;
|
|
Preferences_Reset();
|
|
QCoreApplication::quit();
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// PrefsDlg class
|
|
|
|
/*
|
|
========
|
|
|
|
very first prefs init deals with selecting the game and the game tools path
|
|
then we can load .ini stuff
|
|
|
|
using prefs / ini settings:
|
|
those are per-game
|
|
|
|
look in ~/.radiant/<version>/gamename
|
|
========
|
|
*/
|
|
|
|
#define PREFS_LOCAL_FILENAME "local.pref"
|
|
|
|
void PrefsDlg::Init(){
|
|
// m_global_rc_path has been set above
|
|
// m_rc_path is for game specific preferences
|
|
// takes the form: global-pref-path/gamename/prefs-file
|
|
|
|
// this is common to win32 and Linux init now
|
|
// game sub-dir
|
|
m_rc_path = StringStream( m_global_rc_path, g_pGameDescription->mGameFile.c_str(), '/' );
|
|
Q_mkdir( m_rc_path.c_str() );
|
|
|
|
// then the ini file
|
|
m_inipath = StringStream( m_rc_path, PREFS_LOCAL_FILENAME );
|
|
}
|
|
|
|
|
|
typedef std::list<PreferenceGroupCallback> PreferenceGroupCallbacks;
|
|
|
|
inline void PreferenceGroupCallbacks_constructGroup( const PreferenceGroupCallbacks& callbacks, PreferenceGroup& group ){
|
|
for ( PreferenceGroupCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i )
|
|
{
|
|
( *i )( group );
|
|
}
|
|
}
|
|
|
|
|
|
inline void PreferenceGroupCallbacks_pushBack( PreferenceGroupCallbacks& callbacks, const PreferenceGroupCallback& callback ){
|
|
callbacks.push_back( callback );
|
|
}
|
|
|
|
typedef std::list<PreferencesPageCallback> PreferencesPageCallbacks;
|
|
|
|
inline void PreferencesPageCallbacks_constructPage( const PreferencesPageCallbacks& callbacks, PreferencesPage& page ){
|
|
for ( PreferencesPageCallbacks::const_iterator i = callbacks.begin(); i != callbacks.end(); ++i )
|
|
{
|
|
( *i )( page );
|
|
}
|
|
}
|
|
|
|
inline void PreferencesPageCallbacks_pushBack( PreferencesPageCallbacks& callbacks, const PreferencesPageCallback& callback ){
|
|
callbacks.push_back( callback );
|
|
}
|
|
|
|
PreferencesPageCallbacks g_gamePreferences;
|
|
void PreferencesDialog_addGamePreferences( const PreferencesPageCallback& callback ){
|
|
PreferencesPageCallbacks_pushBack( g_gamePreferences, callback );
|
|
}
|
|
PreferenceGroupCallbacks g_gameCallbacks;
|
|
void PreferencesDialog_addGamePage( const PreferenceGroupCallback& callback ){
|
|
PreferenceGroupCallbacks_pushBack( g_gameCallbacks, callback );
|
|
}
|
|
|
|
PreferencesPageCallbacks g_interfacePreferences;
|
|
void PreferencesDialog_addInterfacePreferences( const PreferencesPageCallback& callback ){
|
|
PreferencesPageCallbacks_pushBack( g_interfacePreferences, callback );
|
|
}
|
|
PreferenceGroupCallbacks g_interfaceCallbacks;
|
|
void PreferencesDialog_addInterfacePage( const PreferenceGroupCallback& callback ){
|
|
PreferenceGroupCallbacks_pushBack( g_interfaceCallbacks, callback );
|
|
}
|
|
|
|
PreferencesPageCallbacks g_displayPreferences;
|
|
void PreferencesDialog_addDisplayPreferences( const PreferencesPageCallback& callback ){
|
|
PreferencesPageCallbacks_pushBack( g_displayPreferences, callback );
|
|
}
|
|
PreferenceGroupCallbacks g_displayCallbacks;
|
|
void PreferencesDialog_addDisplayPage( const PreferenceGroupCallback& callback ){
|
|
PreferenceGroupCallbacks_pushBack( g_displayCallbacks, callback );
|
|
}
|
|
|
|
PreferencesPageCallbacks g_settingsPreferences;
|
|
void PreferencesDialog_addSettingsPreferences( const PreferencesPageCallback& callback ){
|
|
PreferencesPageCallbacks_pushBack( g_settingsPreferences, callback );
|
|
}
|
|
PreferenceGroupCallbacks g_settingsCallbacks;
|
|
void PreferencesDialog_addSettingsPage( const PreferenceGroupCallback& callback ){
|
|
PreferenceGroupCallbacks_pushBack( g_settingsCallbacks, callback );
|
|
}
|
|
|
|
void Widget_connectToggleDependency( QWidget* self, QCheckBox* toggleButton ){
|
|
class EnabledTracker : public QObject
|
|
{
|
|
QCheckBox *const m_checkbox;
|
|
QWidget *const m_dependent;
|
|
public:
|
|
EnabledTracker( QCheckBox *checkbox, QWidget *dependent ) : QObject( checkbox ), m_checkbox( checkbox ), m_dependent( dependent ){
|
|
m_checkbox->installEventFilter( this );
|
|
}
|
|
protected:
|
|
bool eventFilter( QObject *obj, QEvent *event ) override {
|
|
if( event->type() == QEvent::EnabledChange ) {
|
|
m_dependent->setEnabled( m_checkbox->checkState() && m_checkbox->isEnabled() );
|
|
}
|
|
return QObject::eventFilter( obj, event ); // standard event processing
|
|
}
|
|
};
|
|
new EnabledTracker( toggleButton, self ); // track graying out for chained dependencies
|
|
QObject::connect( toggleButton, &QCheckBox::stateChanged, [self, toggleButton]( int state ){ // track being checked
|
|
self->setEnabled( state && toggleButton->isEnabled() );
|
|
} );
|
|
self->setEnabled( toggleButton->checkState() && toggleButton->isEnabled() ); // apply dependency effect right away
|
|
}
|
|
void Widget_connectToggleDependency( QCheckBox* self, QCheckBox* toggleButton ){
|
|
Widget_connectToggleDependency( static_cast<QWidget*>( self ), toggleButton );
|
|
}
|
|
|
|
|
|
QStandardItem* PreferenceTree_appendPage( QStandardItemModel* model, QStandardItem* parent, const char* name, int pageIndex ){
|
|
auto item = new QStandardItem( name );
|
|
item->setData( pageIndex, Qt::ItemDataRole::UserRole );
|
|
parent->appendRow( item );
|
|
return item;
|
|
}
|
|
|
|
auto PreferencePages_addPage( QStackedWidget* notebook, const char* name ){
|
|
auto frame = new QGroupBox( name );
|
|
auto grid = new QGridLayout( frame );
|
|
grid->setAlignment( Qt::AlignmentFlag::AlignTop );
|
|
grid->setColumnStretch( 0, 111 );
|
|
grid->setColumnStretch( 1, 333 );
|
|
return std::pair( notebook->addWidget( frame ), grid );
|
|
}
|
|
|
|
class PreferenceTreeGroup : public PreferenceGroup
|
|
{
|
|
Dialog& m_dialog;
|
|
QStackedWidget* m_notebook;
|
|
QStandardItemModel* m_model;
|
|
QStandardItem *m_group;
|
|
public:
|
|
PreferenceTreeGroup( Dialog& dialog, QStackedWidget* notebook, QStandardItemModel* model, QStandardItem *group ) :
|
|
m_dialog( dialog ),
|
|
m_notebook( notebook ),
|
|
m_model( model ),
|
|
m_group( group ){
|
|
}
|
|
PreferencesPage createPage( const char* treeName, const char* frameName ) override {
|
|
const auto [ pageIndex, layout ] = PreferencePages_addPage( m_notebook, frameName );
|
|
PreferenceTree_appendPage( m_model, m_group, treeName, pageIndex );
|
|
return PreferencesPage( m_dialog, layout );
|
|
}
|
|
};
|
|
|
|
void PrefsDlg::BuildDialog(){
|
|
PreferencesDialog_addInterfacePreferences( FreeCaller1<PreferencesPage&, Interface_constructPreferences>() );
|
|
|
|
GetWidget()->setWindowTitle( "NetRadiant Preferences" );
|
|
|
|
{
|
|
auto grid = new QGridLayout( GetWidget() );
|
|
grid->setSizeConstraint( QLayout::SizeConstraint::SetFixedSize );
|
|
{
|
|
auto buttons = new QDialogButtonBox( QDialogButtonBox::StandardButton::Ok | QDialogButtonBox::StandardButton::Cancel );
|
|
grid->addWidget( buttons, 1, 1 );
|
|
QObject::connect( buttons, &QDialogButtonBox::accepted, GetWidget(), &QDialog::accept );
|
|
QObject::connect( buttons, &QDialogButtonBox::rejected, GetWidget(), &QDialog::reject );
|
|
QObject::connect( buttons->addButton( "Clean", QDialogButtonBox::ButtonRole::ResetRole ), &QPushButton::clicked, [this](){ OnButtonClean( this ); } );
|
|
}
|
|
|
|
{
|
|
{
|
|
// prefs pages notebook
|
|
m_notebook = new QStackedWidget;
|
|
grid->addWidget( m_notebook, 0, 1 );
|
|
{
|
|
m_treeview = new QTreeView;
|
|
m_treeview->setHeaderHidden( true );
|
|
m_treeview->setEditTriggers( QAbstractItemView::EditTrigger::NoEditTriggers );
|
|
m_treeview->setUniformRowHeights( true ); // optimization
|
|
m_treeview->setHorizontalScrollBarPolicy( Qt::ScrollBarPolicy::ScrollBarAlwaysOff );
|
|
m_treeview->setSizeAdjustPolicy( QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents ); // scroll area will inherit column size
|
|
m_treeview->setSizePolicy( QSizePolicy::Policy::Fixed, m_treeview->sizePolicy().verticalPolicy() );
|
|
m_treeview->header()->setStretchLastSection( false ); // non greedy column sizing; + QHeaderView::ResizeMode::ResizeToContents = no text elision 🤷♀️
|
|
m_treeview->header()->setSectionResizeMode( QHeaderView::ResizeMode::ResizeToContents );
|
|
grid->addWidget( m_treeview, 0, 0, 2, 1 );
|
|
|
|
// store display name in column #0 and page index in data( Qt::ItemDataRole::UserRole )
|
|
auto model = new QStandardItemModel( m_treeview );
|
|
m_treeview->setModel( model );
|
|
|
|
QObject::connect( m_treeview->selectionModel(), &QItemSelectionModel::currentChanged, [this]( const QModelIndex& current ){
|
|
m_notebook->setCurrentIndex( current.data( Qt::ItemDataRole::UserRole ).toInt() );
|
|
} );
|
|
|
|
{
|
|
/********************************************************************/
|
|
/* Add preference tree options */
|
|
/********************************************************************/
|
|
{
|
|
const auto [ pageIndex, layout ] = PreferencePages_addPage( m_notebook, "Global Preferences" );
|
|
{
|
|
PreferencesPage preferencesPage( *this, layout );
|
|
Global_constructPreferences( preferencesPage );
|
|
}
|
|
QStandardItem *group = PreferenceTree_appendPage( model, model->invisibleRootItem(), "Global", pageIndex );
|
|
{
|
|
const auto [ pageIndex, layout ] = PreferencePages_addPage( m_notebook, "Game" );
|
|
PreferencesPage preferencesPage( *this, layout );
|
|
g_GamesDialog.CreateGlobalFrame( preferencesPage, false );
|
|
|
|
PreferenceTree_appendPage( model, group, "Game", pageIndex );
|
|
}
|
|
}
|
|
|
|
{
|
|
const auto [ pageIndex, layout ] = PreferencePages_addPage( m_notebook, "Game Settings" );
|
|
{
|
|
PreferencesPage preferencesPage( *this, layout );
|
|
Game_constructPreferences( preferencesPage );
|
|
PreferencesPageCallbacks_constructPage( g_gamePreferences, preferencesPage );
|
|
}
|
|
|
|
QStandardItem *group = PreferenceTree_appendPage( model, model->invisibleRootItem(), "Game", pageIndex );
|
|
PreferenceTreeGroup preferenceGroup( *this, m_notebook, model, group );
|
|
|
|
PreferenceGroupCallbacks_constructGroup( g_gameCallbacks, preferenceGroup );
|
|
}
|
|
|
|
{
|
|
const auto [ pageIndex, layout ] = PreferencePages_addPage( m_notebook, "Interface Preferences" );
|
|
{
|
|
PreferencesPage preferencesPage( *this, layout );
|
|
PreferencesPageCallbacks_constructPage( g_interfacePreferences, preferencesPage );
|
|
}
|
|
|
|
QStandardItem *group = PreferenceTree_appendPage( model, model->invisibleRootItem(), "Interface", pageIndex );
|
|
PreferenceTreeGroup preferenceGroup( *this, m_notebook, model, group );
|
|
|
|
PreferenceGroupCallbacks_constructGroup( g_interfaceCallbacks, preferenceGroup );
|
|
}
|
|
|
|
{
|
|
const auto [ pageIndex, layout ] = PreferencePages_addPage( m_notebook, "Display Preferences" );
|
|
{
|
|
PreferencesPage preferencesPage( *this, layout );
|
|
PreferencesPageCallbacks_constructPage( g_displayPreferences, preferencesPage );
|
|
}
|
|
QStandardItem *group = PreferenceTree_appendPage( model, model->invisibleRootItem(), "Display", pageIndex );
|
|
PreferenceTreeGroup preferenceGroup( *this, m_notebook, model, group );
|
|
|
|
PreferenceGroupCallbacks_constructGroup( g_displayCallbacks, preferenceGroup );
|
|
}
|
|
|
|
{
|
|
const auto [ pageIndex, layout ] = PreferencePages_addPage( m_notebook, "General Settings" );
|
|
{
|
|
PreferencesPage preferencesPage( *this, layout );
|
|
PreferencesPageCallbacks_constructPage( g_settingsPreferences, preferencesPage );
|
|
}
|
|
|
|
QStandardItem *group = PreferenceTree_appendPage( model, model->invisibleRootItem(), "Settings", pageIndex );
|
|
PreferenceTreeGroup preferenceGroup( *this, m_notebook, model, group );
|
|
|
|
PreferenceGroupCallbacks_constructGroup( g_settingsCallbacks, preferenceGroup );
|
|
}
|
|
}
|
|
// convenience calls
|
|
m_treeview->expandAll();
|
|
m_treeview->setCurrentIndex( m_treeview->model()->index( 0, 0 ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
preferences_globals_t g_preferences_globals;
|
|
|
|
PrefsDlg g_Preferences; // global prefs instance
|
|
|
|
|
|
void PreferencesDialog_constructWindow( QWidget* main_window ){
|
|
g_Preferences.Create( main_window );
|
|
}
|
|
void PreferencesDialog_destroyWindow(){
|
|
g_Preferences.Destroy();
|
|
}
|
|
|
|
|
|
PreferenceDictionary g_preferences;
|
|
|
|
PreferenceSystem& GetPreferenceSystem(){
|
|
return g_preferences;
|
|
}
|
|
|
|
class PreferenceSystemAPI
|
|
{
|
|
PreferenceSystem* m_preferencesystem;
|
|
public:
|
|
typedef PreferenceSystem Type;
|
|
STRING_CONSTANT( Name, "*" );
|
|
|
|
PreferenceSystemAPI(){
|
|
m_preferencesystem = &GetPreferenceSystem();
|
|
}
|
|
PreferenceSystem* getTable(){
|
|
return m_preferencesystem;
|
|
}
|
|
};
|
|
|
|
#include "modulesystem/singletonmodule.h"
|
|
#include "modulesystem/moduleregistry.h"
|
|
|
|
typedef SingletonModule<PreferenceSystemAPI> PreferenceSystemModule;
|
|
typedef Static<PreferenceSystemModule> StaticPreferenceSystemModule;
|
|
StaticRegisterModule staticRegisterPreferenceSystem( StaticPreferenceSystemModule::instance() );
|
|
|
|
void Preferences_Load(){
|
|
g_GamesDialog.LoadPrefs();
|
|
|
|
globalOutputStream() << "loading local preferences from " << g_Preferences.m_inipath << '\n';
|
|
|
|
if ( !Preferences_Load( g_preferences, g_Preferences.m_inipath.c_str(), g_GamesDialog.m_sGameFile.m_value.c_str() ) ) {
|
|
globalWarningStream() << "failed to load local preferences from " << g_Preferences.m_inipath << '\n';
|
|
}
|
|
}
|
|
|
|
void Preferences_Save(){
|
|
if ( g_preferences_globals.disable_ini ) {
|
|
return;
|
|
}
|
|
|
|
g_GamesDialog.SavePrefs();
|
|
|
|
globalOutputStream() << "saving local preferences to " << g_Preferences.m_inipath << '\n';
|
|
|
|
if ( !Preferences_Save_Safe( g_preferences, g_Preferences.m_inipath.c_str() ) ) {
|
|
globalWarningStream() << "failed to save local preferences to " << g_Preferences.m_inipath << '\n';
|
|
}
|
|
}
|
|
|
|
void Preferences_Reset(){
|
|
file_remove( g_Preferences.m_inipath.c_str() );
|
|
}
|
|
|
|
|
|
void PrefsDlg::PostModal( QDialog::DialogCode code ){
|
|
if ( code == QDialog::DialogCode::Accepted ) {
|
|
Preferences_Save();
|
|
UpdateAllWindows();
|
|
}
|
|
}
|
|
|
|
std::vector<const char*> g_restart_required;
|
|
|
|
void PreferencesDialog_restartRequired( const char* staticName ){
|
|
g_restart_required.push_back( staticName );
|
|
}
|
|
|
|
void PreferencesDialog_showDialog(){
|
|
//if ( ConfirmModified( "Edit Preferences" ) && g_Preferences.DoModal() == eIDOK ) {
|
|
g_Preferences.m_treeview->setFocus(); // focus tree to have it immediately available for text search
|
|
if ( g_Preferences.DoModal() == QDialog::DialogCode::Accepted ) {
|
|
if ( !g_restart_required.empty() ) {
|
|
auto message = StringStream( "Preference changes require a restart:\n\n" );
|
|
for ( const auto i : g_restart_required )
|
|
message << i << '\n';
|
|
g_restart_required.clear();
|
|
message << "\nRestart now?";
|
|
|
|
if( qt_MessageBox( MainFrame_getWindow(), message, "Restart is required", EMessageBoxType::Question ) == eIDYES )
|
|
Radiant_Restart();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GameName_importString( const char* value ){
|
|
gamename_set( value );
|
|
}
|
|
typedef FreeCaller1<const char*, GameName_importString> GameNameImportStringCaller;
|
|
void GameName_exportString( const StringImportCallback& importer ){
|
|
importer( gamename_get() );
|
|
}
|
|
typedef FreeCaller1<const StringImportCallback&, GameName_exportString> GameNameExportStringCaller;
|
|
|
|
void GameMode_importString( const char* value ){
|
|
gamemode_set( value );
|
|
}
|
|
typedef FreeCaller1<const char*, GameMode_importString> GameModeImportStringCaller;
|
|
void GameMode_exportString( const StringImportCallback& importer ){
|
|
importer( gamemode_get() );
|
|
}
|
|
typedef FreeCaller1<const StringImportCallback&, GameMode_exportString> GameModeExportStringCaller;
|
|
|
|
|
|
void RegisterPreferences( PreferenceSystem& preferences ){
|
|
preferences.registerPreference( "CustomShaderEditorCommand", CopiedStringImportStringCaller( g_TextEditor_editorCommand ), CopiedStringExportStringCaller( g_TextEditor_editorCommand ) );
|
|
|
|
preferences.registerPreference( "GameName", GameNameImportStringCaller(), GameNameExportStringCaller() );
|
|
preferences.registerPreference( "GameMode", GameModeImportStringCaller(), GameModeExportStringCaller() );
|
|
}
|
|
|
|
void Preferences_Init(){
|
|
RegisterPreferences( GetPreferenceSystem() );
|
|
}
|