X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=radiant%2Fqe3.cpp;h=201154ecb48cc393494685fc1377c56c26ebd5b2;hb=961fb5bd5f3998167bb7df90556d9c3941049bd1;hp=a526c608d003cc1e63ed23100d94ec3d08a30aeb;hpb=3591d7dc01732577ebbb25f3818ccec8f440338f;p=xonotic%2Fnetradiant.git diff --git a/radiant/qe3.cpp b/radiant/qe3.cpp index a526c608..201154ec 100644 --- a/radiant/qe3.cpp +++ b/radiant/qe3.cpp @@ -1,30 +1,23 @@ /* -Copyright (C) 1999-2006 Id Software, Inc. and contributors. -For a list of contributors, see the accompanying CONTRIBUTORS file. + 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. + 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 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. + 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 -*/ - -/* -The following source code is licensed by Id Software and subject to the terms of -its LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with -GtkRadiant. If you did not receive a LIMITED USE SOFTWARE LICENSE AGREEMENT, -please contact Id Software immediately at info@idsoftware.com. -*/ + 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 @@ -32,363 +25,1691 @@ please contact Id Software immediately at info@idsoftware.com. // Leonardo Zide (leo@lokigames.com) // -#include "qe3.h" +#include "stdafx.h" +#include +#include +#include "gtkmisc.h" +#include +#if defined ( __linux__ ) || defined ( __APPLE__ ) +#include +#include +#include +#include +#endif +// for the logging part +#include +#include + +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; -#include "debugging/debugging.h" +void* debug_malloc( size_t size, const char* file, int line ){ + void *buf = g_malloc( size + 8 ); -#include "ifilesystem.h" -//#include "imap.h" + *( (const char**)buf ) = file; + buf = (char*)buf + 4; + *( (int*)buf ) = line; + buf = (char*)buf + 4; -#include + memblocks = g_list_append( memblocks, buf ); -#include + return buf; +} + +void debug_free( void *buf, const char* file, int line ){ + const char *f; + int l; -#include "stream/textfilestream.h" -#include "cmdlib.h" -#include "stream/stringstream.h" -#include "os/path.h" -#include "scenelib.h" + if ( g_list_find( memblocks, buf ) ) { + memblocks = g_list_remove( memblocks, buf ); -#include "gtkutil/messagebox.h" -#include "error.h" -#include "map.h" -#include "build.h" -#include "points.h" -#include "camwindow.h" -#include "mainframe.h" -#include "preferences.h" -#include "watchbsp.h" -#include "autosave.h" -#include "convert.h" + buf = (char*)buf - 4; + l = *( (int*)buf ); + buf = (char*)buf - 4; + f = *( (const char**)buf ); -QEGlobals_t g_qeglobals; + 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 +} -#if defined(WIN32) -#define PATH_MAX 260 #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; -void QE_InitVFS() -{ - // VFS initialization ----------------------- - // we will call GlobalFileSystem().initDirectory, 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 + // I hope the modified flag is kept correctly up to date + if ( !modified ) { + return; + } - const char* gamename = gamename_get(); - const char* basegame = basegame_get(); -#if defined(POSIX) - const char* userRoot = g_qeglobals.m_userEnginePath.c_str(); + // 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 - const char* globalRoot = EnginePath_get(); - // if we have a mod dir - if(!string_equal(gamename, basegame)) - { -#if defined(POSIX) - // ~/./ - { - StringOutputStream userGamePath(256); - userGamePath << userRoot << gamename << '/'; - GlobalFileSystem().initDirectory(userGamePath.c_str()); - } +#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] == '\\') { - StringOutputStream globalGamePath(256); - globalGamePath << globalRoot << gamename << '/'; - GlobalFileSystem().initDirectory(globalGamePath.c_str()); + 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; } - } - -#if defined(POSIX) - // ~/./ - { - StringOutputStream userBasePath(256); - userBasePath << userRoot << basegame << '/'; - GlobalFileSystem().initDirectory(userBasePath.c_str()); - } -#endif - // / - { - StringOutputStream globalBasePath(256); - globalBasePath << globalRoot << basegame << '/'; - GlobalFileSystem().initDirectory(globalBasePath.c_str()); - } + 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 ); } -int g_numbrushes = 0; -int g_numentities = 0; +#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; -void QE_UpdateStatusBar() -{ - char buffer[128]; - sprintf(buffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities); - g_pParentWnd->SetStatusText(g_pParentWnd->m_brushcount_status, buffer); + //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; } -SimpleCounter g_brushCount; +xmlDocPtr ParseXMLFile( const char* filename, bool validate = false ){ + FileStream stream; + if ( stream.Open( filename, "r" ) ) { + return ParseXMLStream( &stream, validate ); + } -void QE_brushCountChanged() -{ - g_numbrushes = int(g_brushCount.get()); - QE_UpdateStatusBar(); + Sys_FPrintf( SYS_ERR, "Failed to open file: %s\n",filename ); + return NULL; } -SimpleCounter g_entityCount; +// 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 = "$"; + } -void QE_entityCountChanged() -{ - g_numentities = int(g_entityCount.get()); - QE_UpdateStatusBar(); + while ( *p != '\0' ) *w++ = *p++; + } + else{ *w++ = *r++; } + } + *w = '\0'; } -bool ConfirmModified(const char* title) -{ - if (!Map_Modified(g_map)) - return true; +/* + =========== + 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; - EMessageBoxReturn result = gtk_MessageBox(GTK_WIDGET(MainFrame_getWindow()), "The current map has changed since it was last saved.\nDo you want to save the current map before continuing?", title, eMB_YESNOCANCEL, eMB_ICONQUESTION); - if(result == eIDCANCEL) - { - return false; - } - if(result == eIDYES) - { - if(Map_Unnamed(g_map)) - { - return Map_SaveAs(); - } - else - { - return Map_Save(); - } - } - return true; + 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 = 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; + } + + // + 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. + + // + 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__ ) + // ~/./ + 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 ); -const char* const EXECUTABLE_TYPE = -#if defined(__linux__) || defined (__FreeBSD__) -"x86" -#elif defined(__APPLE__) -"ppc" -#elif defined(WIN32) -"exe" -#else -#error "unknown platform" #endif -; - -void bsp_init() -{ - build_set_variable("RadiantPath", AppPath_get()); - build_set_variable("ExecutableType", EXECUTABLE_TYPE); - build_set_variable("EnginePath", EnginePath_get()); - build_set_variable("MonitorAddress", (g_WatchBSP_Enabled) ? "127.0.0.1:39000" : ""); - build_set_variable("GameName", gamename_get()); - - build_set_variable("MapFile", Map_Name(g_map)); -} - -void bsp_shutdown() -{ - build_clear_variables(); -} - -class ArrayCommandListener : public CommandListener -{ - GPtrArray* m_array; -public: - ArrayCommandListener() - { - m_array = g_ptr_array_new(); - } - ~ArrayCommandListener() - { - g_ptr_array_free(m_array, TRUE); - } - - void execute(const char* command) - { - g_ptr_array_add(m_array, g_strdup(command)); - } - - GPtrArray* array() const - { - return m_array; - } -}; - -class BatchCommandListener : public CommandListener -{ - TextOutputStream& m_file; - std::size_t m_commandCount; - const char* m_outputRedirect; -public: - BatchCommandListener(TextOutputStream& file, const char* outputRedirect) : m_file(file), m_commandCount(0), m_outputRedirect(outputRedirect) - { - } - - void execute(const char* command) - { - m_file << command; - if (m_commandCount == 0) - { - m_file << " > "; - } - else - { - m_file << " >> "; - } - m_file << "\"" << m_outputRedirect << "\""; - m_file << "\n"; - ++m_commandCount; - } -}; - -bool Region_cameraValid() -{ - Vector3 vOrig(vector3_snapped(Camera_getOrigin(*g_pParentWnd->GetCamWnd()))); - - for (int i=0 ; i<3 ; i++) - { - if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) - { - return false; - } - } - return true; -} - - -void RunBSP(const char* name) -{ - // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=503 - // make sure we don't attempt to region compile a map with the camera outside the region - if (region_active && !Region_cameraValid()) - { - globalErrorStream() << "The camera must be in the region to start a region compile.\n"; - return; - } - - SaveMap(); - - if(Map_Unnamed(g_map)) - { - globalOutputStream() << "build cancelled\n"; - return; - } - - if (g_SnapShots_Enabled && !Map_Unnamed(g_map) && Map_Modified(g_map)) - { - Map_Snapshot(); - } - - if (region_active) - { - const char* mapname = Map_Name(g_map); - StringOutputStream name(256); - name << StringRange(mapname, path_get_filename_base_end(mapname)) << ".reg"; - Map_SaveRegion(name.c_str()); - } - - Pointfile_Delete(); - - bsp_init(); - - if (g_WatchBSP_Enabled) - { - ArrayCommandListener listener; - build_run(name, listener); - // grab the file name for engine running - const char* fullname = Map_Name(g_map); - StringOutputStream bspname(64); - bspname << StringRange(path_get_filename_start(fullname), path_get_filename_base_end(fullname)); - BuildMonitor_Run( listener.array(), bspname.c_str() ); - } - else - { - char junkpath[PATH_MAX]; - strcpy(junkpath, SettingsPath_get()); - strcat(junkpath, "junk.txt"); - - char batpath[PATH_MAX]; -#if defined(POSIX) - strcpy(batpath, SettingsPath_get()); - strcat(batpath, "qe3bsp.sh"); -#elif defined(WIN32) - strcpy(batpath, SettingsPath_get()); - strcat(batpath, "qe3bsp.bat"); -#else -#error "unsupported platform" + + // / + 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__ ) + // ~/./ + directory = g_qeglobals.m_strHomeGame.GetBuffer(); + directory += g_pGameDescription->mBaseGame; + vfsInitDirectory( directory.GetBuffer() ); #endif - bool written = false; + + // / + 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 ) { - TextFileOutputStream batchFile(batpath); - if(!batchFile.failed()) - { -#if defined (POSIX) - batchFile << "#!/bin/sh \n\n"; -#endif - BatchCommandListener listener(batchFile, junkpath); - build_run(name, listener); - written = true; - } + for ( e = entities.next ; e != &entities && g_numentities != MAX_MAP_ENTITIES ; e = e->next) + { + g_numentities++; + } } - if(written) - { -#if defined (POSIX) - chmod (batpath, 0744); + */ + 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 - globalOutputStream() << "Writing the compile script to '" << batpath << "'\n"; - globalOutputStream() << "The build output will be saved in '" << junkpath << "'\n"; - Q_Exec(batpath, NULL, NULL, true); - } - } +} + + +/* + ============== + 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++; - bsp_shutdown(); + 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 -void Sys_SetTitle(const char *text, bool modified) -{ - StringOutputStream title; - title << ConvertLocaleToUTF8(text); +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 ); - if(modified) - { - title << " *"; - } + x = XKeysymToKeycode( GDK_DISPLAY(), XK_Alt_L ); + if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) { + return true; + } - gtk_window_set_title(MainFrame_getWindow(), title.c_str()); + 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 Sys_BeginWait (void) -{ - ScreenUpdates_Disable("Processing...", "Please Wait"); - GdkCursor *cursor = gdk_cursor_new (GDK_WATCH); - gdk_window_set_cursor(GTK_WIDGET(MainFrame_getWindow())->window, cursor); - gdk_cursor_unref (cursor); - g_bWaitCursor = true; +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 Sys_EndWait (void) -{ - ScreenUpdates_Enable(); - gdk_window_set_cursor(GTK_WIDGET(MainFrame_getWindow())->window, 0); - g_bWaitCursor = false; +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 Sys_Beep (void) -{ - gdk_beep(); +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; + } + + if ( gtk_MessageBox( g_pParentWnd->m_pWidget, "This will lose changes to the map", "warning", MB_OKCANCEL ) == IDCANCEL ) { + 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 ); +}