mirror of
https://github.com/TTimo/GtkRadiant.git
synced 2024-11-10 07:11:54 +00:00
0fd04ccf85
This adds a special VFS providing textures/radiant/notex and shadernotex. This way, game packs does not have to provide them using the hardcoded textures/radiant/[shader]notex path (some games prefer other prefixes). It also allows GtkRadiant to not crash at startup when a wrong game path is set. It would also prevents radiant to fail where it tries to load these textures on very early stage when the whole VFS is not yet loaded, for example on pk3dir scenario. The notex and shadernotex textures come from Unvanquished's tex-common package and are distributed under public domain-like CC0 1.0 Universal license: https://creativecommons.org/publicdomain/zero/1.0/ See: https://github.com/UnvanquishedAssets/tex-common_src.dpkdir/blob/master/about/tex-common.txt
1838 lines
46 KiB
C++
1838 lines
46 KiB
C++
/*
|
|
Copyright (C) 1999-2007 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
|
|
*/
|
|
|
|
//
|
|
// Linux stuff
|
|
//
|
|
// Leonardo Zide (leo@lokigames.com)
|
|
//
|
|
|
|
#include "stdafx.h"
|
|
#include <gtk/gtk.h>
|
|
#include <sys/stat.h>
|
|
#include "gtkmisc.h"
|
|
#include <glib/gi18n.h>
|
|
#if defined( __linux__ ) || defined( __FreeBSD__ ) || defined( __APPLE__ )
|
|
#include <unistd.h>
|
|
#include <X11/keysym.h>
|
|
#include <gdk/gdkx.h>
|
|
#include <gdk/gdkprivate.h>
|
|
#endif
|
|
// for the logging part
|
|
#include <fcntl.h>
|
|
#include <sys/types.h>
|
|
|
|
QEGlobals_t g_qeglobals;
|
|
QEGlobals_GUI_t g_qeglobals_gui;
|
|
|
|
// leo: Track memory allocations for debugging
|
|
// NOTE TTimo this was never used and probably not relevant
|
|
// there are tools to do that
|
|
#ifdef MEM_DEBUG
|
|
|
|
static GList *memblocks;
|
|
|
|
void* debug_malloc( size_t size, const char* file, int line ){
|
|
void *buf = g_malloc( size + 8 );
|
|
|
|
*( (const char**)buf ) = file;
|
|
buf = (char*)buf + 4;
|
|
*( (int*)buf ) = line;
|
|
buf = (char*)buf + 4;
|
|
|
|
memblocks = g_list_append( memblocks, buf );
|
|
|
|
return buf;
|
|
}
|
|
|
|
void debug_free( void *buf, const char* file, int line ){
|
|
const char *f;
|
|
int l;
|
|
|
|
if ( g_list_find( memblocks, buf ) ) {
|
|
memblocks = g_list_remove( memblocks, buf );
|
|
|
|
buf = (char*)buf - 4;
|
|
l = *( (int*)buf );
|
|
buf = (char*)buf - 4;
|
|
f = *( (const char**)buf );
|
|
|
|
Sys_FPrintf( SYS_DBG, "free: %s %d", file, line );
|
|
Sys_FPrintf( SYS_DBG, " allocated: %s %d\n", f, l );
|
|
|
|
g_free( buf );
|
|
}
|
|
// else
|
|
// free (buf); // from qmalloc, will leak unless we add this same hack to cmdlib
|
|
}
|
|
|
|
#endif
|
|
|
|
vec_t Rad_rint( vec_t in ){
|
|
if ( g_PrefsDlg.m_bNoClamp ) {
|
|
return in;
|
|
}
|
|
else{
|
|
return (float)floor( in + 0.5 );
|
|
}
|
|
}
|
|
|
|
void WINAPI QE_CheckOpenGLForErrors( void ){
|
|
char strMsg[1024];
|
|
int i = qglGetError();
|
|
if ( i != GL_NO_ERROR ) {
|
|
if ( i == GL_OUT_OF_MEMORY ) {
|
|
sprintf( strMsg, _( "OpenGL out of memory error %s\nDo you wish to save before exiting?" ), qgluErrorString( (GLenum)i ) );
|
|
if ( gtk_MessageBox( g_pParentWnd->m_pWidget, strMsg, _( "Radiant Error" ), MB_YESNO ) == IDYES ) {
|
|
Map_SaveFile( NULL, false );
|
|
}
|
|
_exit( 1 );
|
|
}
|
|
else
|
|
{
|
|
Sys_FPrintf( SYS_WRN, "WARNING: OpenGL Error %s\n", qgluErrorString( (GLenum)i ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: don't this function, use VFS instead
|
|
char *ExpandReletivePath( char *p ){
|
|
static char temp[1024];
|
|
const char *base;
|
|
|
|
if ( !p || !p[0] ) {
|
|
return NULL;
|
|
}
|
|
if ( p[0] == '/' || p[0] == '\\' ) {
|
|
return p;
|
|
}
|
|
|
|
base = ValueForKey( g_qeglobals.d_project_entity, "basepath" );
|
|
sprintf( temp, "%s/%s", base, p );
|
|
return temp;
|
|
}
|
|
|
|
char *copystring( char *s ){
|
|
char *b;
|
|
b = (char*)malloc( strlen( s ) + 1 );
|
|
strcpy( b,s );
|
|
return b;
|
|
}
|
|
|
|
|
|
bool DoesFileExist( const char* pBuff, long& lSize ){
|
|
FileStream file;
|
|
if ( file.Open( pBuff, "r" ) ) {
|
|
lSize += file.GetLength();
|
|
file.Close();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Map_Snapshot(){
|
|
CString strMsg;
|
|
|
|
// I hope the modified flag is kept correctly up to date
|
|
if ( !modified ) {
|
|
return;
|
|
}
|
|
|
|
// we need to do the following
|
|
// 1. make sure the snapshot directory exists (create it if it doesn't)
|
|
// 2. find out what the lastest save is based on number
|
|
// 3. inc that and save the map
|
|
CString strOrgPath, strOrgFile;
|
|
ExtractPath_and_Filename( currentmap, strOrgPath, strOrgFile );
|
|
AddSlash( strOrgPath );
|
|
strOrgPath += "snapshots";
|
|
bool bGo = true;
|
|
struct stat Stat;
|
|
if ( stat( strOrgPath, &Stat ) == -1 ) {
|
|
#ifdef _WIN32
|
|
bGo = ( _mkdir( strOrgPath ) != -1 );
|
|
#endif
|
|
|
|
#if defined( __linux__ ) || defined( __FreeBSD__ ) || defined( __APPLE__ )
|
|
bGo = ( mkdir( strOrgPath,0755 ) != -1 );
|
|
#endif
|
|
}
|
|
AddSlash( strOrgPath );
|
|
if ( bGo ) {
|
|
int nCount = 0;
|
|
long lSize = 0;
|
|
CString strNewPath;
|
|
strNewPath = strOrgPath;
|
|
strNewPath += strOrgFile;
|
|
|
|
// QB - snapshots now follow the format: <mapname>.<snapnum>.<ext>
|
|
// **NOTE** atm snapshots must end with a .map (or .xmap) ext (this is why they were broken)
|
|
CString strOldEXT = "map"; //default to .map
|
|
const char* type = strrchr( strOrgFile.GetBuffer(),'.' );
|
|
if ( type != NULL ) { strOldEXT = ++type; }; // get the ext for later.
|
|
StripExtension(strNewPath); // then strip it from the new path
|
|
//
|
|
|
|
CString strFile;
|
|
while ( bGo )
|
|
{
|
|
char buf[PATH_MAX];
|
|
//sprintf( buf, "%s.%i", strNewPath.GetBuffer(), nCount );
|
|
// snapshot will now end with a known ext.
|
|
sprintf( buf, "%s.%i.%s", strNewPath.GetBuffer(), nCount, strOldEXT.GetBuffer() );
|
|
strFile = buf;
|
|
bGo = DoesFileExist( strFile, lSize );
|
|
nCount++;
|
|
}
|
|
// strFile has the next available slot
|
|
Map_SaveFile( strFile, false );
|
|
// it is still a modified map (we enter this only if this is a modified map)
|
|
Sys_SetTitle( currentmap );
|
|
Sys_MarkMapModified();
|
|
if ( lSize > 12 * 1024 * 1024 ) { // total size of saves > 4 mb
|
|
Sys_Printf( "The snapshot files in %s total more than 4 megabytes. You might consider cleaning up.", strOrgPath.GetBuffer() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
strMsg.Format( "Snapshot save failed.. unabled to create directory\n%s", strOrgPath.GetBuffer() );
|
|
gtk_MessageBox( g_pParentWnd->m_pWidget, strMsg );
|
|
}
|
|
strOrgPath = "";
|
|
strOrgFile = "";
|
|
}
|
|
/*
|
|
===============
|
|
QE_CheckAutoSave
|
|
|
|
If five minutes have passed since making a change
|
|
and the map hasn't been saved, save it out.
|
|
===============
|
|
*/
|
|
|
|
|
|
void QE_CheckAutoSave( void ){
|
|
static time_t s_start;
|
|
time_t now;
|
|
time( &now );
|
|
|
|
if ( modified != 1 || !s_start ) {
|
|
s_start = now;
|
|
return;
|
|
}
|
|
|
|
if ( ( now - s_start ) > ( 60 * g_PrefsDlg.m_nAutoSave ) ) {
|
|
if ( g_PrefsDlg.m_bAutoSave ) {
|
|
CString strMsg;
|
|
strMsg = g_PrefsDlg.m_bSnapShots ? "Autosaving snapshot..." : "Autosaving...";
|
|
Sys_Printf( strMsg );
|
|
Sys_Printf( "\n" );
|
|
Sys_Status( strMsg,0 );
|
|
|
|
// only snapshot if not working on a default map
|
|
if ( g_PrefsDlg.m_bSnapShots && stricmp( currentmap, "unnamed.map" ) != 0 ) {
|
|
Map_Snapshot();
|
|
}
|
|
else
|
|
{
|
|
Map_SaveFile( ValueForKey( g_qeglobals.d_project_entity, "autosave" ), false );
|
|
}
|
|
|
|
Sys_Status( "Autosaving...Saved.", 0 );
|
|
modified = 2;
|
|
}
|
|
else
|
|
{
|
|
Sys_Printf( "Autosave skipped...\n" );
|
|
Sys_Status( "Autosave skipped...", 0 );
|
|
}
|
|
s_start = now;
|
|
}
|
|
}
|
|
|
|
|
|
// NOTE TTimo we don't like that BuildShortPathName too much
|
|
// the VFS provides a vfsCleanFileName which should perform the cleanup tasks
|
|
// in the long run I'd like to completely get rid of this
|
|
|
|
// used to be disabled, but caused problems
|
|
|
|
// can't work with long win32 names until the BSP commands are not working differently
|
|
#ifdef _WIN32
|
|
int BuildShortPathName( const char* pPath, char* pBuffer, int nBufferLen ){
|
|
char *pFile = NULL;
|
|
int nResult = GetFullPathName( pPath, nBufferLen, pBuffer, &pFile );
|
|
nResult = GetShortPathName( pPath, pBuffer, nBufferLen );
|
|
if ( nResult == 0 ) {
|
|
strcpy( pBuffer, pPath ); // Use long filename
|
|
}
|
|
return nResult;
|
|
}
|
|
#endif
|
|
|
|
#if defined( __linux__ ) || defined( __FreeBSD__ ) || defined( __APPLE__ )
|
|
int BuildShortPathName( const char* pPath, char* pBuffer, int nBufferLen ){
|
|
// remove /../ from directories
|
|
const char *scr = pPath; char *dst = pBuffer;
|
|
for ( int i = 0; ( i < nBufferLen ) && ( *scr != 0 ); i++ )
|
|
{
|
|
if ( *scr == '/' && *( scr + 1 ) == '.' && *( scr + 2 ) == '.' ) {
|
|
scr += 3;
|
|
while ( dst != pBuffer && *( --dst ) != '/' )
|
|
{
|
|
i--;
|
|
}
|
|
}
|
|
|
|
*dst = *scr;
|
|
|
|
scr++; dst++;
|
|
}
|
|
*dst = 0;
|
|
|
|
return strlen( pBuffer );
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
const char *g_pPathFixups[]=
|
|
{
|
|
"basepath",
|
|
"autosave",
|
|
};
|
|
|
|
const int g_nPathFixupCount = sizeof(g_pPathFixups) / sizeof(const char*);
|
|
|
|
void QE_CheckProjectEntity()
|
|
{
|
|
char *pFile;
|
|
char pBuff[PATH_MAX];
|
|
char pNewPath[PATH_MAX];
|
|
for (int i = 0; i < g_nPathFixupCount; i++)
|
|
{
|
|
char *pPath = ValueForKey (g_qeglobals.d_project_entity, g_pPathFixups[i]);
|
|
|
|
strcpy (pNewPath, pPath);
|
|
if (pPath[0] != '\\' && pPath[0] != '/')
|
|
if (GetFullPathName(pPath, PATH_MAX, pBuff, &pFile))
|
|
strcpy (pNewPath, pBuff);
|
|
|
|
BuildShortPathName (pNewPath, pBuff, PATH_MAX);
|
|
|
|
// check it's not ending with a filename seperator
|
|
if (pBuff[strlen(pBuff)-1] == '/' || pBuff[strlen(pBuff)-1] == '\\')
|
|
{
|
|
Sys_FPrintf(SYS_WRN, "WARNING: \"%s\" path in the project file has an ending file seperator, fixing.\n", g_pPathFixups[i]);
|
|
pBuff[strlen(pBuff)-1]=0;
|
|
}
|
|
|
|
SetKeyValue(g_qeglobals.d_project_entity, g_pPathFixups[i], pBuff);
|
|
}
|
|
}
|
|
*/
|
|
|
|
void HandleXMLError( void* ctxt, const char* text, ... ){
|
|
va_list argptr;
|
|
static char buf[32768];
|
|
|
|
va_start( argptr,text );
|
|
vsprintf( buf, text, argptr );
|
|
Sys_FPrintf( SYS_ERR, "XML %s\n", buf );
|
|
va_end( argptr );
|
|
}
|
|
|
|
#define DTD_BUFFER_LENGTH 1024
|
|
xmlDocPtr ParseXMLStream( IDataStream *stream, bool validate = false ){
|
|
xmlDocPtr doc = NULL;
|
|
bool wellFormed = false, valid = false;
|
|
int res, size = 1024;
|
|
char chars[1024];
|
|
xmlParserCtxtPtr ctxt;
|
|
|
|
//if(validate)
|
|
// xmlDoValidityCheckingDefaultValue = 1;
|
|
//else
|
|
xmlDoValidityCheckingDefaultValue = 0;
|
|
xmlSetGenericErrorFunc( NULL, HandleXMLError );
|
|
|
|
// SPoG
|
|
// HACK: use AppPath to resolve DTD location
|
|
// do a buffer-safe string copy and concatenate
|
|
int i;
|
|
char* w;
|
|
const char* r;
|
|
char buf[DTD_BUFFER_LENGTH];
|
|
|
|
w = buf;
|
|
i = 0;
|
|
// copy
|
|
//assert(g_strAppPath.GetBuffer() != NULL);
|
|
for ( r = g_strAppPath.GetBuffer(); i < DTD_BUFFER_LENGTH && *r != '\0'; i++, r++ ) w[i] = *r;
|
|
// concatenate
|
|
for ( r = "dtds/"; i < DTD_BUFFER_LENGTH && *r != '\0'; i++, r++ ) w[i] = *r;
|
|
// terminate
|
|
w[i] = '\0';
|
|
|
|
if ( i == DTD_BUFFER_LENGTH ) {
|
|
HandleXMLError( NULL, "ERROR: buffer overflow: DTD path length too large\n" );
|
|
return NULL;
|
|
}
|
|
|
|
res = stream->Read( chars, 4 );
|
|
if ( res > 0 ) {
|
|
ctxt = xmlCreatePushParserCtxt( NULL, NULL, chars, res, buf );
|
|
|
|
while ( ( res = stream->Read( chars, size ) ) > 0 )
|
|
{
|
|
xmlParseChunk( ctxt, chars, res, 0 );
|
|
}
|
|
xmlParseChunk( ctxt, chars, 0, 1 );
|
|
doc = ctxt->myDoc;
|
|
|
|
wellFormed = ( ctxt->wellFormed == 1 );
|
|
valid = ( ctxt->valid == 1 );
|
|
|
|
xmlFreeParserCtxt( ctxt );
|
|
}
|
|
|
|
if ( wellFormed && ( !validate || ( validate && valid ) ) ) {
|
|
return doc;
|
|
}
|
|
|
|
if ( doc != NULL ) {
|
|
xmlFreeDoc( doc );
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
xmlDocPtr ParseXMLFile( const char* filename, bool validate = false ){
|
|
FileStream stream;
|
|
if ( stream.Open( filename, "r" ) ) {
|
|
return ParseXMLStream( &stream, validate );
|
|
}
|
|
|
|
Sys_FPrintf( SYS_ERR, "Failed to open file: %s\n",filename );
|
|
return NULL;
|
|
}
|
|
|
|
// copy a string r to a buffer w
|
|
// replace $string as appropriate
|
|
static void ReplaceTemplates( char* w, const char* r ){
|
|
const char *p;
|
|
const char *__ENGINEPATH = "TEMPLATEenginepath";
|
|
const char *__USERHOMEPATH = "TEMPLATEuserhomepath";
|
|
const char *__TOOLSPATH = "TEMPLATEtoolspath";
|
|
const char *__EXECPATH = "TEMPLATEexecpath";
|
|
const char *__BASEDIR = "TEMPLATEbasedir";
|
|
const char *__APPPATH = "TEMPLATEapppath";
|
|
const char *__Q3MAP2 = "TEMPLATEq3map2";
|
|
|
|
// iterate through string r
|
|
while ( *r != '\0' )
|
|
{
|
|
// check for special character
|
|
if ( *r == '$' ) {
|
|
if ( strncmp( r + 1, __ENGINEPATH, strlen( __ENGINEPATH ) ) == 0 ) {
|
|
r += strlen( __ENGINEPATH ) + 1;
|
|
p = g_pGameDescription->mEnginePath.GetBuffer();
|
|
}
|
|
else if ( strncmp( r + 1, __USERHOMEPATH, strlen( __USERHOMEPATH ) ) == 0 ) {
|
|
r += strlen( __USERHOMEPATH ) + 1;
|
|
p = g_qeglobals.m_strHomeGame.GetBuffer();
|
|
}
|
|
else if ( strncmp( r + 1, __BASEDIR, strlen( __BASEDIR ) ) == 0 ) {
|
|
r += strlen( __BASEDIR ) + 1;
|
|
p = g_pGameDescription->mBaseGame;
|
|
}
|
|
else if ( strncmp( r + 1, __TOOLSPATH, strlen( __TOOLSPATH ) ) == 0 ) {
|
|
r += strlen( __TOOLSPATH ) + 1;
|
|
p = g_strGameToolsPath.GetBuffer();
|
|
}
|
|
else if ( strncmp( r + 1, __EXECPATH, strlen( __EXECPATH ) ) == 0 ) {
|
|
r += strlen( __EXECPATH ) + 1;
|
|
p = g_strExecutablesPath.GetBuffer();
|
|
}
|
|
else if ( strncmp( r + 1, __APPPATH, strlen( __APPPATH ) ) == 0 ) {
|
|
r += strlen( __APPPATH ) + 1;
|
|
p = g_strAppPath.GetBuffer();
|
|
}
|
|
else if ( strncmp( r + 1, __Q3MAP2, strlen( __Q3MAP2 ) ) == 0 ) {
|
|
r += strlen( __Q3MAP2 ) + 1;
|
|
// see https://github.com/TTimo/GtkRadiant/issues/116
|
|
#ifdef _WIN32
|
|
if ( g_PrefsDlg.m_bx64q3map2 ) {
|
|
p = "x64/q3map2";
|
|
} else
|
|
#endif
|
|
{
|
|
p = "q3map2";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
r++;
|
|
p = "$";
|
|
}
|
|
|
|
while ( *p != '\0' ) *w++ = *p++;
|
|
}
|
|
else{ *w++ = *r++; }
|
|
}
|
|
*w = '\0';
|
|
}
|
|
|
|
/*
|
|
Load up a project file to get the current version
|
|
*/
|
|
int QE_GetTemplateVersionForProject( const char * projectfile ) {
|
|
xmlDocPtr doc;
|
|
xmlNodePtr node, project;
|
|
int ret;
|
|
|
|
Sys_Printf( "Scanning template version in %s\n", projectfile );
|
|
doc = ParseXMLFile( projectfile, true );
|
|
if ( doc == NULL ) {
|
|
Sys_FPrintf( SYS_ERR, "ERROR: XML parse failed %s\n", projectfile );
|
|
return 0;
|
|
}
|
|
node = doc->children;
|
|
while ( node != NULL && node->type != XML_DTD_NODE ) {
|
|
node = node->next;
|
|
}
|
|
if ( node == NULL || strcmp( (char*)node->name, "project" ) != 0 ) {
|
|
Sys_FPrintf( SYS_ERR, "ERROR: invalid file type %s\n", projectfile );
|
|
xmlFree( doc );
|
|
return 0;
|
|
}
|
|
while ( node->type != XML_ELEMENT_NODE ) {
|
|
node = node->next;
|
|
}
|
|
// <project>
|
|
project = node;
|
|
|
|
for ( node = project->children; node != NULL; node = node->next ) {
|
|
if ( node->type != XML_ELEMENT_NODE ) {
|
|
continue;
|
|
}
|
|
if ( strcmp( (char*)node->properties->children->content, "template_version" ) == 0 ) {
|
|
ret = atoi( (char*)node->properties->next->children->content );
|
|
xmlFreeDoc( doc );
|
|
return ret;
|
|
}
|
|
}
|
|
Sys_FPrintf( SYS_WRN, "Version key not found in %s\n", projectfile );
|
|
xmlFreeDoc( doc );
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
QE_LoadProject
|
|
NOTE: rather than bumping "version", consider bumping "template_version" (see above)
|
|
NOTE: when QE_LoadProject is called, the prefs are updated with path to the latest project and saved on disk
|
|
===========
|
|
*/
|
|
bool QE_LoadProject( const char *projectfile ){
|
|
char buf[1024];
|
|
xmlDocPtr doc;
|
|
xmlNodePtr node, project;
|
|
|
|
Sys_Printf( "Loading project file: \"%s\"\n", projectfile );
|
|
doc = ParseXMLFile( projectfile, true );
|
|
|
|
if ( doc == NULL ) {
|
|
return false;
|
|
}
|
|
|
|
node = doc->children;
|
|
while ( node != NULL && node->type != XML_DTD_NODE ) node = node->next;
|
|
if ( node == NULL || strcmp( (char*)node->name, "project" ) != 0 ) {
|
|
Sys_FPrintf( SYS_ERR, "ERROR: invalid file type\n" );
|
|
return false;
|
|
}
|
|
|
|
while ( node->type != XML_ELEMENT_NODE ) node = node->next;
|
|
// <project>
|
|
project = node;
|
|
|
|
if ( g_qeglobals.d_project_entity != NULL ) {
|
|
Entity_Free( g_qeglobals.d_project_entity );
|
|
}
|
|
g_qeglobals.d_project_entity = Entity_Alloc();
|
|
|
|
for ( node = project->children; node != NULL; node = node->next )
|
|
{
|
|
if ( node->type != XML_ELEMENT_NODE ) {
|
|
continue;
|
|
}
|
|
|
|
// <key>
|
|
ReplaceTemplates( buf, (char*)node->properties->next->children->content );
|
|
|
|
SetKeyValue( g_qeglobals.d_project_entity, (char*)node->properties->children->content, buf );
|
|
}
|
|
|
|
xmlFreeDoc( doc );
|
|
|
|
// project file version checking
|
|
// add a version checking to avoid people loading later versions of the project file and bitching
|
|
int ver = IntForKey( g_qeglobals.d_project_entity, "version" );
|
|
if ( ver > PROJECT_VERSION ) {
|
|
char strMsg[1024];
|
|
sprintf( strMsg, _( "This is a version %d project file. This build only supports <=%d project files.\n"
|
|
"Please choose another project file or upgrade your version of Radiant." ), ver, PROJECT_VERSION );
|
|
gtk_MessageBox( g_pParentWnd->m_pWidget, strMsg, _( "Can't load project file" ), MB_ICONERROR | MB_OK );
|
|
// set the project file to nothing so we are sure we'll ask next time?
|
|
g_PrefsDlg.m_strLastProject = "";
|
|
g_PrefsDlg.SavePrefs();
|
|
return false;
|
|
}
|
|
|
|
// set here some default project settings you need
|
|
if ( strlen( ValueForKey( g_qeglobals.d_project_entity, "brush_primit" ) ) == 0 ) {
|
|
SetKeyValue( g_qeglobals.d_project_entity, "brush_primit", "0" );
|
|
}
|
|
|
|
g_qeglobals.m_bBrushPrimitMode = IntForKey( g_qeglobals.d_project_entity, "brush_primit" );
|
|
|
|
g_qeglobals.m_strHomeMaps = g_qeglobals.m_strHomeGame;
|
|
const char* str = ValueForKey( g_qeglobals.d_project_entity, "gamename" );
|
|
if ( str[0] == '\0' ) {
|
|
str = g_pGameDescription->mBaseGame.GetBuffer();
|
|
}
|
|
g_qeglobals.m_strHomeMaps += str;
|
|
g_qeglobals.m_strHomeMaps += G_DIR_SEPARATOR;
|
|
|
|
// don't forget to create the dirs
|
|
Q_mkdir( g_qeglobals.m_strHomeGame.GetBuffer(), 0775 );
|
|
Q_mkdir( g_qeglobals.m_strHomeMaps.GetBuffer(), 0775 );
|
|
|
|
// usefull for the log file and debuggin fucked up configurations from users:
|
|
// output the basic information of the .qe4 project file
|
|
// SPoG
|
|
// all these paths should be unix format, with a trailing slash at the end
|
|
// if not.. to debug, check that the project file paths are set up correctly
|
|
Sys_Printf( "basepath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "basepath" ) );
|
|
Sys_Printf( "entitypath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "entitypath" ) );
|
|
|
|
|
|
// check whether user_project key exists..
|
|
// if not, save the current project under a new name
|
|
if ( ValueForKey( g_qeglobals.d_project_entity, "user_project" )[0] == '\0' ) {
|
|
Sys_Printf( "Loaded a template project file\n" );
|
|
|
|
// create the user_project key
|
|
SetKeyValue( g_qeglobals.d_project_entity, "user_project", "1" );
|
|
|
|
if ( IntForKey( g_qeglobals.d_project_entity, "version" ) != PROJECT_VERSION ) {
|
|
char strMsg[2048];
|
|
sprintf( strMsg,
|
|
_( "The template project '%s' has version %d. The editor binary is configured for version %d.\n"
|
|
"This indicates a problem in your setup.\n"
|
|
"I will keep going with this project till you fix this" ),
|
|
projectfile, IntForKey( g_qeglobals.d_project_entity, "version" ), PROJECT_VERSION );
|
|
gtk_MessageBox( g_pParentWnd->m_pWidget, strMsg, _( "Can't load project file" ), MB_ICONERROR | MB_OK );
|
|
}
|
|
|
|
// create the writable project file path
|
|
strcpy( buf, g_qeglobals.m_strHomeGame.GetBuffer() );
|
|
strcat( buf, g_pGameDescription->mBaseGame.GetBuffer() );
|
|
strcat( buf, G_DIR_SEPARATOR_S "scripts" G_DIR_SEPARATOR_S );
|
|
// while the filename is already in use, increment the number we add to the end
|
|
int counter = 0;
|
|
char pUser[PATH_MAX];
|
|
while ( 1 )
|
|
{
|
|
sprintf( pUser, "%suser%d." PROJECT_FILETYPE, buf, counter );
|
|
counter++;
|
|
if ( access( pUser, R_OK ) != 0 ) {
|
|
// this is the one
|
|
strcpy( buf, pUser );
|
|
break;
|
|
}
|
|
}
|
|
// saving project will cause a save prefs
|
|
g_PrefsDlg.m_strLastProject = buf;
|
|
g_PrefsDlg.m_nLastProjectVer = IntForKey( g_qeglobals.d_project_entity, "version" );
|
|
QE_SaveProject( buf );
|
|
}
|
|
else
|
|
{
|
|
// update preferences::LastProject with path of this successfully-loaded project
|
|
// save preferences
|
|
Sys_Printf( "Setting current project in prefs to \"%s\"\n", g_PrefsDlg.m_strLastProject.GetBuffer() );
|
|
g_PrefsDlg.m_strLastProject = projectfile;
|
|
g_PrefsDlg.SavePrefs();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===========
|
|
QE_SaveProject
|
|
TTimo: whenever QE_SaveProject is called, prefs are updated and saved with the path to the project
|
|
===========
|
|
*/
|
|
qboolean QE_SaveProject( const char* filename ){
|
|
Sys_Printf( "Save project file '%s'\n", filename );
|
|
|
|
xmlNodePtr node;
|
|
xmlDocPtr doc = xmlNewDoc( (xmlChar *)"1.0" );
|
|
// create DTD node
|
|
xmlCreateIntSubset( doc, (xmlChar *)"project", NULL, (xmlChar *)"project.dtd" );
|
|
// create project node
|
|
doc->children->next = xmlNewDocNode( doc, NULL, (xmlChar *)"project", NULL );
|
|
|
|
for ( epair_t* epair = g_qeglobals.d_project_entity->epairs; epair != NULL; epair = epair->next )
|
|
{
|
|
node = xmlNewChild( doc->children->next, NULL, (xmlChar *)"key", NULL );
|
|
xmlSetProp( node, (xmlChar*)"name", (xmlChar*)epair->key );
|
|
xmlSetProp( node, (xmlChar*)"value", (xmlChar*)epair->value );
|
|
}
|
|
|
|
CreateDirectoryPath( filename );
|
|
if ( xmlSaveFormatFile( filename, doc, 1 ) != -1 ) {
|
|
xmlFreeDoc( doc );
|
|
Sys_Printf( "Setting current project in prefs to \"%s\"\n", filename );
|
|
g_PrefsDlg.m_strLastProject = filename;
|
|
g_PrefsDlg.SavePrefs();
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
xmlFreeDoc( doc );
|
|
Sys_FPrintf( SYS_ERR, "failed to save project file: \"%s\"\n", filename );
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
===========
|
|
QE_KeyDown
|
|
===========
|
|
*/
|
|
#define SPEED_MOVE 32
|
|
#define SPEED_TURN 22.5
|
|
|
|
|
|
/*
|
|
===============
|
|
ConnectEntities
|
|
|
|
Sets target / targetname on the two entities selected
|
|
from the first selected to the secon
|
|
===============
|
|
*/
|
|
void ConnectEntities( void ){
|
|
entity_t *e1, *e2;
|
|
const char *target;
|
|
char *newtarg = NULL;
|
|
|
|
if ( g_qeglobals.d_select_count != 2 ) {
|
|
Sys_Status( "Must have two brushes selected", 0 );
|
|
Sys_Beep();
|
|
return;
|
|
}
|
|
|
|
e1 = g_qeglobals.d_select_order[0]->owner;
|
|
e2 = g_qeglobals.d_select_order[1]->owner;
|
|
|
|
if ( e1 == world_entity || e2 == world_entity ) {
|
|
Sys_Status( "Can't connect to the world", 0 );
|
|
Sys_Beep();
|
|
return;
|
|
}
|
|
|
|
if ( e1 == e2 ) {
|
|
Sys_Status( "Brushes are from same entity", 0 );
|
|
Sys_Beep();
|
|
return;
|
|
}
|
|
|
|
target = ValueForKey( e1, "target" );
|
|
if ( target && target[0] ) {
|
|
newtarg = g_strdup( target );
|
|
}
|
|
else
|
|
{
|
|
target = ValueForKey( e2, "targetname" );
|
|
if ( target && target[0] ) {
|
|
newtarg = g_strdup( target );
|
|
}
|
|
else{
|
|
Entity_Connect( e1, e2 );
|
|
}
|
|
}
|
|
|
|
if ( newtarg != NULL ) {
|
|
SetKeyValue( e1, "target", newtarg );
|
|
SetKeyValue( e2, "targetname", newtarg );
|
|
g_free( newtarg );
|
|
}
|
|
|
|
Sys_UpdateWindows( W_XY | W_CAMERA );
|
|
|
|
Select_Deselect();
|
|
Select_Brush( g_qeglobals.d_select_order[1] );
|
|
}
|
|
|
|
qboolean QE_SingleBrush( bool bQuiet ){
|
|
if ( ( selected_brushes.next == &selected_brushes )
|
|
|| ( selected_brushes.next->next != &selected_brushes ) ) {
|
|
if ( !bQuiet ) {
|
|
Sys_FPrintf( SYS_ERR, "ERROR: you must have a single brush selected\n" );
|
|
}
|
|
return false;
|
|
}
|
|
if ( selected_brushes.next->owner->eclass->fixedsize ) {
|
|
if ( !bQuiet ) {
|
|
Sys_FPrintf( SYS_ERR, "ERROR: you cannot manipulate fixed size entities\n" );
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void QE_InitVFS( void ){
|
|
// VFS initialization -----------------------
|
|
// we will call vfsInitDirectory, giving the directories to look in (for files in pk3's and for standalone files)
|
|
// we need to call in order, the mod ones first, then the base ones .. they will be searched in this order
|
|
// *nix systems have a dual filesystem in ~/.q3a, which is searched first .. so we need to add that too
|
|
Str directory,prefabs;
|
|
|
|
Str basePakPath = g_strAppPath.GetBuffer();
|
|
basePakPath += "base";
|
|
vfsInitDirectory( basePakPath.GetBuffer() );
|
|
|
|
// TTimo: let's leave this to HL mode for now
|
|
if ( g_pGameDescription->mGameFile == "hl.game" ) {
|
|
// Hydra: we search the "gametools" path first so that we can provide editor
|
|
// specific pk3's wads and misc files for use by the editor.
|
|
// the relevant map compiler tools will NOT use this directory, so this helps
|
|
// to ensure that editor files are not used/required in release versions of maps
|
|
// it also helps keep your editor files all in once place, with the editor modules,
|
|
// plugins, scripts and config files.
|
|
// it also helps when testing maps, as you'll know your files won't/can't be used
|
|
// by the game engine itself.
|
|
|
|
// <gametools>
|
|
directory = g_pGameDescription->mGameToolsPath;
|
|
vfsInitDirectory( directory.GetBuffer() );
|
|
}
|
|
|
|
// NOTE TTimo about the mymkdir calls .. this is a bit dirty, but a safe thing on *nix
|
|
|
|
// if we have a mod dir
|
|
if ( *ValueForKey( g_qeglobals.d_project_entity, "gamename" ) != '\0' ) {
|
|
|
|
// ~/.<gameprefix>/<fs_game>
|
|
if ( g_qeglobals.m_strHomeGame.GetLength() ) {
|
|
directory = g_qeglobals.m_strHomeGame.GetBuffer();
|
|
Q_mkdir( directory.GetBuffer(), 0775 );
|
|
directory += ValueForKey( g_qeglobals.d_project_entity, "gamename" );
|
|
Q_mkdir( directory.GetBuffer(), 0775 );
|
|
vfsInitDirectory( directory.GetBuffer() );
|
|
AddSlash( directory );
|
|
prefabs = directory;
|
|
// also create the maps dir, it will be used as prompt for load/save
|
|
directory += "maps";
|
|
Q_mkdir( directory, 0775 );
|
|
// and the prefabs dir
|
|
prefabs += "prefabs";
|
|
Q_mkdir( prefabs, 0775 );
|
|
}
|
|
|
|
// <fs_basepath>/<fs_game>
|
|
directory = g_pGameDescription->mEnginePath;
|
|
directory += ValueForKey( g_qeglobals.d_project_entity, "gamename" );
|
|
Q_mkdir( directory.GetBuffer(), 0775 );
|
|
vfsInitDirectory( directory.GetBuffer() );
|
|
AddSlash( directory );
|
|
prefabs = directory;
|
|
// also create the maps dir, it will be used as prompt for load/save
|
|
directory += "maps";
|
|
Q_mkdir( directory.GetBuffer(), 0775 );
|
|
// and the prefabs dir
|
|
prefabs += "prefabs";
|
|
Q_mkdir( prefabs, 0775 );
|
|
}
|
|
|
|
// ~/.<gameprefix>/<fs_main>
|
|
if ( g_qeglobals.m_strHomeGame.GetLength() ) {
|
|
directory = g_qeglobals.m_strHomeGame.GetBuffer();
|
|
directory += g_pGameDescription->mBaseGame;
|
|
vfsInitDirectory( directory.GetBuffer() );
|
|
}
|
|
|
|
// <fs_basepath>/<fs_main>
|
|
directory = g_pGameDescription->mEnginePath;
|
|
directory += g_pGameDescription->mBaseGame;
|
|
vfsInitDirectory( directory.GetBuffer() );
|
|
}
|
|
|
|
void QE_Init( void ){
|
|
/*
|
|
** initialize variables
|
|
*/
|
|
g_qeglobals.d_gridsize = 8;
|
|
g_qeglobals.d_showgrid = true;
|
|
|
|
QE_InitVFS();
|
|
|
|
Eclass_Init();
|
|
FillClassList(); // list in entity window
|
|
Map_Init();
|
|
|
|
GSList *texdirs = NULL;
|
|
FillTextureList( &texdirs );
|
|
FillTextureMenu( texdirs );
|
|
ClearGSList( texdirs );
|
|
FillBSPMenu();
|
|
|
|
/*
|
|
** other stuff
|
|
*/
|
|
Z_Init();
|
|
}
|
|
|
|
void WINAPI QE_ConvertDOSToUnixName( char *dst, const char *src ){
|
|
while ( *src )
|
|
{
|
|
if ( *src == '\\' ) {
|
|
*dst = '/';
|
|
}
|
|
else{
|
|
*dst = *src;
|
|
}
|
|
dst++; src++;
|
|
}
|
|
*dst = 0;
|
|
}
|
|
|
|
int g_numbrushes, g_numentities;
|
|
|
|
void QE_CountBrushesAndUpdateStatusBar( void ){
|
|
static int s_lastbrushcount, s_lastentitycount;
|
|
static qboolean s_didonce;
|
|
|
|
//entity_t *e;
|
|
brush_t *b, *next;
|
|
|
|
g_numbrushes = 0;
|
|
g_numentities = 0;
|
|
|
|
if ( active_brushes.next != NULL ) {
|
|
for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; b = next )
|
|
{
|
|
next = b->next;
|
|
if ( b->brush_faces ) {
|
|
if ( !b->owner->eclass->fixedsize ) {
|
|
g_numbrushes++;
|
|
}
|
|
else{
|
|
g_numentities++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
if ( entities.next != NULL )
|
|
{
|
|
for ( e = entities.next ; e != &entities && g_numentities != MAX_MAP_ENTITIES ; e = e->next)
|
|
{
|
|
g_numentities++;
|
|
}
|
|
}
|
|
*/
|
|
if ( ( ( g_numbrushes != s_lastbrushcount ) || ( g_numentities != s_lastentitycount ) ) || ( !s_didonce ) ) {
|
|
Sys_UpdateStatusBar();
|
|
|
|
s_lastbrushcount = g_numbrushes;
|
|
s_lastentitycount = g_numentities;
|
|
s_didonce = true;
|
|
}
|
|
}
|
|
|
|
char com_token[1024];
|
|
qboolean com_eof;
|
|
|
|
/*
|
|
================
|
|
I_FloatTime
|
|
================
|
|
*/
|
|
double I_FloatTime( void ){
|
|
time_t t;
|
|
|
|
time( &t );
|
|
|
|
return t;
|
|
#if 0
|
|
// more precise, less portable
|
|
struct timeval tp;
|
|
struct timezone tzp;
|
|
static int secbase;
|
|
|
|
gettimeofday( &tp, &tzp );
|
|
|
|
if ( !secbase ) {
|
|
secbase = tp.tv_sec;
|
|
return tp.tv_usec / 1000000.0;
|
|
}
|
|
|
|
return ( tp.tv_sec - secbase ) + tp.tv_usec / 1000000.0;
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
==============
|
|
COM_Parse
|
|
|
|
Parse a token out of a string
|
|
==============
|
|
*/
|
|
char *COM_Parse( char *data ){
|
|
int c;
|
|
int len;
|
|
|
|
len = 0;
|
|
com_token[0] = 0;
|
|
|
|
if ( !data ) {
|
|
return NULL;
|
|
}
|
|
|
|
// skip whitespace
|
|
skipwhite:
|
|
while ( ( c = *data ) <= ' ' )
|
|
{
|
|
if ( c == 0 ) {
|
|
com_eof = true;
|
|
return NULL; // end of file;
|
|
}
|
|
data++;
|
|
}
|
|
|
|
// skip // comments
|
|
if ( c == '/' && data[1] == '/' ) {
|
|
while ( *data && *data != '\n' )
|
|
data++;
|
|
goto skipwhite;
|
|
}
|
|
|
|
|
|
// handle quoted strings specially
|
|
if ( c == '\"' ) {
|
|
data++;
|
|
do
|
|
{
|
|
c = *data++;
|
|
if ( c == '\"' ) {
|
|
com_token[len] = 0;
|
|
return data;
|
|
}
|
|
com_token[len] = c;
|
|
len++;
|
|
} while ( 1 );
|
|
}
|
|
|
|
// parse single characters
|
|
if ( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':' ) {
|
|
com_token[len] = c;
|
|
len++;
|
|
com_token[len] = 0;
|
|
return data + 1;
|
|
}
|
|
|
|
// parse a regular word
|
|
do
|
|
{
|
|
com_token[len] = c;
|
|
data++;
|
|
len++;
|
|
c = *data;
|
|
if ( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':' ) {
|
|
break;
|
|
}
|
|
} while ( c > 32 );
|
|
|
|
com_token[len] = 0;
|
|
return data;
|
|
}
|
|
|
|
char* Get_COM_Token(){
|
|
return com_token;
|
|
}
|
|
|
|
/*
|
|
=============================================================================
|
|
|
|
MISC FUNCTIONS
|
|
|
|
=============================================================================
|
|
*/
|
|
|
|
|
|
int argc;
|
|
char *argv[MAX_NUM_ARGVS];
|
|
|
|
/*
|
|
============
|
|
ParseCommandLine
|
|
============
|
|
*/
|
|
void ParseCommandLine( char *lpCmdLine ){
|
|
argc = 1;
|
|
argv[0] = const_cast<char*>("programname");
|
|
|
|
while ( *lpCmdLine && ( argc < MAX_NUM_ARGVS ) )
|
|
{
|
|
while ( *lpCmdLine && ( ( *lpCmdLine <= 32 ) || ( *lpCmdLine > 126 ) ) )
|
|
lpCmdLine++;
|
|
|
|
if ( *lpCmdLine ) {
|
|
argv[argc] = lpCmdLine;
|
|
argc++;
|
|
|
|
while ( *lpCmdLine && ( ( *lpCmdLine > 32 ) && ( *lpCmdLine <= 126 ) ) )
|
|
lpCmdLine++;
|
|
|
|
if ( *lpCmdLine ) {
|
|
*lpCmdLine = 0;
|
|
lpCmdLine++;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=================
|
|
CheckParm
|
|
|
|
Checks for the given parameter in the program's command line arguments
|
|
Returns the argument number (1 to argc-1) or 0 if not present
|
|
=================
|
|
*/
|
|
int CheckParm( const char *check ){
|
|
int i;
|
|
|
|
for ( i = 1; i < argc; i++ )
|
|
{
|
|
if ( stricmp( check, argv[i] ) ) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
==============
|
|
ParseNum / ParseHex
|
|
==============
|
|
*/
|
|
int ParseHex( const char *hex ){
|
|
const char *str;
|
|
int num;
|
|
|
|
num = 0;
|
|
str = hex;
|
|
|
|
while ( *str )
|
|
{
|
|
num <<= 4;
|
|
if ( *str >= '0' && *str <= '9' ) {
|
|
num += *str - '0';
|
|
}
|
|
else if ( *str >= 'a' && *str <= 'f' ) {
|
|
num += 10 + *str - 'a';
|
|
}
|
|
else if ( *str >= 'A' && *str <= 'F' ) {
|
|
num += 10 + *str - 'A';
|
|
}
|
|
else{
|
|
Error( "Bad hex number: %s",hex );
|
|
}
|
|
str++;
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
|
|
int ParseNum( const char *str ){
|
|
if ( str[0] == '$' ) {
|
|
return ParseHex( str + 1 );
|
|
}
|
|
if ( str[0] == '0' && str[1] == 'x' ) {
|
|
return ParseHex( str + 2 );
|
|
}
|
|
return atol( str );
|
|
}
|
|
|
|
// BSP frontend plugin
|
|
// global flag for BSP frontend plugin is g_qeglobals.bBSPFrontendPlugin
|
|
_QERPlugBSPFrontendTable g_BSPFrontendTable;
|
|
|
|
// =============================================================================
|
|
// Sys_ functions
|
|
|
|
bool Sys_AltDown(){
|
|
#ifdef _WIN32
|
|
return ( GetKeyState( VK_MENU ) & 0x8000 ) != 0;
|
|
#endif
|
|
|
|
#if defined( __linux__ ) || defined( __FreeBSD__ ) || defined( __APPLE__ )
|
|
char keys[32];
|
|
int x;
|
|
|
|
XQueryKeymap( gdk_x11_get_default_xdisplay(), keys );
|
|
|
|
x = XKeysymToKeycode( gdk_x11_get_default_xdisplay(), XK_Alt_L );
|
|
if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
|
|
return true;
|
|
}
|
|
|
|
x = XKeysymToKeycode( gdk_x11_get_default_xdisplay(), XK_Alt_R );
|
|
if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
|
|
return true;
|
|
}
|
|
|
|
// For Apple, let users use their Command keys since Alt + X11 is hosed
|
|
x = XKeysymToKeycode( gdk_x11_get_default_xdisplay(), XK_Meta_L );
|
|
if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
|
|
return true;
|
|
}
|
|
|
|
x = XKeysymToKeycode( gdk_x11_get_default_xdisplay(), XK_Meta_R );
|
|
if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool Sys_ShiftDown(){
|
|
#ifdef _WIN32
|
|
return ( GetKeyState( VK_SHIFT ) & 0x8000 ) != 0;
|
|
#endif
|
|
|
|
#if defined( __linux__ ) || defined( __FreeBSD__ ) || defined( __APPLE__ )
|
|
char keys[32];
|
|
int x;
|
|
|
|
XQueryKeymap( gdk_x11_get_default_xdisplay(), keys );
|
|
|
|
x = XKeysymToKeycode( gdk_x11_get_default_xdisplay(), XK_Shift_L );
|
|
if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
|
|
return true;
|
|
}
|
|
|
|
x = XKeysymToKeycode( gdk_x11_get_default_xdisplay(), XK_Shift_R );
|
|
if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void Sys_MarkMapModified( void ){
|
|
char title[PATH_MAX];
|
|
|
|
if ( modified != 1 ) {
|
|
modified = true; // mark the map as changed
|
|
sprintf( title, "%s *", currentmap );
|
|
|
|
QE_ConvertDOSToUnixName( title, title );
|
|
Sys_SetTitle( title );
|
|
}
|
|
}
|
|
|
|
void Sys_SetTitle( const char *text ){
|
|
gtk_window_set_title( GTK_WINDOW( g_qeglobals_gui.d_main_window ), text );
|
|
}
|
|
|
|
bool g_bWaitCursor = false;
|
|
|
|
void WINAPI Sys_BeginWait( void ){
|
|
GdkWindow *window;
|
|
GdkDisplay *display;
|
|
GdkCursor *cursor;
|
|
|
|
window = gtk_widget_get_window( g_pParentWnd->m_pWidget );
|
|
display = gdk_window_get_display( window );
|
|
cursor = gdk_cursor_new_for_display( display, GDK_WATCH );
|
|
gdk_window_set_cursor( window, cursor );
|
|
#if GTK_CHECK_VERSION( 3, 0, 0 )
|
|
g_object_unref( cursor );
|
|
#else
|
|
gdk_cursor_unref( cursor );
|
|
#endif
|
|
|
|
g_bWaitCursor = true;
|
|
}
|
|
|
|
void WINAPI Sys_EndWait( void ){
|
|
GdkWindow *window;
|
|
GdkDisplay *display;
|
|
GdkCursor *cursor;
|
|
|
|
window = gtk_widget_get_window( g_pParentWnd->m_pWidget );
|
|
display = gdk_window_get_display( window );
|
|
cursor = gdk_cursor_new_for_display( display, GDK_LEFT_PTR );
|
|
|
|
gdk_window_set_cursor( window, cursor );
|
|
#if GTK_CHECK_VERSION( 3, 0, 0 )
|
|
g_object_unref( cursor );
|
|
#else
|
|
gdk_cursor_unref( cursor );
|
|
#endif
|
|
|
|
g_bWaitCursor = false;
|
|
}
|
|
|
|
void Sys_GetCursorPos( int *x, int *y ){
|
|
gdk_display_get_pointer( gdk_display_get_default(), 0, x, y, 0 );
|
|
}
|
|
|
|
void Sys_SetCursorPos( int x, int y ){
|
|
GdkDisplay *display = gdk_display_get_default();
|
|
gdk_display_warp_pointer( display, gdk_display_get_default_screen( display ), x, y );
|
|
}
|
|
|
|
void Sys_Beep( void ){
|
|
#if defined( __linux__ ) || defined( __FreeBSD__ ) || defined( __APPLE__ )
|
|
gdk_beep();
|
|
#else
|
|
MessageBeep( MB_ICONASTERISK );
|
|
#endif
|
|
}
|
|
|
|
double Sys_DoubleTime( void ){
|
|
return clock() / 1000.0;
|
|
}
|
|
|
|
/*
|
|
===============================================================
|
|
|
|
STATUS WINDOW
|
|
|
|
===============================================================
|
|
*/
|
|
|
|
void Sys_UpdateStatusBar( void ){
|
|
extern int g_numbrushes, g_numentities;
|
|
|
|
char numbrushbuffer[100] = "";
|
|
|
|
sprintf( numbrushbuffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities );
|
|
g_pParentWnd->SetStatusText( 2, numbrushbuffer );
|
|
//Sys_Status( numbrushbuffer, 2 );
|
|
}
|
|
|
|
void Sys_Status( const char *psz, int part ){
|
|
g_pParentWnd->SetStatusText( part, psz );
|
|
}
|
|
|
|
// =============================================================================
|
|
// MRU
|
|
|
|
#define MRU_MAX 4
|
|
static GtkWidget *MRU_items[MRU_MAX];
|
|
static int MRU_used;
|
|
typedef char MRU_filename_t[PATH_MAX];
|
|
MRU_filename_t MRU_filenames[MRU_MAX];
|
|
|
|
static char* MRU_GetText( int index ){
|
|
return MRU_filenames[index];
|
|
}
|
|
|
|
void buffer_write_escaped_mnemonic( char* buffer, const char* string ){
|
|
while ( *string != '\0' )
|
|
{
|
|
if ( *string == '_' ) {
|
|
*buffer++ = '_';
|
|
}
|
|
|
|
*buffer++ = *string++;
|
|
}
|
|
*buffer = '\0';
|
|
}
|
|
|
|
static void MRU_SetText( int index, const char *filename ){
|
|
strcpy( MRU_filenames[index], filename );
|
|
|
|
char mnemonic[PATH_MAX * 2 + 4];
|
|
mnemonic[0] = '_';
|
|
sprintf( mnemonic + 1, "%d", index + 1 );
|
|
mnemonic[2] = '-';
|
|
mnemonic[3] = ' ';
|
|
buffer_write_escaped_mnemonic( mnemonic + 4, filename );
|
|
gtk_menu_item_set_label( GTK_MENU_ITEM( MRU_items[index] ), mnemonic );
|
|
}
|
|
|
|
void MRU_Load(){
|
|
int i = g_PrefsDlg.m_nMRUCount;
|
|
|
|
if ( i > 4 ) {
|
|
i = 4; //FIXME: make this a define
|
|
|
|
}
|
|
for (; i > 0; i-- )
|
|
MRU_AddFile( g_PrefsDlg.m_strMRUFiles[i - 1].GetBuffer() );
|
|
}
|
|
|
|
void MRU_Save(){
|
|
g_PrefsDlg.m_nMRUCount = MRU_used;
|
|
|
|
for ( int i = 0; i < MRU_used; i++ )
|
|
g_PrefsDlg.m_strMRUFiles[i] = MRU_GetText( i );
|
|
}
|
|
|
|
void MRU_AddWidget( GtkWidget *widget, int pos ){
|
|
if ( pos < MRU_MAX ) {
|
|
MRU_items[pos] = widget;
|
|
}
|
|
}
|
|
|
|
void MRU_AddFile( const char *str ){
|
|
int i;
|
|
char* text;
|
|
|
|
// check if file is already in our list
|
|
for ( i = 0; i < MRU_used; i++ )
|
|
{
|
|
text = MRU_GetText( i );
|
|
|
|
if ( strcmp( text, str ) == 0 ) {
|
|
// reorder menu
|
|
for (; i > 0; i-- )
|
|
MRU_SetText( i, MRU_GetText( i - 1 ) );
|
|
|
|
MRU_SetText( 0, str );
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( MRU_used < MRU_MAX ) {
|
|
MRU_used++;
|
|
}
|
|
|
|
// move items down
|
|
for ( i = MRU_used - 1; i > 0; i-- )
|
|
MRU_SetText( i, MRU_GetText( i - 1 ) );
|
|
|
|
MRU_SetText( 0, str );
|
|
gtk_widget_set_sensitive( MRU_items[0], TRUE );
|
|
gtk_widget_show( MRU_items[MRU_used - 1] );
|
|
}
|
|
|
|
void MRU_Activate( int index ){
|
|
char *text = MRU_GetText( index );
|
|
|
|
if ( access( text, R_OK ) == 0 ) {
|
|
text = strdup( text );
|
|
MRU_AddFile( text );
|
|
Map_LoadFile( text );
|
|
free( text );
|
|
}
|
|
else
|
|
{
|
|
MRU_used--;
|
|
|
|
for ( int i = index; i < MRU_used; i++ )
|
|
MRU_SetText( i, MRU_GetText( i + 1 ) );
|
|
|
|
if ( MRU_used == 0 ) {
|
|
gtk_menu_item_set_label( GTK_MENU_ITEM( MRU_items[0] ), _( "Recent Files" ) );
|
|
gtk_widget_set_sensitive( MRU_items[0], FALSE );
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_hide( MRU_items[MRU_used] );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
======================================================================
|
|
|
|
FILE DIALOGS
|
|
|
|
======================================================================
|
|
*/
|
|
|
|
qboolean ConfirmModified() {
|
|
if( !modified )
|
|
return TRUE;
|
|
|
|
int saveChoice = gtk_MessageBoxNew( g_pParentWnd->m_pWidget,
|
|
"The current map has changed since it was last saved.\n"
|
|
"Would you like to save before continuing?", "Radiant",
|
|
MB_YESNOCANCEL | MB_ICONQUESTION );
|
|
|
|
switch( saveChoice ) {
|
|
case IDYES: {
|
|
g_pParentWnd->OnFileSave();
|
|
break;
|
|
}
|
|
case IDNO: {
|
|
return TRUE;
|
|
}
|
|
case IDCANCEL:
|
|
default: {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void ProjectDialog(){
|
|
const char *filename;
|
|
char buffer[NAME_MAX];
|
|
|
|
/*
|
|
* Obtain the system directory name and
|
|
* store it in buffer.
|
|
*/
|
|
|
|
strcpy( buffer, g_qeglobals.m_strHomeGame.GetBuffer() );
|
|
strcat( buffer, g_pGameDescription->mBaseGame.GetBuffer() );
|
|
strcat( buffer, "/scripts/" );
|
|
|
|
// Display the Open dialog box
|
|
filename = file_dialog( NULL, TRUE, _( "Open File" ), buffer, "project" );
|
|
|
|
if ( filename == NULL ) {
|
|
return; // canceled
|
|
|
|
}
|
|
// Open the file.
|
|
// NOTE: QE_LoadProject takes care of saving prefs with new path to the project file
|
|
if ( !QE_LoadProject( filename ) ) {
|
|
Sys_Printf( "Failed to load project from file: %s\n", filename );
|
|
}
|
|
else{
|
|
// FIXME TTimo QE_Init is probably broken if you don't call it during startup right now ..
|
|
QE_Init();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=======================================================
|
|
|
|
Menu modifications
|
|
|
|
=======================================================
|
|
*/
|
|
|
|
/*
|
|
==================
|
|
FillBSPMenu
|
|
|
|
==================
|
|
*/
|
|
char *bsp_commands[256];
|
|
|
|
void FillBSPMenu(){
|
|
GtkWidget *item, *menu; // menu points to a GtkMenu (not an item)
|
|
epair_t *ep;
|
|
GList *children, *lst;
|
|
int i;
|
|
|
|
menu = GTK_WIDGET( g_object_get_data( G_OBJECT( g_qeglobals_gui.d_main_window ), "menu_bsp" ) );
|
|
|
|
children = gtk_container_get_children( GTK_CONTAINER( menu ) );
|
|
if( children ) {
|
|
for ( lst = children; lst != NULL; lst = g_list_next( lst ) )
|
|
{
|
|
gtk_container_remove( GTK_CONTAINER( menu ), GTK_WIDGET( lst->data ) );
|
|
}
|
|
g_list_free( children );
|
|
}
|
|
|
|
if ( g_PrefsDlg.m_bDetachableMenus ) {
|
|
item = gtk_tearoff_menu_item_new();
|
|
gtk_menu_shell_append( GTK_MENU_SHELL( menu ), item );
|
|
gtk_widget_set_sensitive( item, TRUE );
|
|
gtk_widget_show( item );
|
|
}
|
|
|
|
if ( g_qeglobals.bBSPFrontendPlugin ) {
|
|
CString str = g_BSPFrontendTable.m_pfnGetBSPMenu();
|
|
char cTemp[1024];
|
|
strcpy( cTemp, str );
|
|
char* token = strtok( cTemp, ",;" );
|
|
if ( token && *token == ' ' ) {
|
|
while ( *token == ' ' )
|
|
token++;
|
|
}
|
|
i = 0;
|
|
|
|
// first token is menu name
|
|
children = gtk_container_get_children( GTK_CONTAINER( menu ) );
|
|
if( children ) {
|
|
if( g_list_first( children ) ) {
|
|
gtk_label_set_text( GTK_LABEL( g_list_first( children )->data ), token );
|
|
}
|
|
g_list_free( children );
|
|
}
|
|
token = strtok( NULL, ",;" );
|
|
while ( token != NULL )
|
|
{
|
|
g_BSPFrontendCommands = g_slist_append( g_BSPFrontendCommands, g_strdup( token ) );
|
|
item = gtk_menu_item_new_with_label( token );
|
|
gtk_widget_show( item );
|
|
gtk_container_add( GTK_CONTAINER( menu ), item );
|
|
g_signal_connect( G_OBJECT( item ), "activate",
|
|
G_CALLBACK( HandleCommand ), GINT_TO_POINTER( CMD_BSPCOMMAND + i ) );
|
|
token = strtok( NULL, ",;" );
|
|
i++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i = 0;
|
|
for ( ep = g_qeglobals.d_project_entity->epairs; ep; ep = ep->next )
|
|
{
|
|
if ( strncmp( ep->key, "bsp_", 4 ) == 0 ) {
|
|
bsp_commands[i] = ep->key;
|
|
item = gtk_menu_item_new_with_label( ep->key + 4 );
|
|
gtk_widget_show( item );
|
|
gtk_container_add( GTK_CONTAINER( menu ), item );
|
|
g_signal_connect( G_OBJECT( item ), "activate",
|
|
G_CALLBACK( HandleCommand ), GINT_TO_POINTER( CMD_BSPCOMMAND + i ) );
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================
|
|
|
|
void AddSlash( CString& strPath ){
|
|
if ( strPath.GetLength() > 0 ) {
|
|
if ( !g_str_has_suffix( strPath.GetBuffer(), G_DIR_SEPARATOR_S ) ) {
|
|
strPath += G_DIR_SEPARATOR_S;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ExtractPath_and_Filename( const char* pPath, CString& strPath, CString& strFilename ){
|
|
CString strPathName;
|
|
strPathName = pPath;
|
|
int nSlash = strPathName.ReverseFind( '\\' );
|
|
if ( nSlash == -1 ) {
|
|
// TTimo: try forward slash, some are using forward
|
|
nSlash = strPathName.ReverseFind( '/' );
|
|
}
|
|
if ( nSlash >= 0 ) {
|
|
strPath = strPathName.Left( nSlash + 1 );
|
|
strFilename = strPathName.Right( strPathName.GetLength() - nSlash - 1 );
|
|
}
|
|
// TTimo: try forward slash, some are using forward
|
|
else{
|
|
strFilename = pPath;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//===========================================
|
|
|
|
//++timo FIXME: no longer used .. remove!
|
|
char *TranslateString( char *buf ){
|
|
static char buf2[32768];
|
|
int i, l;
|
|
char *out;
|
|
|
|
l = strlen( buf );
|
|
out = buf2;
|
|
for ( i = 0 ; i < l ; i++ )
|
|
{
|
|
if ( buf[i] == '\n' ) {
|
|
*out++ = '\r';
|
|
*out++ = '\n';
|
|
}
|
|
else{
|
|
*out++ = buf[i];
|
|
}
|
|
}
|
|
*out++ = 0;
|
|
|
|
return buf2;
|
|
}
|
|
|
|
// called whenever we need to open/close/check the console log file
|
|
void Sys_LogFile( void ){
|
|
if ( g_PrefsDlg.mGamesDialog.m_bLogConsole && !g_qeglobals.hLogFile ) {
|
|
// settings say we should be logging and we don't have a log file .. so create it
|
|
// open a file to log the console (if user prefs say so)
|
|
// the file handle is g_qeglobals.hLogFile
|
|
// the log file is erased
|
|
Str name;
|
|
name = g_strTempPath;
|
|
name += "radiant.log";
|
|
#if defined( __linux__ ) || defined( __FreeBSD__ ) || defined( __APPLE__ )
|
|
g_qeglobals.hLogFile = open( name.GetBuffer(), O_TRUNC | O_CREAT | O_WRONLY, S_IREAD | S_IWRITE );
|
|
#endif
|
|
#ifdef _WIN32
|
|
g_qeglobals.hLogFile = _open( name.GetBuffer(), _O_TRUNC | _O_CREAT | _O_WRONLY, _S_IREAD | _S_IWRITE );
|
|
#endif
|
|
if ( g_qeglobals.hLogFile ) {
|
|
Sys_Printf( "Started logging to %s\n", name.GetBuffer() );
|
|
time_t localtime;
|
|
time( &localtime );
|
|
Sys_Printf( "Today is: %s", ctime( &localtime ) );
|
|
Sys_Printf( "This is radiant '" RADIANT_VERSION "' compiled " __DATE__ "\n" );
|
|
Sys_Printf( RADIANT_ABOUTMSG "\n" );
|
|
}
|
|
else{
|
|
gtk_MessageBox( NULL, _( "Failed to create log file, check write permissions in Radiant directory.\n" ),
|
|
_( "Console logging" ), MB_OK );
|
|
}
|
|
}
|
|
else if ( !g_PrefsDlg.mGamesDialog.m_bLogConsole && g_qeglobals.hLogFile ) {
|
|
// settings say we should not be logging but still we have an active logfile .. close it
|
|
time_t localtime;
|
|
time( &localtime );
|
|
Sys_Printf( "Closing log file at %s\n", ctime( &localtime ) );
|
|
#ifdef _WIN32
|
|
_close( g_qeglobals.hLogFile );
|
|
#endif
|
|
#if defined( __linux__ ) || defined( __FreeBSD__ ) || defined( __APPLE__ )
|
|
close( g_qeglobals.hLogFile );
|
|
#endif
|
|
g_qeglobals.hLogFile = 0;
|
|
}
|
|
}
|
|
|
|
void Sys_ClearPrintf( void ){
|
|
GtkTextBuffer* buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( g_qeglobals_gui.d_edit ) );
|
|
gtk_text_buffer_set_text( buffer, "", -1 );
|
|
}
|
|
|
|
// used to be around 32000, that should be way enough already
|
|
#define BUFFER_SIZE 4096
|
|
|
|
extern "C" void Sys_FPrintf_VA( int level, const char *text, va_list args ) {
|
|
char buf[BUFFER_SIZE];
|
|
|
|
buf[0] = 0;
|
|
vsnprintf( buf, BUFFER_SIZE, text, args );
|
|
buf[BUFFER_SIZE - 1] = 0;
|
|
const unsigned int length = strlen( buf );
|
|
|
|
if ( g_qeglobals.hLogFile ) {
|
|
#ifdef _WIN32
|
|
_write( g_qeglobals.hLogFile, buf, length );
|
|
_commit( g_qeglobals.hLogFile );
|
|
#endif
|
|
#if defined( __linux__ ) || defined( __FreeBSD__ ) || defined( __APPLE__ )
|
|
write( g_qeglobals.hLogFile, buf, length );
|
|
#endif
|
|
}
|
|
|
|
if ( level != SYS_NOCON ) {
|
|
// TTimo: FIXME: killed the console to avoid GDI leak fuckup
|
|
if ( g_qeglobals_gui.d_edit != NULL ) {
|
|
GtkTextBuffer* buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( g_qeglobals_gui.d_edit ) );
|
|
|
|
GtkTextIter iter;
|
|
gtk_text_buffer_get_end_iter( buffer, &iter );
|
|
|
|
static GtkTextMark* end = gtk_text_buffer_create_mark( buffer, "end", &iter, FALSE );
|
|
|
|
const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 };
|
|
const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 };
|
|
const GdkColor black = { 0, 0x0000, 0x0000, 0x0000 };
|
|
|
|
static GtkTextTag* error_tag = gtk_text_buffer_create_tag( buffer, "red_foreground", "foreground-gdk", &red, NULL );
|
|
static GtkTextTag* warning_tag = gtk_text_buffer_create_tag( buffer, "yellow_foreground", "foreground-gdk", &yellow, NULL );
|
|
static GtkTextTag* standard_tag = gtk_text_buffer_create_tag( buffer, "black_foreground", "foreground-gdk", &black, NULL );
|
|
GtkTextTag* tag;
|
|
switch ( level )
|
|
{
|
|
case SYS_WRN:
|
|
tag = warning_tag;
|
|
break;
|
|
case SYS_ERR:
|
|
tag = error_tag;
|
|
break;
|
|
case SYS_STD:
|
|
case SYS_VRB:
|
|
default:
|
|
tag = standard_tag;
|
|
break;
|
|
}
|
|
gtk_text_buffer_insert_with_tags( buffer, &iter, buf, length, tag, (char *) NULL );
|
|
|
|
gtk_text_view_scroll_mark_onscreen( GTK_TEXT_VIEW( g_qeglobals_gui.d_edit ), end );
|
|
|
|
// update console widget immediately if we're doing something time-consuming
|
|
if ( !g_bScreenUpdates && gtk_widget_get_realized( g_qeglobals_gui.d_edit ) ) {
|
|
gtk_grab_add( g_qeglobals_gui.d_edit );
|
|
|
|
while ( gtk_events_pending() )
|
|
gtk_main_iteration();
|
|
|
|
gtk_grab_remove( g_qeglobals_gui.d_edit );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: this is the handler sent to synapse
|
|
// must match PFN_SYN_PRINTF_VA
|
|
extern "C" void Sys_Printf_VA( const char *text, va_list args ){
|
|
Sys_FPrintf_VA( SYS_STD, text, args );
|
|
}
|
|
|
|
extern "C" void Sys_Printf( const char *text, ... ) {
|
|
va_list args;
|
|
|
|
va_start( args, text );
|
|
Sys_FPrintf_VA( SYS_STD, text, args );
|
|
va_end( args );
|
|
}
|
|
|
|
extern "C" void Sys_FPrintf( int level, const char *text, ... ){
|
|
va_list args;
|
|
|
|
va_start( args, text );
|
|
Sys_FPrintf_VA( level, text, args );
|
|
va_end( args );
|
|
}
|