2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 // Leonardo Zide (leo@lokigames.com)
32 #include <glib/gi18n.h>
33 #if defined ( __linux__ ) || defined ( __APPLE__ )
35 #include <X11/keysym.h>
37 #include <gdk/gdkprivate.h>
39 // for the logging part
41 #include <sys/types.h>
43 QEGlobals_t g_qeglobals;
44 QEGlobals_GUI_t g_qeglobals_gui;
46 // leo: Track memory allocations for debugging
47 // NOTE TTimo this was never used and probably not relevant
48 // there are tools to do that
51 static GList *memblocks;
53 void* debug_malloc( size_t size, const char* file, int line ){
54 void *buf = g_malloc( size + 8 );
56 *( (const char**)buf ) = file;
58 *( (int*)buf ) = line;
61 memblocks = g_list_append( memblocks, buf );
66 void debug_free( void *buf, const char* file, int line ){
70 if ( g_list_find( memblocks, buf ) ) {
71 memblocks = g_list_remove( memblocks, buf );
76 f = *( (const char**)buf );
78 Sys_FPrintf( SYS_DBG, "free: %s %d", file, line );
79 Sys_FPrintf( SYS_DBG, " allocated: %s %d\n", f, l );
84 // free (buf); // from qmalloc, will leak unless we add this same hack to cmdlib
89 vec_t Rad_rint( vec_t in ){
90 if ( g_PrefsDlg.m_bNoClamp ) {
94 return (float)floor( in + 0.5 );
98 void WINAPI QE_CheckOpenGLForErrors( void ){
100 int i = qglGetError();
101 if ( i != GL_NO_ERROR ) {
102 if ( i == GL_OUT_OF_MEMORY ) {
103 sprintf( strMsg, "OpenGL out of memory error %s\nDo you wish to save before exiting?", qgluErrorString( (GLenum)i ) );
104 if ( gtk_MessageBox( g_pParentWnd->m_pWidget, strMsg, "Radiant Error", MB_YESNO ) == IDYES ) {
105 Map_SaveFile( NULL, false );
111 Sys_Printf( "Warning: OpenGL Error %s\n", qgluErrorString( (GLenum)i ) );
116 // NOTE: don't this function, use VFS instead
117 char *ExpandReletivePath( char *p ){
118 static char temp[1024];
124 if ( p[0] == '/' || p[0] == '\\' ) {
128 base = ValueForKey( g_qeglobals.d_project_entity, "basepath" );
129 sprintf( temp, "%s/%s", base, p );
133 char *copystring( char *s ){
135 b = (char*)malloc( strlen( s ) + 1 );
141 bool DoesFileExist( const char* pBuff, long& lSize ){
143 if ( file.Open( pBuff, "r" ) ) {
144 lSize += file.GetLength();
155 // I hope the modified flag is kept correctly up to date
160 // we need to do the following
161 // 1. make sure the snapshot directory exists (create it if it doesn't)
162 // 2. find out what the lastest save is based on number
163 // 3. inc that and save the map
164 CString strOrgPath, strOrgFile;
165 ExtractPath_and_Filename( currentmap, strOrgPath, strOrgFile );
166 AddSlash( strOrgPath );
167 strOrgPath += "snapshots";
170 if ( stat( strOrgPath, &Stat ) == -1 ) {
172 bGo = ( _mkdir( strOrgPath ) != -1 );
175 #if defined ( __linux__ ) || defined ( __APPLE__ )
176 bGo = ( mkdir( strOrgPath,0755 ) != -1 );
179 AddSlash( strOrgPath );
184 strNewPath = strOrgPath;
185 strNewPath += strOrgFile;
190 sprintf( buf, "%s.%i", strNewPath.GetBuffer(), nCount );
192 bGo = DoesFileExist( strFile, lSize );
195 // strFile has the next available slot
196 Map_SaveFile( strFile, false );
197 // it is still a modified map (we enter this only if this is a modified map)
198 Sys_SetTitle( currentmap );
199 Sys_MarkMapModified();
200 if ( lSize > 12 * 1024 * 1024 ) { // total size of saves > 4 mb
201 Sys_Printf( "The snapshot files in %s total more than 4 megabytes. You might consider cleaning up.", strOrgPath.GetBuffer() );
206 strMsg.Format( "Snapshot save failed.. unabled to create directory\n%s", strOrgPath.GetBuffer() );
207 gtk_MessageBox( g_pParentWnd->m_pWidget, strMsg );
216 If five minutes have passed since making a change
217 and the map hasn't been saved, save it out.
222 void QE_CheckAutoSave( void ){
223 static time_t s_start;
227 if ( modified != 1 || !s_start ) {
232 if ( ( now - s_start ) > ( 60 * g_PrefsDlg.m_nAutoSave ) ) {
233 if ( g_PrefsDlg.m_bAutoSave ) {
235 strMsg = g_PrefsDlg.m_bSnapShots ? "Autosaving snapshot..." : "Autosaving...";
236 Sys_Printf( strMsg );
238 Sys_Status( strMsg,0 );
240 // only snapshot if not working on a default map
241 if ( g_PrefsDlg.m_bSnapShots && stricmp( currentmap, "unnamed.map" ) != 0 ) {
246 Map_SaveFile( ValueForKey( g_qeglobals.d_project_entity, "autosave" ), false );
249 Sys_Status( "Autosaving...Saved.", 0 );
254 Sys_Printf( "Autosave skipped...\n" );
255 Sys_Status( "Autosave skipped...", 0 );
262 // NOTE TTimo we don't like that BuildShortPathName too much
263 // the VFS provides a vfsCleanFileName which should perform the cleanup tasks
264 // in the long run I'd like to completely get rid of this
266 // used to be disabled, but caused problems
268 // can't work with long win32 names until the BSP commands are not working differently
270 int BuildShortPathName( const char* pPath, char* pBuffer, int nBufferLen ){
272 int nResult = GetFullPathName( pPath, nBufferLen, pBuffer, &pFile );
273 nResult = GetShortPathName( pPath, pBuffer, nBufferLen );
274 if ( nResult == 0 ) {
275 strcpy( pBuffer, pPath ); // Use long filename
281 #if defined ( __linux__ ) || defined ( __APPLE__ )
282 int BuildShortPathName( const char* pPath, char* pBuffer, int nBufferLen ){
283 // remove /../ from directories
284 const char *scr = pPath; char *dst = pBuffer;
285 for ( int i = 0; ( i < nBufferLen ) && ( *scr != 0 ); i++ )
287 if ( *scr == '/' && *( scr + 1 ) == '.' && *( scr + 2 ) == '.' ) {
289 while ( dst != pBuffer && *( --dst ) != '/' )
301 return strlen( pBuffer );
306 const char *g_pPathFixups[]=
312 const int g_nPathFixupCount = sizeof(g_pPathFixups) / sizeof(const char*);
314 void QE_CheckProjectEntity()
317 char pBuff[PATH_MAX];
318 char pNewPath[PATH_MAX];
319 for (int i = 0; i < g_nPathFixupCount; i++)
321 char *pPath = ValueForKey (g_qeglobals.d_project_entity, g_pPathFixups[i]);
323 strcpy (pNewPath, pPath);
324 if (pPath[0] != '\\' && pPath[0] != '/')
325 if (GetFullPathName(pPath, PATH_MAX, pBuff, &pFile))
326 strcpy (pNewPath, pBuff);
328 BuildShortPathName (pNewPath, pBuff, PATH_MAX);
330 // check it's not ending with a filename seperator
331 if (pBuff[strlen(pBuff)-1] == '/' || pBuff[strlen(pBuff)-1] == '\\')
333 Sys_FPrintf(SYS_WRN, "WARNING: \"%s\" path in the project file has an ending file seperator, fixing.\n", g_pPathFixups[i]);
334 pBuff[strlen(pBuff)-1]=0;
337 SetKeyValue(g_qeglobals.d_project_entity, g_pPathFixups[i], pBuff);
342 void HandleXMLError( void* ctxt, const char* text, ... ){
344 static char buf[32768];
346 va_start( argptr,text );
347 vsprintf( buf, text, argptr );
348 Sys_FPrintf( SYS_ERR, "XML %s\n", buf );
352 #define DTD_BUFFER_LENGTH 1024
353 xmlDocPtr ParseXMLStream( IDataStream *stream, bool validate = false ){
354 xmlDocPtr doc = NULL;
355 bool wellFormed = false, valid = false;
356 int res, size = 1024;
358 xmlParserCtxtPtr ctxt;
361 // xmlDoValidityCheckingDefaultValue = 1;
363 xmlDoValidityCheckingDefaultValue = 0;
364 xmlSetGenericErrorFunc( NULL, HandleXMLError );
367 // HACK: use AppPath to resolve DTD location
368 // do a buffer-safe string copy and concatenate
372 char buf[DTD_BUFFER_LENGTH];
377 //assert(g_strAppPath.GetBuffer() != NULL);
378 for ( r = g_strAppPath.GetBuffer(); i < DTD_BUFFER_LENGTH && *r != '\0'; i++, r++ ) w[i] = *r;
380 for ( r = "dtds/"; i < DTD_BUFFER_LENGTH && *r != '\0'; i++, r++ ) w[i] = *r;
384 if ( i == DTD_BUFFER_LENGTH ) {
385 HandleXMLError( NULL, "ERROR: buffer overflow: DTD path length too large\n" );
389 res = stream->Read( chars, 4 );
391 ctxt = xmlCreatePushParserCtxt( NULL, NULL, chars, res, buf );
393 while ( ( res = stream->Read( chars, size ) ) > 0 )
395 xmlParseChunk( ctxt, chars, res, 0 );
397 xmlParseChunk( ctxt, chars, 0, 1 );
400 wellFormed = ( ctxt->wellFormed == 1 );
401 valid = ( ctxt->valid == 1 );
403 xmlFreeParserCtxt( ctxt );
406 if ( wellFormed && ( !validate || ( validate && valid ) ) ) {
417 xmlDocPtr ParseXMLFile( const char* filename, bool validate = false ){
419 if ( stream.Open( filename, "r" ) ) {
420 return ParseXMLStream( &stream, validate );
423 Sys_FPrintf( SYS_ERR, "Failed to open file: %s\n",filename );
427 // copy a string r to a buffer w
428 // replace $string as appropriate
429 void ReplaceTemplates( char* w, const char* r ){
431 const char *__ENGINEPATH = "TEMPLATEenginepath";
432 const char *__USERHOMEPATH = "TEMPLATEuserhomepath";
433 const char *__TOOLSPATH = "TEMPLATEtoolspath";
434 const char *__BASEDIR = "TEMPLATEbasedir";
435 const char *__APPPATH = "TEMPLATEapppath";
437 // iterate through string r
440 // check for special character
442 if ( strncmp( r + 1, __ENGINEPATH, strlen( __ENGINEPATH ) ) == 0 ) {
443 r += strlen( __ENGINEPATH ) + 1;
444 p = g_pGameDescription->mEnginePath.GetBuffer();
446 else if ( strncmp( r + 1, __USERHOMEPATH, strlen( __USERHOMEPATH ) ) == 0 ) {
447 r += strlen( __USERHOMEPATH ) + 1;
448 p = g_qeglobals.m_strHomeGame.GetBuffer();
450 else if ( strncmp( r + 1, __BASEDIR, strlen( __BASEDIR ) ) == 0 ) {
451 r += strlen( __BASEDIR ) + 1;
452 p = g_pGameDescription->mBaseGame;
454 else if ( strncmp( r + 1, __TOOLSPATH, strlen( __TOOLSPATH ) ) == 0 ) {
455 r += strlen( __TOOLSPATH ) + 1;
456 p = g_strGameToolsPath.GetBuffer();
458 else if ( strncmp( r + 1, __APPPATH, strlen( __APPPATH ) ) == 0 ) {
459 r += strlen( __APPPATH ) + 1;
460 p = g_strAppPath.GetBuffer();
468 while ( *p != '\0' ) *w++ = *p++;
478 TODO TODO TODO (don't think this got fully merged in)
479 TTimo: added project file "version", version 2 adds '#' chars to the BSP command strings
480 version 3 was .. I don't remember .. version 4 adds q3map2 commands
481 TTimo: when QE_LoadProject is called, the prefs are updated with path to the latest project and saved on disk
484 /*\todo decide on a sensible location/name for project files.*/
485 bool QE_LoadProject( const char *projectfile ){
488 xmlNodePtr node, project;
490 Sys_Printf( "Loading project file: \"%s\"\n", projectfile );
491 doc = ParseXMLFile( projectfile, true );
497 node = doc->children;
498 while ( node != NULL && node->type != XML_DTD_NODE ) node = node->next;
499 if ( node == NULL || strcmp( (char*)node->name, "project" ) != 0 ) {
500 Sys_FPrintf( SYS_ERR, "ERROR: invalid file type\n" );
504 while ( node->type != XML_ELEMENT_NODE ) node = node->next;
508 if ( g_qeglobals.d_project_entity != NULL ) {
509 Entity_Free( g_qeglobals.d_project_entity );
511 g_qeglobals.d_project_entity = Entity_Alloc();
513 for ( node = project->children; node != NULL; node = node->next )
515 if ( node->type != XML_ELEMENT_NODE ) {
520 ReplaceTemplates( buf, (char*)node->properties->next->children->content );
522 SetKeyValue( g_qeglobals.d_project_entity, (char*)node->properties->children->content, buf );
527 // project file version checking
528 // add a version checking to avoid people loading later versions of the project file and bitching
529 int ver = IntForKey( g_qeglobals.d_project_entity, "version" );
530 if ( ver > PROJECT_VERSION ) {
532 sprintf( strMsg, "This is a version %d project file. This build only supports <=%d project files.\n"
533 "Please choose another project file or upgrade your version of Radiant.", ver, PROJECT_VERSION );
534 gtk_MessageBox( g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK );
535 // set the project file to nothing so we are sure we'll ask next time?
536 g_PrefsDlg.m_strLastProject = "";
537 g_PrefsDlg.SavePrefs();
541 // set here some default project settings you need
542 if ( strlen( ValueForKey( g_qeglobals.d_project_entity, "brush_primit" ) ) == 0 ) {
543 SetKeyValue( g_qeglobals.d_project_entity, "brush_primit", "0" );
546 g_qeglobals.m_bBrushPrimitMode = IntForKey( g_qeglobals.d_project_entity, "brush_primit" );
548 g_qeglobals.m_strHomeMaps = g_qeglobals.m_strHomeGame;
549 const char* str = ValueForKey( g_qeglobals.d_project_entity, "gamename" );
550 if ( str[0] == '\0' ) {
551 str = g_pGameDescription->mBaseGame.GetBuffer();
553 g_qeglobals.m_strHomeMaps += str;
554 g_qeglobals.m_strHomeMaps += '/';
556 // don't forget to create the dirs
557 Q_mkdir( g_qeglobals.m_strHomeGame.GetBuffer(), 0775 );
558 Q_mkdir( g_qeglobals.m_strHomeMaps.GetBuffer(), 0775 );
560 // usefull for the log file and debuggin fucked up configurations from users:
561 // output the basic information of the .qe4 project file
563 // all these paths should be unix format, with a trailing slash at the end
564 // if not.. to debug, check that the project file paths are set up correctly
565 Sys_Printf( "basepath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "basepath" ) );
566 Sys_Printf( "entitypath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "entitypath" ) );
569 // check whether user_project key exists..
570 // if not, save the current project under a new name
571 if ( ValueForKey( g_qeglobals.d_project_entity, "user_project" )[0] == '\0' ) {
572 Sys_Printf( "Loaded a template project file\n" );
574 // create the user_project key
575 SetKeyValue( g_qeglobals.d_project_entity, "user_project", "1" );
577 if ( IntForKey( g_qeglobals.d_project_entity, "version" ) != PROJECT_VERSION ) {
580 "The template project '%s' has version %d. The editor binary is configured for version %d.\n"
581 "This indicates a problem in your setup.\n"
582 "I will keep going with this project till you fix this",
583 projectfile, IntForKey( g_qeglobals.d_project_entity, "version" ), PROJECT_VERSION );
584 gtk_MessageBox( g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK );
587 // create the writable project file path
588 strcpy( buf, g_qeglobals.m_strHomeGame.GetBuffer() );
589 strcat( buf, g_pGameDescription->mBaseGame.GetBuffer() );
590 strcat( buf, "/scripts/" );
591 // while the filename is already in use, increment the number we add to the end
593 char pUser[PATH_MAX];
596 sprintf( pUser, "%suser%d." PROJECT_FILETYPE, buf, counter );
598 if ( access( pUser, R_OK ) != 0 ) {
600 strcpy( buf, pUser );
604 // saving project will cause a save prefs
605 g_PrefsDlg.m_strLastProject = buf;
606 g_PrefsDlg.m_nLastProjectVer = IntForKey( g_qeglobals.d_project_entity, "version" );
607 QE_SaveProject( buf );
611 // update preferences::LastProject with path of this successfully-loaded project
613 Sys_Printf( "Setting current project in prefs to \"%s\"\n", g_PrefsDlg.m_strLastProject.GetBuffer() );
614 g_PrefsDlg.m_strLastProject = projectfile;
615 g_PrefsDlg.SavePrefs();
624 TTimo: whenever QE_SaveProject is called, prefs are updated and saved with the path to the project
627 qboolean QE_SaveProject( const char* filename ){
628 Sys_Printf( "Save project file '%s'\n", filename );
631 xmlDocPtr doc = xmlNewDoc( (xmlChar *)"1.0" );
633 xmlCreateIntSubset( doc, (xmlChar *)"project", NULL, (xmlChar *)"project.dtd" );
634 // create project node
635 doc->children->next = xmlNewDocNode( doc, NULL, (xmlChar *)"project", NULL );
637 for ( epair_t* epair = g_qeglobals.d_project_entity->epairs; epair != NULL; epair = epair->next )
639 node = xmlNewChild( doc->children->next, NULL, (xmlChar *)"key", NULL );
640 xmlSetProp( node, (xmlChar*)"name", (xmlChar*)epair->key );
641 xmlSetProp( node, (xmlChar*)"value", (xmlChar*)epair->value );
644 CreateDirectoryPath( filename );
645 if ( xmlSaveFormatFile( filename, doc, 1 ) != -1 ) {
647 Sys_Printf( "Setting current project in prefs to \"%s\"\n", filename );
648 g_PrefsDlg.m_strLastProject = filename;
649 g_PrefsDlg.SavePrefs();
655 Sys_FPrintf( SYS_ERR, "failed to save project file: \"%s\"\n", filename );
667 #define SPEED_MOVE 32
668 #define SPEED_TURN 22.5
675 Sets target / targetname on the two entities selected
676 from the first selected to the secon
679 void ConnectEntities( void ){
682 char *newtarg = NULL;
684 if ( g_qeglobals.d_select_count != 2 ) {
685 Sys_Status( "Must have two brushes selected", 0 );
690 e1 = g_qeglobals.d_select_order[0]->owner;
691 e2 = g_qeglobals.d_select_order[1]->owner;
693 if ( e1 == world_entity || e2 == world_entity ) {
694 Sys_Status( "Can't connect to the world", 0 );
700 Sys_Status( "Brushes are from same entity", 0 );
705 target = ValueForKey( e1, "target" );
706 if ( target && target[0] ) {
707 newtarg = g_strdup( target );
711 target = ValueForKey( e2, "targetname" );
712 if ( target && target[0] ) {
713 newtarg = g_strdup( target );
716 Entity_Connect( e1, e2 );
720 if ( newtarg != NULL ) {
721 SetKeyValue( e1, "target", newtarg );
722 SetKeyValue( e2, "targetname", newtarg );
726 Sys_UpdateWindows( W_XY | W_CAMERA );
729 Select_Brush( g_qeglobals.d_select_order[1] );
732 qboolean QE_SingleBrush( bool bQuiet ){
733 if ( ( selected_brushes.next == &selected_brushes )
734 || ( selected_brushes.next->next != &selected_brushes ) ) {
736 Sys_Printf( "Error: you must have a single brush selected\n" );
740 if ( selected_brushes.next->owner->eclass->fixedsize ) {
742 Sys_Printf( "Error: you cannot manipulate fixed size entities\n" );
750 void QE_InitVFS( void ){
751 // VFS initialization -----------------------
752 // we will call vfsInitDirectory, giving the directories to look in (for files in pk3's and for standalone files)
753 // we need to call in order, the mod ones first, then the base ones .. they will be searched in this order
754 // *nix systems have a dual filesystem in ~/.q3a, which is searched first .. so we need to add that too
755 Str directory,prefabs;
757 // TTimo: let's leave this to HL mode for now
758 if ( g_pGameDescription->mGameFile == "hl.game" ) {
759 // Hydra: we search the "gametools" path first so that we can provide editor
760 // specific pk3's wads and misc files for use by the editor.
761 // the relevant map compiler tools will NOT use this directory, so this helps
762 // to ensure that editor files are not used/required in release versions of maps
763 // it also helps keep your editor files all in once place, with the editor modules,
764 // plugins, scripts and config files.
765 // it also helps when testing maps, as you'll know your files won't/can't be used
766 // by the game engine itself.
769 directory = g_pGameDescription->mGameToolsPath;
770 vfsInitDirectory( directory.GetBuffer() );
773 // NOTE TTimo about the mymkdir calls .. this is a bit dirty, but a safe thing on *nix
775 // if we have a mod dir
776 if ( *ValueForKey( g_qeglobals.d_project_entity, "gamename" ) != '\0' ) {
778 #if defined ( __linux__ ) || defined ( __APPLE__ )
779 // ~/.<gameprefix>/<fs_game>
780 directory = g_qeglobals.m_strHomeGame.GetBuffer();
781 Q_mkdir( directory.GetBuffer(), 0775 );
782 directory += ValueForKey( g_qeglobals.d_project_entity, "gamename" );
783 Q_mkdir( directory.GetBuffer(), 0775 );
784 vfsInitDirectory( directory.GetBuffer() );
785 AddSlash( directory );
787 // also create the maps dir, it will be used as prompt for load/save
788 directory += "/maps";
789 Q_mkdir( directory, 0775 );
790 // and the prefabs dir
791 prefabs += "/prefabs";
792 Q_mkdir( prefabs, 0775 );
796 // <fs_basepath>/<fs_game>
797 directory = g_pGameDescription->mEnginePath;
798 directory += ValueForKey( g_qeglobals.d_project_entity, "gamename" );
799 Q_mkdir( directory.GetBuffer(), 0775 );
800 vfsInitDirectory( directory.GetBuffer() );
801 AddSlash( directory );
803 // also create the maps dir, it will be used as prompt for load/save
804 directory += "/maps";
805 Q_mkdir( directory.GetBuffer(), 0775 );
806 // and the prefabs dir
807 prefabs += "/prefabs";
808 Q_mkdir( prefabs, 0775 );
811 #if defined ( __linux__ ) || defined ( __APPLE__ )
812 // ~/.<gameprefix>/<fs_main>
813 directory = g_qeglobals.m_strHomeGame.GetBuffer();
814 directory += g_pGameDescription->mBaseGame;
815 vfsInitDirectory( directory.GetBuffer() );
818 // <fs_basepath>/<fs_main>
819 directory = g_pGameDescription->mEnginePath;
820 directory += g_pGameDescription->mBaseGame;
821 vfsInitDirectory( directory.GetBuffer() );
824 void QE_Init( void ){
826 ** initialize variables
828 g_qeglobals.d_gridsize = 8;
829 g_qeglobals.d_showgrid = true;
834 FillClassList(); // list in entity window
846 void WINAPI QE_ConvertDOSToUnixName( char *dst, const char *src ){
849 if ( *src == '\\' ) {
860 int g_numbrushes, g_numentities;
862 void QE_CountBrushesAndUpdateStatusBar( void ){
863 static int s_lastbrushcount, s_lastentitycount;
864 static qboolean s_didonce;
872 if ( active_brushes.next != NULL ) {
873 for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; b = next )
876 if ( b->brush_faces ) {
877 if ( !b->owner->eclass->fixedsize ) {
887 if ( entities.next != NULL )
889 for ( e = entities.next ; e != &entities && g_numentities != MAX_MAP_ENTITIES ; e = e->next)
895 if ( ( ( g_numbrushes != s_lastbrushcount ) || ( g_numentities != s_lastentitycount ) ) || ( !s_didonce ) ) {
896 Sys_UpdateStatusBar();
898 s_lastbrushcount = g_numbrushes;
899 s_lastentitycount = g_numentities;
904 char com_token[1024];
912 double I_FloatTime( void ){
919 // more precise, less portable
924 gettimeofday( &tp, &tzp );
928 return tp.tv_usec / 1000000.0;
931 return ( tp.tv_sec - secbase ) + tp.tv_usec / 1000000.0;
940 Parse a token out of a string
943 char *COM_Parse( char *data ){
956 while ( ( c = *data ) <= ' ' )
960 return NULL; // end of file;
966 if ( c == '/' && data[1] == '/' ) {
967 while ( *data && *data != '\n' )
973 // handle quoted strings specially
988 // parse single characters
989 if ( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':' ) {
996 // parse a regular word
1003 if ( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':' ) {
1012 char* Get_COM_Token(){
1017 =============================================================================
1021 =============================================================================
1026 char *argv[MAX_NUM_ARGVS];
1033 void ParseCommandLine( char *lpCmdLine ){
1035 argv[0] = "programname";
1037 while ( *lpCmdLine && ( argc < MAX_NUM_ARGVS ) )
1039 while ( *lpCmdLine && ( ( *lpCmdLine <= 32 ) || ( *lpCmdLine > 126 ) ) )
1043 argv[argc] = lpCmdLine;
1046 while ( *lpCmdLine && ( ( *lpCmdLine > 32 ) && ( *lpCmdLine <= 126 ) ) )
1064 Checks for the given parameter in the program's command line arguments
1065 Returns the argument number (1 to argc-1) or 0 if not present
1068 int CheckParm( const char *check ){
1071 for ( i = 1; i < argc; i++ )
1073 if ( stricmp( check, argv[i] ) ) {
1089 int ParseHex( const char *hex ){
1099 if ( *str >= '0' && *str <= '9' ) {
1102 else if ( *str >= 'a' && *str <= 'f' ) {
1103 num += 10 + *str - 'a';
1105 else if ( *str >= 'A' && *str <= 'F' ) {
1106 num += 10 + *str - 'A';
1109 Error( "Bad hex number: %s",hex );
1118 int ParseNum( const char *str ){
1119 if ( str[0] == '$' ) {
1120 return ParseHex( str + 1 );
1122 if ( str[0] == '0' && str[1] == 'x' ) {
1123 return ParseHex( str + 2 );
1128 // BSP frontend plugin
1129 // global flag for BSP frontend plugin is g_qeglobals.bBSPFrontendPlugin
1130 _QERPlugBSPFrontendTable g_BSPFrontendTable;
1132 // =============================================================================
1137 return ( GetKeyState( VK_MENU ) & 0x8000 ) != 0;
1140 #if defined ( __linux__ ) || defined ( __APPLE__ )
1144 XQueryKeymap( GDK_DISPLAY(), keys );
1146 x = XKeysymToKeycode( GDK_DISPLAY(), XK_Alt_L );
1147 if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
1151 x = XKeysymToKeycode( GDK_DISPLAY(), XK_Alt_R );
1152 if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
1160 bool Sys_ShiftDown(){
1162 return ( GetKeyState( VK_SHIFT ) & 0x8000 ) != 0;
1165 #if defined ( __linux__ ) || defined ( __APPLE__ )
1169 XQueryKeymap( GDK_DISPLAY(), keys );
1171 x = XKeysymToKeycode( GDK_DISPLAY(), XK_Shift_L );
1172 if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
1176 x = XKeysymToKeycode( GDK_DISPLAY(), XK_Shift_R );
1177 if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
1185 void Sys_MarkMapModified( void ){
1186 char title[PATH_MAX];
1188 if ( modified != 1 ) {
1189 modified = true; // mark the map as changed
1190 sprintf( title, "%s *", currentmap );
1192 QE_ConvertDOSToUnixName( title, title );
1193 Sys_SetTitle( title );
1197 void Sys_SetTitle( const char *text ){
1198 gtk_window_set_title( GTK_WINDOW( g_qeglobals_gui.d_main_window ), text );
1201 bool g_bWaitCursor = false;
1203 void WINAPI Sys_BeginWait( void ){
1204 GdkCursor *cursor = gdk_cursor_new( GDK_WATCH );
1205 gdk_window_set_cursor( g_pParentWnd->m_pWidget->window, cursor );
1206 gdk_cursor_unref( cursor );
1207 g_bWaitCursor = true;
1210 void WINAPI Sys_EndWait( void ){
1211 GdkCursor *cursor = gdk_cursor_new( GDK_LEFT_PTR );
1212 gdk_window_set_cursor( g_pParentWnd->m_pWidget->window, cursor );
1213 gdk_cursor_unref( cursor );
1214 g_bWaitCursor = false;
1217 void Sys_GetCursorPos( int *x, int *y ){
1218 // FIXME: not multihead safe
1219 gdk_window_get_pointer( NULL, x, y, NULL );
1222 void Sys_SetCursorPos( int x, int y ){
1223 // NOTE: coordinates are in GDK space, not OS space
1225 int sys_x = x - g_pParentWnd->GetGDKOffsetX();
1226 int sys_y = y - g_pParentWnd->GetGDKOffsetY();
1228 SetCursorPos( sys_x, sys_y );
1231 #if defined ( __linux__ ) || defined ( __APPLE__ )
1232 XWarpPointer( GDK_DISPLAY(), None, GDK_ROOT_WINDOW(), 0, 0, 0, 0, x, y );
1236 void Sys_Beep( void ){
1237 #if defined ( __linux__ ) || defined ( __APPLE__ )
1240 MessageBeep( MB_ICONASTERISK );
1244 double Sys_DoubleTime( void ){
1245 return clock() / 1000.0;
1249 ===============================================================
1253 ===============================================================
1256 void Sys_UpdateStatusBar( void ){
1257 extern int g_numbrushes, g_numentities;
1259 char numbrushbuffer[100] = "";
1261 sprintf( numbrushbuffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities );
1262 g_pParentWnd->SetStatusText( 2, numbrushbuffer );
1263 //Sys_Status( numbrushbuffer, 2 );
1266 void Sys_Status( const char *psz, int part ){
1267 g_pParentWnd->SetStatusText( part, psz );
1270 // =============================================================================
1274 static GtkWidget *MRU_items[MRU_MAX];
1275 static int MRU_used;
1276 typedef char MRU_filename_t[PATH_MAX];
1277 MRU_filename_t MRU_filenames[MRU_MAX];
1279 static char* MRU_GetText( int index ){
1280 return MRU_filenames[index];
1283 void buffer_write_escaped_mnemonic( char* buffer, const char* string ){
1284 while ( *string != '\0' )
1286 if ( *string == '_' ) {
1290 *buffer++ = *string++;
1295 static void MRU_SetText( int index, const char *filename ){
1296 strcpy( MRU_filenames[index], filename );
1298 char mnemonic[PATH_MAX * 2 + 4];
1300 sprintf( mnemonic + 1, "%d", index + 1 );
1303 buffer_write_escaped_mnemonic( mnemonic + 4, filename );
1304 gtk_label_set_text_with_mnemonic( GTK_LABEL( GTK_BIN( MRU_items[index] )->child ), mnemonic );
1308 int i = g_PrefsDlg.m_nMRUCount;
1311 i = 4; //FIXME: make this a define
1315 MRU_AddFile( g_PrefsDlg.m_strMRUFiles[i - 1].GetBuffer() );
1319 g_PrefsDlg.m_nMRUCount = MRU_used;
1321 for ( int i = 0; i < MRU_used; i++ )
1322 g_PrefsDlg.m_strMRUFiles[i] = MRU_GetText( i );
1325 void MRU_AddWidget( GtkWidget *widget, int pos ){
1326 if ( pos < MRU_MAX ) {
1327 MRU_items[pos] = widget;
1331 void MRU_AddFile( const char *str ){
1335 // check if file is already in our list
1336 for ( i = 0; i < MRU_used; i++ )
1338 text = MRU_GetText( i );
1340 if ( strcmp( text, str ) == 0 ) {
1343 MRU_SetText( i, MRU_GetText( i - 1 ) );
1345 MRU_SetText( 0, str );
1351 if ( MRU_used < MRU_MAX ) {
1356 for ( i = MRU_used - 1; i > 0; i-- )
1357 MRU_SetText( i, MRU_GetText( i - 1 ) );
1359 MRU_SetText( 0, str );
1360 gtk_widget_set_sensitive( MRU_items[0], TRUE );
1361 gtk_widget_show( MRU_items[MRU_used - 1] );
1364 void MRU_Activate( int index ){
1365 char *text = MRU_GetText( index );
1367 if ( access( text, R_OK ) == 0 ) {
1368 text = strdup( text );
1369 MRU_AddFile( text );
1370 Map_LoadFile( text );
1377 for ( int i = index; i < MRU_used; i++ )
1378 MRU_SetText( i, MRU_GetText( i + 1 ) );
1380 if ( MRU_used == 0 ) {
1381 gtk_label_set_text( GTK_LABEL( GTK_BIN( MRU_items[0] )->child ), "Recent Files" );
1382 gtk_widget_set_sensitive( MRU_items[0], FALSE );
1386 gtk_widget_hide( MRU_items[MRU_used] );
1392 ======================================================================
1396 ======================================================================
1399 qboolean ConfirmModified(){
1404 if ( gtk_MessageBox( g_pParentWnd->m_pWidget, "This will lose changes to the map", "warning", MB_OKCANCEL ) == IDCANCEL ) {
1410 void ProjectDialog(){
1411 const char *filename;
1412 char buffer[NAME_MAX];
1415 * Obtain the system directory name and
1416 * store it in buffer.
1419 strcpy( buffer, g_qeglobals.m_strHomeGame.GetBuffer() );
1420 strcat( buffer, g_pGameDescription->mBaseGame.GetBuffer() );
1421 strcat( buffer, "/scripts/" );
1423 // Display the Open dialog box
1424 filename = file_dialog( NULL, TRUE, _( "Open File" ), buffer, "project" );
1426 if ( filename == NULL ) {
1431 // NOTE: QE_LoadProject takes care of saving prefs with new path to the project file
1432 if ( !QE_LoadProject( filename ) ) {
1433 Sys_Printf( "Failed to load project from file: %s\n", filename );
1436 // FIXME TTimo QE_Init is probably broken if you don't call it during startup right now ..
1442 =======================================================
1446 =======================================================
1455 char *bsp_commands[256];
1458 GtkWidget *item, *menu; // menu points to a GtkMenu (not an item)
1463 menu = GTK_WIDGET( g_object_get_data( G_OBJECT( g_qeglobals_gui.d_main_window ), "menu_bsp" ) );
1465 while ( ( lst = gtk_container_children( GTK_CONTAINER( menu ) ) ) != NULL )
1466 gtk_container_remove( GTK_CONTAINER( menu ), GTK_WIDGET( lst->data ) );
1468 if ( g_PrefsDlg.m_bDetachableMenus ) {
1469 item = gtk_tearoff_menu_item_new();
1470 gtk_menu_append( GTK_MENU( menu ), item );
1471 gtk_widget_set_sensitive( item, TRUE );
1472 gtk_widget_show( item );
1475 if ( g_qeglobals.bBSPFrontendPlugin ) {
1476 CString str = g_BSPFrontendTable.m_pfnGetBSPMenu();
1478 strcpy( cTemp, str );
1479 char* token = strtok( cTemp, ",;" );
1480 if ( token && *token == ' ' ) {
1481 while ( *token == ' ' )
1486 // first token is menu name
1487 item = gtk_menu_get_attach_widget( GTK_MENU( menu ) );
1488 gtk_label_set_text( GTK_LABEL( GTK_BIN( item )->child ), token );
1490 token = strtok( NULL, ",;" );
1491 while ( token != NULL )
1493 g_BSPFrontendCommands = g_slist_append( g_BSPFrontendCommands, g_strdup( token ) );
1494 item = gtk_menu_item_new_with_label( token );
1495 gtk_widget_show( item );
1496 gtk_container_add( GTK_CONTAINER( menu ), item );
1497 gtk_signal_connect( GTK_OBJECT( item ), "activate",
1498 GTK_SIGNAL_FUNC( HandleCommand ), GINT_TO_POINTER( CMD_BSPCOMMAND + i ) );
1499 token = strtok( NULL, ",;" );
1506 for ( ep = g_qeglobals.d_project_entity->epairs; ep; ep = ep->next )
1508 if ( strncmp( ep->key, "bsp_", 4 ) == 0 ) {
1509 bsp_commands[i] = ep->key;
1510 item = gtk_menu_item_new_with_label( ep->key + 4 );
1511 gtk_widget_show( item );
1512 gtk_container_add( GTK_CONTAINER( menu ), item );
1513 gtk_signal_connect( GTK_OBJECT( item ), "activate",
1514 GTK_SIGNAL_FUNC( HandleCommand ), GINT_TO_POINTER( CMD_BSPCOMMAND + i ) );
1521 //==============================================
1523 void AddSlash( CString& strPath ){
1524 if ( strPath.GetLength() > 0 ) {
1525 if ( ( strPath.GetAt( strPath.GetLength() - 1 ) != '/' ) &&
1526 ( strPath.GetAt( strPath.GetLength() - 1 ) != '\\' ) ) {
1532 bool ExtractPath_and_Filename( const char* pPath, CString& strPath, CString& strFilename ){
1533 CString strPathName;
1534 strPathName = pPath;
1535 int nSlash = strPathName.ReverseFind( '\\' );
1536 if ( nSlash == -1 ) {
1537 // TTimo: try forward slash, some are using forward
1538 nSlash = strPathName.ReverseFind( '/' );
1540 if ( nSlash >= 0 ) {
1541 strPath = strPathName.Left( nSlash + 1 );
1542 strFilename = strPathName.Right( strPathName.GetLength() - nSlash - 1 );
1544 // TTimo: try forward slash, some are using forward
1546 strFilename = pPath;
1551 //===========================================
1553 //++timo FIXME: no longer used .. remove!
1554 char *TranslateString( char *buf ){
1555 static char buf2[32768];
1561 for ( i = 0 ; i < l ; i++ )
1563 if ( buf[i] == '\n' ) {
1576 // called whenever we need to open/close/check the console log file
1577 void Sys_LogFile( void ){
1578 if ( g_PrefsDlg.mGamesDialog.m_bLogConsole && !g_qeglobals.hLogFile ) {
1579 // settings say we should be logging and we don't have a log file .. so create it
1580 // open a file to log the console (if user prefs say so)
1581 // the file handle is g_qeglobals.hLogFile
1582 // the log file is erased
1584 name = g_strTempPath;
1585 name += "radiant.log";
1586 #if defined ( __linux__ ) || defined ( __APPLE__ )
1587 g_qeglobals.hLogFile = open( name.GetBuffer(), O_TRUNC | O_CREAT | O_WRONLY, S_IREAD | S_IWRITE );
1590 g_qeglobals.hLogFile = _open( name.GetBuffer(), _O_TRUNC | _O_CREAT | _O_WRONLY, _S_IREAD | _S_IWRITE );
1592 if ( g_qeglobals.hLogFile ) {
1593 Sys_Printf( "Started logging to %s\n", name.GetBuffer() );
1596 Sys_Printf( "Today is: %s", ctime( &localtime ) );
1597 Sys_Printf( "This is radiant '" RADIANT_VERSION "' compiled " __DATE__ "\n" );
1598 Sys_Printf( RADIANT_ABOUTMSG "\n" );
1601 gtk_MessageBox( NULL, "Failed to create log file, check write permissions in Radiant directory.\n",
1602 "Console logging", MB_OK );
1605 else if ( !g_PrefsDlg.mGamesDialog.m_bLogConsole && g_qeglobals.hLogFile ) {
1606 // settings say we should not be logging but still we have an active logfile .. close it
1609 Sys_Printf( "Closing log file at %s\n", ctime( &localtime ) );
1611 _close( g_qeglobals.hLogFile );
1613 #if defined ( __linux__ ) || defined ( __APPLE__ )
1614 close( g_qeglobals.hLogFile );
1616 g_qeglobals.hLogFile = 0;
1620 void Sys_ClearPrintf( void ){
1621 GtkTextBuffer* buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( g_qeglobals_gui.d_edit ) );
1622 gtk_text_buffer_set_text( buffer, "", -1 );
1625 // used to be around 32000, that should be way enough already
1626 #define BUFFER_SIZE 4096
1628 extern "C" void Sys_FPrintf_VA( int level, const char *text, va_list args ) {
1629 char buf[BUFFER_SIZE];
1632 vsnprintf( buf, BUFFER_SIZE, text, args );
1633 buf[BUFFER_SIZE - 1] = 0;
1634 const unsigned int length = strlen( buf );
1636 if ( g_qeglobals.hLogFile ) {
1638 _write( g_qeglobals.hLogFile, buf, length );
1639 _commit( g_qeglobals.hLogFile );
1641 #if defined ( __linux__ ) || defined ( __APPLE__ )
1642 write( g_qeglobals.hLogFile, buf, length );
1646 if ( level != SYS_NOCON ) {
1647 // TTimo: FIXME: killed the console to avoid GDI leak fuckup
1648 if ( g_qeglobals_gui.d_edit != NULL ) {
1649 GtkTextBuffer* buffer = gtk_text_view_get_buffer( GTK_TEXT_VIEW( g_qeglobals_gui.d_edit ) );
1652 gtk_text_buffer_get_end_iter( buffer, &iter );
1654 static GtkTextMark* end = gtk_text_buffer_create_mark( buffer, "end", &iter, FALSE );
1656 const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 };
1657 const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 };
1658 const GdkColor black = { 0, 0x0000, 0x0000, 0x0000 };
1660 static GtkTextTag* error_tag = gtk_text_buffer_create_tag( buffer, "red_foreground", "foreground-gdk", &red, NULL );
1661 static GtkTextTag* warning_tag = gtk_text_buffer_create_tag( buffer, "yellow_foreground", "foreground-gdk", &yellow, NULL );
1662 static GtkTextTag* standard_tag = gtk_text_buffer_create_tag( buffer, "black_foreground", "foreground-gdk", &black, NULL );
1678 gtk_text_buffer_insert_with_tags( buffer, &iter, buf, length, tag, NULL );
1680 gtk_text_view_scroll_mark_onscreen( GTK_TEXT_VIEW( g_qeglobals_gui.d_edit ), end );
1682 // update console widget immediatly if we're doing something time-consuming
1683 if ( !g_bScreenUpdates && GTK_WIDGET_REALIZED( g_qeglobals_gui.d_edit ) ) {
1684 gtk_grab_add( g_qeglobals_gui.d_edit );
1686 while ( gtk_events_pending() )
1687 gtk_main_iteration();
1689 gtk_grab_remove( g_qeglobals_gui.d_edit );
1695 // NOTE: this is the handler sent to synapse
1696 // must match PFN_SYN_PRINTF_VA
1697 extern "C" void Sys_Printf_VA( const char *text, va_list args ){
1698 Sys_FPrintf_VA( SYS_STD, text, args );
1701 extern "C" void Sys_Printf( const char *text, ... ) {
1704 va_start( args, text );
1705 Sys_FPrintf_VA( SYS_STD, text, args );
1709 extern "C" void Sys_FPrintf( int level, const char *text, ... ){
1712 va_start( args, text );
1713 Sys_FPrintf_VA( level, text, args );