mirror of
https://github.com/TTimo/GtkRadiant.git
synced 2024-11-10 07:11:54 +00:00
1775 lines
43 KiB
C++
1775 lines
43 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 ( __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_Printf( "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 ( __APPLE__ )
|
|
bGo = ( mkdir( strOrgPath,0755 ) != -1 );
|
|
#endif
|
|
}
|
|
AddSlash( strOrgPath );
|
|
if ( bGo ) {
|
|
int nCount = 0;
|
|
long lSize = 0;
|
|
CString strNewPath;
|
|
strNewPath = strOrgPath;
|
|
strNewPath += strOrgFile;
|
|
CString strFile;
|
|
while ( bGo )
|
|
{
|
|
char buf[PATH_MAX];
|
|
sprintf( buf, "%s.%i", strNewPath.GetBuffer(), nCount );
|
|
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 ( __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
|
|
void ReplaceTemplates( char* w, const char* r ){
|
|
const char *p;
|
|
const char *__ENGINEPATH = "TEMPLATEenginepath";
|
|
const char *__USERHOMEPATH = "TEMPLATEuserhomepath";
|
|
const char *__TOOLSPATH = "TEMPLATEtoolspath";
|
|
const char *__BASEDIR = "TEMPLATEbasedir";
|
|
const char *__APPPATH = "TEMPLATEapppath";
|
|
|
|
// 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, __APPPATH, strlen( __APPPATH ) ) == 0 ) {
|
|
r += strlen( __APPPATH ) + 1;
|
|
p = g_strAppPath.GetBuffer();
|
|
}
|
|
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
|
|
TODO TODO TODO (don't think this got fully merged in)
|
|
TTimo: added project file "version", version 2 adds '#' chars to the BSP command strings
|
|
version 3 was .. I don't remember .. version 4 adds q3map2 commands
|
|
TTimo: when QE_LoadProject is called, the prefs are updated with path to the latest project and saved on disk
|
|
===========
|
|
*/
|
|
/*\todo decide on a sensible location/name for project files.*/
|
|
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 += '/';
|
|
|
|
// 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, "/scripts/" );
|
|
// 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_Printf( "Error: you must have a single brush selected\n" );
|
|
}
|
|
return false;
|
|
}
|
|
if ( selected_brushes.next->owner->eclass->fixedsize ) {
|
|
if ( !bQuiet ) {
|
|
Sys_Printf( "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;
|
|
|
|
// 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' ) {
|
|
|
|
#if defined ( __linux__ ) || defined ( __APPLE__ )
|
|
// ~/.<gameprefix>/<fs_game>
|
|
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 );
|
|
|
|
#endif
|
|
|
|
// <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 );
|
|
}
|
|
|
|
#if defined ( __linux__ ) || defined ( __APPLE__ )
|
|
// ~/.<gameprefix>/<fs_main>
|
|
directory = g_qeglobals.m_strHomeGame.GetBuffer();
|
|
directory += g_pGameDescription->mBaseGame;
|
|
vfsInitDirectory( directory.GetBuffer() );
|
|
#endif
|
|
|
|
// <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();
|
|
|
|
FillTextureMenu();
|
|
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] = "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 ( __APPLE__ )
|
|
char keys[32];
|
|
int x;
|
|
|
|
XQueryKeymap( GDK_DISPLAY(), keys );
|
|
|
|
x = XKeysymToKeycode( GDK_DISPLAY(), XK_Alt_L );
|
|
if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
|
|
return true;
|
|
}
|
|
|
|
x = XKeysymToKeycode( GDK_DISPLAY(), XK_Alt_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 ( __APPLE__ )
|
|
char keys[32];
|
|
int x;
|
|
|
|
XQueryKeymap( GDK_DISPLAY(), keys );
|
|
|
|
x = XKeysymToKeycode( GDK_DISPLAY(), XK_Shift_L );
|
|
if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
|
|
return true;
|
|
}
|
|
|
|
x = XKeysymToKeycode( GDK_DISPLAY(), 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 ){
|
|
GdkCursor *cursor = gdk_cursor_new( GDK_WATCH );
|
|
gdk_window_set_cursor( g_pParentWnd->m_pWidget->window, cursor );
|
|
gdk_cursor_unref( cursor );
|
|
g_bWaitCursor = true;
|
|
}
|
|
|
|
void WINAPI Sys_EndWait( void ){
|
|
GdkCursor *cursor = gdk_cursor_new( GDK_LEFT_PTR );
|
|
gdk_window_set_cursor( g_pParentWnd->m_pWidget->window, cursor );
|
|
gdk_cursor_unref( cursor );
|
|
g_bWaitCursor = false;
|
|
}
|
|
|
|
void Sys_GetCursorPos( int *x, int *y ){
|
|
// FIXME: not multihead safe
|
|
gdk_window_get_pointer( NULL, x, y, NULL );
|
|
}
|
|
|
|
void Sys_SetCursorPos( int x, int y ){
|
|
// NOTE: coordinates are in GDK space, not OS space
|
|
#ifdef _WIN32
|
|
int sys_x = x - g_pParentWnd->GetGDKOffsetX();
|
|
int sys_y = y - g_pParentWnd->GetGDKOffsetY();
|
|
|
|
SetCursorPos( sys_x, sys_y );
|
|
#endif
|
|
|
|
#if defined ( __linux__ ) || defined ( __APPLE__ )
|
|
XWarpPointer( GDK_DISPLAY(), None, GDK_ROOT_WINDOW(), 0, 0, 0, 0, x, y );
|
|
#endif
|
|
}
|
|
|
|
void Sys_Beep( void ){
|
|
#if defined ( __linux__ ) || 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_label_set_text_with_mnemonic( GTK_LABEL( GTK_BIN( MRU_items[index] )->child ), 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_label_set_text( GTK_LABEL( GTK_BIN( MRU_items[0] )->child ), "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?",
|
|
"Exit 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 *lst;
|
|
int i;
|
|
|
|
menu = GTK_WIDGET( g_object_get_data( G_OBJECT( g_qeglobals_gui.d_main_window ), "menu_bsp" ) );
|
|
|
|
while ( ( lst = gtk_container_children( GTK_CONTAINER( menu ) ) ) != NULL )
|
|
gtk_container_remove( GTK_CONTAINER( menu ), GTK_WIDGET( lst->data ) );
|
|
|
|
if ( g_PrefsDlg.m_bDetachableMenus ) {
|
|
item = gtk_tearoff_menu_item_new();
|
|
gtk_menu_append( GTK_MENU( 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
|
|
item = gtk_menu_get_attach_widget( GTK_MENU( menu ) );
|
|
gtk_label_set_text( GTK_LABEL( GTK_BIN( item )->child ), token );
|
|
|
|
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 );
|
|
gtk_signal_connect( GTK_OBJECT( item ), "activate",
|
|
GTK_SIGNAL_FUNC( 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 );
|
|
gtk_signal_connect( GTK_OBJECT( item ), "activate",
|
|
GTK_SIGNAL_FUNC( HandleCommand ), GINT_TO_POINTER( CMD_BSPCOMMAND + i ) );
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================
|
|
|
|
void AddSlash( CString& strPath ){
|
|
if ( strPath.GetLength() > 0 ) {
|
|
if ( ( strPath.GetAt( strPath.GetLength() - 1 ) != '/' ) &&
|
|
( strPath.GetAt( strPath.GetLength() - 1 ) != '\\' ) ) {
|
|
strPath += '/';
|
|
}
|
|
}
|
|
}
|
|
|
|
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 ( __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 ( __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 ( __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, NULL );
|
|
|
|
gtk_text_view_scroll_mark_onscreen( GTK_TEXT_VIEW( g_qeglobals_gui.d_edit ), end );
|
|
|
|
// update console widget immediatly if we're doing something time-consuming
|
|
if ( !g_bScreenUpdates && GTK_WIDGET_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 );
|
|
}
|