/*
-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
// Leonardo Zide (leo@lokigames.com)
//
-#include "qe3.h"
+#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;
-#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 <map>
+ memblocks = g_list_append( memblocks, buf );
-#include <gtk/gtktearoffmenuitem.h>
+ 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)
- // ~/.<gameprefix>/<fs_game>
- {
- 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
- // <fs_basepath>/<fs_game>
+#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)
- // ~/.<gameprefix>/<fs_main>
- {
- StringOutputStream userBasePath(256);
- userBasePath << userRoot << basegame << '/';
- GlobalFileSystem().initDirectory(userBasePath.c_str());
- }
-#endif
- // <fs_basepath>/<fs_main>
- {
- 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>
+ 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 );
-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"
+
+ // <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
- bool written = false;
+
+ // <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 )
{
- 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 );
+}