]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/qe3.cpp
uncrustify! now the code is only ugly on the *inside*
[xonotic/netradiant.git] / radiant / qe3.cpp
1 /*
2    Copyright (C) 1999-2007 id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5    This file is part of GtkRadiant.
6
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.
11
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.
16
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
20  */
21
22 //
23 // Linux stuff
24 //
25 // Leonardo Zide (leo@lokigames.com)
26 //
27
28 #include "stdafx.h"
29 #include <gtk/gtk.h>
30 #include <sys/stat.h>
31 #include "gtkmisc.h"
32 #include <glib/gi18n.h>
33 #if defined ( __linux__ ) || defined ( __APPLE__ )
34 #include <unistd.h>
35 #include <X11/keysym.h>
36 #include <gdk/gdkx.h>
37 #include <gdk/gdkprivate.h>
38 #endif
39 // for the logging part
40 #include <fcntl.h>
41 #include <sys/types.h>
42
43 QEGlobals_t g_qeglobals;
44 QEGlobals_GUI_t g_qeglobals_gui;
45
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
49 #ifdef MEM_DEBUG
50
51 static GList *memblocks;
52
53 void* debug_malloc( size_t size, const char* file, int line ){
54         void *buf = g_malloc( size + 8 );
55
56         *( (const char**)buf ) = file;
57         buf = (char*)buf + 4;
58         *( (int*)buf ) = line;
59         buf = (char*)buf + 4;
60
61         memblocks = g_list_append( memblocks, buf );
62
63         return buf;
64 }
65
66 void debug_free( void *buf, const char* file, int line ){
67         const char *f;
68         int l;
69
70         if ( g_list_find( memblocks, buf ) ) {
71                 memblocks = g_list_remove( memblocks, buf );
72
73                 buf = (char*)buf - 4;
74                 l = *( (int*)buf );
75                 buf = (char*)buf - 4;
76                 f = *( (const char**)buf );
77
78                 Sys_FPrintf( SYS_DBG, "free: %s %d", file, line );
79                 Sys_FPrintf( SYS_DBG, " allocated: %s %d\n", f, l );
80
81                 g_free( buf );
82         }
83 //  else
84 //    free (buf); // from qmalloc, will leak unless we add this same hack to cmdlib
85 }
86
87 #endif
88
89 vec_t Rad_rint( vec_t in ){
90         if ( g_PrefsDlg.m_bNoClamp ) {
91                 return in;
92         }
93         else{
94                 return (float)floor( in + 0.5 );
95         }
96 }
97
98 void WINAPI QE_CheckOpenGLForErrors( void ){
99         char strMsg[1024];
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 );
106                         }
107                         _exit( 1 );
108                 }
109                 else
110                 {
111                         Sys_Printf( "Warning: OpenGL Error %s\n", qgluErrorString( (GLenum)i ) );
112                 }
113         }
114 }
115
116 // NOTE: don't this function, use VFS instead
117 char *ExpandReletivePath( char *p ){
118         static char temp[1024];
119         const char  *base;
120
121         if ( !p || !p[0] ) {
122                 return NULL;
123         }
124         if ( p[0] == '/' || p[0] == '\\' ) {
125                 return p;
126         }
127
128         base = ValueForKey( g_qeglobals.d_project_entity, "basepath" );
129         sprintf( temp, "%s/%s", base, p );
130         return temp;
131 }
132
133 char *copystring( char *s ){
134         char    *b;
135         b = (char*)malloc( strlen( s ) + 1 );
136         strcpy( b,s );
137         return b;
138 }
139
140
141 bool DoesFileExist( const char* pBuff, long& lSize ){
142         FileStream file;
143         if ( file.Open( pBuff, "r" ) ) {
144                 lSize += file.GetLength();
145                 file.Close();
146                 return true;
147         }
148         return false;
149 }
150
151
152 void Map_Snapshot(){
153         CString strMsg;
154
155         // I hope the modified flag is kept correctly up to date
156         if ( !modified ) {
157                 return;
158         }
159
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";
168         bool bGo = true;
169         struct stat Stat;
170         if ( stat( strOrgPath, &Stat ) == -1 ) {
171 #ifdef _WIN32
172                 bGo = ( _mkdir( strOrgPath ) != -1 );
173 #endif
174
175 #if defined ( __linux__ ) || defined ( __APPLE__ )
176                 bGo = ( mkdir( strOrgPath,0755 ) != -1 );
177 #endif
178         }
179         AddSlash( strOrgPath );
180         if ( bGo ) {
181                 int nCount = 0;
182                 long lSize = 0;
183                 CString strNewPath;
184                 strNewPath = strOrgPath;
185                 strNewPath += strOrgFile;
186                 CString strFile;
187                 while ( bGo )
188                 {
189                         char buf[PATH_MAX];
190                         sprintf( buf, "%s.%i", strNewPath.GetBuffer(), nCount );
191                         strFile = buf;
192                         bGo = DoesFileExist( strFile, lSize );
193                         nCount++;
194                 }
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() );
202                 }
203         }
204         else
205         {
206                 strMsg.Format( "Snapshot save failed.. unabled to create directory\n%s", strOrgPath.GetBuffer() );
207                 gtk_MessageBox( g_pParentWnd->m_pWidget, strMsg );
208         }
209         strOrgPath = "";
210         strOrgFile = "";
211 }
212 /*
213    ===============
214    QE_CheckAutoSave
215
216    If five minutes have passed since making a change
217    and the map hasn't been saved, save it out.
218    ===============
219  */
220
221
222 void QE_CheckAutoSave( void ){
223         static time_t s_start;
224         time_t now;
225         time( &now );
226
227         if ( modified != 1 || !s_start ) {
228                 s_start = now;
229                 return;
230         }
231
232         if ( ( now - s_start ) > ( 60 * g_PrefsDlg.m_nAutoSave ) ) {
233                 if ( g_PrefsDlg.m_bAutoSave ) {
234                         CString strMsg;
235                         strMsg = g_PrefsDlg.m_bSnapShots ? "Autosaving snapshot..." : "Autosaving...";
236                         Sys_Printf( strMsg );
237                         Sys_Printf( "\n" );
238                         Sys_Status( strMsg,0 );
239
240                         // only snapshot if not working on a default map
241                         if ( g_PrefsDlg.m_bSnapShots && stricmp( currentmap, "unnamed.map" ) != 0 ) {
242                                 Map_Snapshot();
243                         }
244                         else
245                         {
246                                 Map_SaveFile( ValueForKey( g_qeglobals.d_project_entity, "autosave" ), false );
247                         }
248
249                         Sys_Status( "Autosaving...Saved.", 0 );
250                         modified = 2;
251                 }
252                 else
253                 {
254                         Sys_Printf( "Autosave skipped...\n" );
255                         Sys_Status( "Autosave skipped...", 0 );
256                 }
257                 s_start = now;
258         }
259 }
260
261
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
265
266 // used to be disabled, but caused problems
267
268 // can't work with long win32 names until the BSP commands are not working differently
269 #ifdef _WIN32
270 int BuildShortPathName( const char* pPath, char* pBuffer, int nBufferLen ){
271         char *pFile = NULL;
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
276         }
277         return nResult;
278 }
279 #endif
280
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++ )
286         {
287                 if ( *scr == '/' && *( scr + 1 ) == '.' && *( scr + 2 ) == '.' ) {
288                         scr += 3;
289                         while ( dst != pBuffer && *( --dst ) != '/' )
290                         {
291                                 i--;
292                         }
293                 }
294
295                 *dst = *scr;
296
297                 scr++; dst++;
298         }
299         *dst = 0;
300
301         return strlen( pBuffer );
302 }
303 #endif
304
305 /*
306    const char *g_pPathFixups[]=
307    {
308    "basepath",
309    "autosave",
310    };
311
312    const int g_nPathFixupCount = sizeof(g_pPathFixups) / sizeof(const char*);
313
314    void QE_CheckProjectEntity()
315    {
316    char *pFile;
317    char pBuff[PATH_MAX];
318    char pNewPath[PATH_MAX];
319    for (int i = 0; i < g_nPathFixupCount; i++)
320    {
321     char *pPath = ValueForKey (g_qeglobals.d_project_entity, g_pPathFixups[i]);
322
323     strcpy (pNewPath, pPath);
324     if (pPath[0] != '\\' && pPath[0] != '/')
325       if (GetFullPathName(pPath, PATH_MAX, pBuff, &pFile))
326           strcpy (pNewPath, pBuff);
327
328     BuildShortPathName (pNewPath, pBuff, PATH_MAX);
329
330     // check it's not ending with a filename seperator
331     if (pBuff[strlen(pBuff)-1] == '/' || pBuff[strlen(pBuff)-1] == '\\')
332     {
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;
335     }
336
337     SetKeyValue(g_qeglobals.d_project_entity, g_pPathFixups[i], pBuff);
338    }
339    }
340  */
341
342 void HandleXMLError( void* ctxt, const char* text, ... ){
343         va_list argptr;
344         static char buf[32768];
345
346         va_start( argptr,text );
347         vsprintf( buf, text, argptr );
348         Sys_FPrintf( SYS_ERR, "XML %s\n", buf );
349         va_end( argptr );
350 }
351
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;
357         char chars[1024];
358         xmlParserCtxtPtr ctxt;
359
360         //if(validate)
361         //  xmlDoValidityCheckingDefaultValue = 1;
362         //else
363         xmlDoValidityCheckingDefaultValue = 0;
364         xmlSetGenericErrorFunc( NULL, HandleXMLError );
365
366         // SPoG
367         // HACK: use AppPath to resolve DTD location
368         // do a buffer-safe string copy and concatenate
369         int i;
370         char* w;
371         const char* r;
372         char buf[DTD_BUFFER_LENGTH];
373
374         w = buf;
375         i = 0;
376         // copy
377         //assert(g_strAppPath.GetBuffer() != NULL);
378         for ( r = g_strAppPath.GetBuffer(); i < DTD_BUFFER_LENGTH && *r != '\0'; i++, r++ ) w[i] = *r;
379         // concatenate
380         for ( r = "dtds/"; i < DTD_BUFFER_LENGTH && *r != '\0'; i++, r++ ) w[i] = *r;
381         // terminate
382         w[i] = '\0';
383
384         if ( i == DTD_BUFFER_LENGTH ) {
385                 HandleXMLError( NULL, "ERROR: buffer overflow: DTD path length too large\n" );
386                 return NULL;
387         }
388
389         res = stream->Read( chars, 4 );
390         if ( res > 0 ) {
391                 ctxt = xmlCreatePushParserCtxt( NULL, NULL, chars, res, buf );
392
393                 while ( ( res = stream->Read( chars, size ) ) > 0 )
394                 {
395                         xmlParseChunk( ctxt, chars, res, 0 );
396                 }
397                 xmlParseChunk( ctxt, chars, 0, 1 );
398                 doc = ctxt->myDoc;
399
400                 wellFormed = ( ctxt->wellFormed == 1 );
401                 valid = ( ctxt->valid == 1 );
402
403                 xmlFreeParserCtxt( ctxt );
404         }
405
406         if ( wellFormed && ( !validate || ( validate && valid ) ) ) {
407                 return doc;
408         }
409
410         if ( doc != NULL ) {
411                 xmlFreeDoc( doc );
412         }
413
414         return NULL;
415 }
416
417 xmlDocPtr ParseXMLFile( const char* filename, bool validate = false ){
418         FileStream stream;
419         if ( stream.Open( filename, "r" ) ) {
420                 return ParseXMLStream( &stream, validate );
421         }
422
423         Sys_FPrintf( SYS_ERR, "Failed to open file: %s\n",filename );
424         return NULL;
425 }
426
427 // copy a string r to a buffer w
428 // replace $string as appropriate
429 void ReplaceTemplates( char* w, const char* r ){
430         const char *p;
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";
436
437         // iterate through string r
438         while ( *r != '\0' )
439         {
440                 // check for special character
441                 if ( *r == '$' ) {
442                         if ( strncmp( r + 1, __ENGINEPATH, strlen( __ENGINEPATH ) ) == 0 ) {
443                                 r += strlen( __ENGINEPATH ) + 1;
444                                 p = g_pGameDescription->mEnginePath.GetBuffer();
445                         }
446                         else if ( strncmp( r + 1, __USERHOMEPATH, strlen( __USERHOMEPATH ) ) == 0 ) {
447                                 r += strlen( __USERHOMEPATH ) + 1;
448                                 p = g_qeglobals.m_strHomeGame.GetBuffer();
449                         }
450                         else if ( strncmp( r + 1, __BASEDIR, strlen( __BASEDIR ) ) == 0 ) {
451                                 r += strlen( __BASEDIR ) + 1;
452                                 p = g_pGameDescription->mBaseGame;
453                         }
454                         else if ( strncmp( r + 1, __TOOLSPATH, strlen( __TOOLSPATH ) ) == 0 ) {
455                                 r += strlen( __TOOLSPATH ) + 1;
456                                 p = g_strGameToolsPath.GetBuffer();
457                         }
458                         else if ( strncmp( r + 1, __APPPATH, strlen( __APPPATH ) ) == 0 ) {
459                                 r += strlen( __APPPATH ) + 1;
460                                 p = g_strAppPath.GetBuffer();
461                         }
462                         else
463                         {
464                                 r++;
465                                 p = "$";
466                         }
467
468                         while ( *p != '\0' ) *w++ = *p++;
469                 }
470                 else{ *w++ = *r++; }
471         }
472         *w = '\0';
473 }
474
475 /*
476    ===========
477    QE_LoadProject
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
482    ===========
483  */
484 /*\todo decide on a sensible location/name for project files.*/
485 bool QE_LoadProject( const char *projectfile ){
486         char buf[1024];
487         xmlDocPtr doc;
488         xmlNodePtr node, project;
489
490         Sys_Printf( "Loading project file: \"%s\"\n", projectfile );
491         doc = ParseXMLFile( projectfile, true );
492
493         if ( doc == NULL ) {
494                 return false;
495         }
496
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" );
501                 return false;
502         }
503
504         while ( node->type != XML_ELEMENT_NODE ) node = node->next;
505         // <project>
506         project = node;
507
508         if ( g_qeglobals.d_project_entity != NULL ) {
509                 Entity_Free( g_qeglobals.d_project_entity );
510         }
511         g_qeglobals.d_project_entity = Entity_Alloc();
512
513         for ( node = project->children; node != NULL; node = node->next )
514         {
515                 if ( node->type != XML_ELEMENT_NODE ) {
516                         continue;
517                 }
518
519                 // <key>
520                 ReplaceTemplates( buf, (char*)node->properties->next->children->content );
521
522                 SetKeyValue( g_qeglobals.d_project_entity, (char*)node->properties->children->content, buf );
523         }
524
525         xmlFreeDoc( doc );
526
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 ) {
531                 char strMsg[1024];
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();
538                 return false;
539         }
540
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" );
544         }
545
546         g_qeglobals.m_bBrushPrimitMode = IntForKey( g_qeglobals.d_project_entity, "brush_primit" );
547
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();
552         }
553         g_qeglobals.m_strHomeMaps += str;
554         g_qeglobals.m_strHomeMaps += '/';
555
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 );
559
560         // usefull for the log file and debuggin fucked up configurations from users:
561         // output the basic information of the .qe4 project file
562         // SPoG
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" ) );
567
568
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" );
573
574                 // create the user_project key
575                 SetKeyValue( g_qeglobals.d_project_entity, "user_project", "1" );
576
577                 if ( IntForKey( g_qeglobals.d_project_entity, "version" ) != PROJECT_VERSION ) {
578                         char strMsg[2048];
579                         sprintf( strMsg,
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 );
585                 }
586
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
592                 int counter = 0;
593                 char pUser[PATH_MAX];
594                 while ( 1 )
595                 {
596                         sprintf( pUser, "%suser%d." PROJECT_FILETYPE, buf, counter );
597                         counter++;
598                         if ( access( pUser, R_OK ) != 0 ) {
599                                 // this is the one
600                                 strcpy( buf, pUser );
601                                 break;
602                         }
603                 }
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 );
608         }
609         else
610         {
611                 // update preferences::LastProject with path of this successfully-loaded project
612                 // save preferences
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();
616         }
617
618         return true;
619 }
620
621 /*
622    ===========
623    QE_SaveProject
624    TTimo: whenever QE_SaveProject is called, prefs are updated and saved with the path to the project
625    ===========
626  */
627 qboolean QE_SaveProject( const char* filename ){
628         Sys_Printf( "Save project file '%s'\n", filename );
629
630         xmlNodePtr node;
631         xmlDocPtr doc = xmlNewDoc( (xmlChar *)"1.0" );
632         // create DTD node
633         xmlCreateIntSubset( doc, (xmlChar *)"project", NULL, (xmlChar *)"project.dtd" );
634         // create project node
635         doc->children->next = xmlNewDocNode( doc, NULL, (xmlChar *)"project", NULL );
636
637         for ( epair_t* epair = g_qeglobals.d_project_entity->epairs; epair != NULL; epair = epair->next )
638         {
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 );
642         }
643
644         CreateDirectoryPath( filename );
645         if ( xmlSaveFormatFile( filename, doc, 1 ) != -1 ) {
646                 xmlFreeDoc( doc );
647                 Sys_Printf( "Setting current project in prefs to \"%s\"\n", filename );
648                 g_PrefsDlg.m_strLastProject = filename;
649                 g_PrefsDlg.SavePrefs();
650                 return TRUE;
651         }
652         else
653         {
654                 xmlFreeDoc( doc );
655                 Sys_FPrintf( SYS_ERR, "failed to save project file: \"%s\"\n", filename );
656                 return FALSE;
657         }
658 }
659
660
661
662 /*
663    ===========
664    QE_KeyDown
665    ===========
666  */
667 #define SPEED_MOVE  32
668 #define SPEED_TURN  22.5
669
670
671 /*
672    ===============
673    ConnectEntities
674
675    Sets target / targetname on the two entities selected
676    from the first selected to the secon
677    ===============
678  */
679 void ConnectEntities( void ){
680         entity_t    *e1, *e2;
681         const char      *target;
682         char        *newtarg = NULL;
683
684         if ( g_qeglobals.d_select_count != 2 ) {
685                 Sys_Status( "Must have two brushes selected", 0 );
686                 Sys_Beep();
687                 return;
688         }
689
690         e1 = g_qeglobals.d_select_order[0]->owner;
691         e2 = g_qeglobals.d_select_order[1]->owner;
692
693         if ( e1 == world_entity || e2 == world_entity ) {
694                 Sys_Status( "Can't connect to the world", 0 );
695                 Sys_Beep();
696                 return;
697         }
698
699         if ( e1 == e2 ) {
700                 Sys_Status( "Brushes are from same entity", 0 );
701                 Sys_Beep();
702                 return;
703         }
704
705         target = ValueForKey( e1, "target" );
706         if ( target && target[0] ) {
707                 newtarg = g_strdup( target );
708         }
709         else
710         {
711                 target = ValueForKey( e2, "targetname" );
712                 if ( target && target[0] ) {
713                         newtarg = g_strdup( target );
714                 }
715                 else{
716                         Entity_Connect( e1, e2 );
717                 }
718         }
719
720         if ( newtarg != NULL ) {
721                 SetKeyValue( e1, "target", newtarg );
722                 SetKeyValue( e2, "targetname", newtarg );
723                 g_free( newtarg );
724         }
725
726         Sys_UpdateWindows( W_XY | W_CAMERA );
727
728         Select_Deselect();
729         Select_Brush( g_qeglobals.d_select_order[1] );
730 }
731
732 qboolean QE_SingleBrush( bool bQuiet ){
733         if ( ( selected_brushes.next == &selected_brushes )
734                  || ( selected_brushes.next->next != &selected_brushes ) ) {
735                 if ( !bQuiet ) {
736                         Sys_Printf( "Error: you must have a single brush selected\n" );
737                 }
738                 return false;
739         }
740         if ( selected_brushes.next->owner->eclass->fixedsize ) {
741                 if ( !bQuiet ) {
742                         Sys_Printf( "Error: you cannot manipulate fixed size entities\n" );
743                 }
744                 return false;
745         }
746
747         return true;
748 }
749
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;
756
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.
767
768                 // <gametools>
769                 directory = g_pGameDescription->mGameToolsPath;
770                 vfsInitDirectory( directory.GetBuffer() );
771         }
772
773         // NOTE TTimo about the mymkdir calls .. this is a bit dirty, but a safe thing on *nix
774
775         // if we have a mod dir
776         if ( *ValueForKey( g_qeglobals.d_project_entity, "gamename" ) != '\0' ) {
777
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 );
786                 prefabs = 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 );
793
794 #endif
795
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 );
802                 prefabs = 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 );
809         }
810
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() );
816 #endif
817
818         // <fs_basepath>/<fs_main>
819         directory = g_pGameDescription->mEnginePath;
820         directory += g_pGameDescription->mBaseGame;
821         vfsInitDirectory( directory.GetBuffer() );
822 }
823
824 void QE_Init( void ){
825         /*
826         ** initialize variables
827         */
828         g_qeglobals.d_gridsize = 8;
829         g_qeglobals.d_showgrid = true;
830
831         QE_InitVFS();
832
833         Eclass_Init();
834         FillClassList();    // list in entity window
835         Map_Init();
836
837         FillTextureMenu();
838         FillBSPMenu();
839
840         /*
841         ** other stuff
842         */
843         Z_Init();
844 }
845
846 void WINAPI QE_ConvertDOSToUnixName( char *dst, const char *src ){
847         while ( *src )
848         {
849                 if ( *src == '\\' ) {
850                         *dst = '/';
851                 }
852                 else{
853                         *dst = *src;
854                 }
855                 dst++; src++;
856         }
857         *dst = 0;
858 }
859
860 int g_numbrushes, g_numentities;
861
862 void QE_CountBrushesAndUpdateStatusBar( void ){
863         static int s_lastbrushcount, s_lastentitycount;
864         static qboolean s_didonce;
865
866         //entity_t   *e;
867         brush_t    *b, *next;
868
869         g_numbrushes = 0;
870         g_numentities = 0;
871
872         if ( active_brushes.next != NULL ) {
873                 for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; b = next )
874                 {
875                         next = b->next;
876                         if ( b->brush_faces ) {
877                                 if ( !b->owner->eclass->fixedsize ) {
878                                         g_numbrushes++;
879                                 }
880                                 else{
881                                         g_numentities++;
882                                 }
883                         }
884                 }
885         }
886 /*
887     if ( entities.next != NULL )
888     {
889         for ( e = entities.next ; e != &entities && g_numentities != MAX_MAP_ENTITIES ; e = e->next)
890         {
891             g_numentities++;
892         }
893     }
894  */
895         if ( ( ( g_numbrushes != s_lastbrushcount ) || ( g_numentities != s_lastentitycount ) ) || ( !s_didonce ) ) {
896                 Sys_UpdateStatusBar();
897
898                 s_lastbrushcount = g_numbrushes;
899                 s_lastentitycount = g_numentities;
900                 s_didonce = true;
901         }
902 }
903
904 char com_token[1024];
905 qboolean com_eof;
906
907 /*
908    ================
909    I_FloatTime
910    ================
911  */
912 double I_FloatTime( void ){
913         time_t t;
914
915         time( &t );
916
917         return t;
918 #if 0
919 // more precise, less portable
920         struct timeval tp;
921         struct timezone tzp;
922         static int secbase;
923
924         gettimeofday( &tp, &tzp );
925
926         if ( !secbase ) {
927                 secbase = tp.tv_sec;
928                 return tp.tv_usec / 1000000.0;
929         }
930
931         return ( tp.tv_sec - secbase ) + tp.tv_usec / 1000000.0;
932 #endif
933 }
934
935
936 /*
937    ==============
938    COM_Parse
939
940    Parse a token out of a string
941    ==============
942  */
943 char *COM_Parse( char *data ){
944         int c;
945         int len;
946
947         len = 0;
948         com_token[0] = 0;
949
950         if ( !data ) {
951                 return NULL;
952         }
953
954 // skip whitespace
955 skipwhite:
956         while ( ( c = *data ) <= ' ' )
957         {
958                 if ( c == 0 ) {
959                         com_eof = true;
960                         return NULL;            // end of file;
961                 }
962                 data++;
963         }
964
965 // skip // comments
966         if ( c == '/' && data[1] == '/' ) {
967                 while ( *data && *data != '\n' )
968                         data++;
969                 goto skipwhite;
970         }
971
972
973 // handle quoted strings specially
974         if ( c == '\"' ) {
975                 data++;
976                 do
977                 {
978                         c = *data++;
979                         if ( c == '\"' ) {
980                                 com_token[len] = 0;
981                                 return data;
982                         }
983                         com_token[len] = c;
984                         len++;
985                 } while ( 1 );
986         }
987
988 // parse single characters
989         if ( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':' ) {
990                 com_token[len] = c;
991                 len++;
992                 com_token[len] = 0;
993                 return data + 1;
994         }
995
996 // parse a regular word
997         do
998         {
999                 com_token[len] = c;
1000                 data++;
1001                 len++;
1002                 c = *data;
1003                 if ( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ':' ) {
1004                         break;
1005                 }
1006         } while ( c > 32 );
1007
1008         com_token[len] = 0;
1009         return data;
1010 }
1011
1012 char* Get_COM_Token(){
1013         return com_token;
1014 }
1015
1016 /*
1017    =============================================================================
1018
1019                         MISC FUNCTIONS
1020
1021    =============================================================================
1022  */
1023
1024
1025 int argc;
1026 char    *argv[MAX_NUM_ARGVS];
1027
1028 /*
1029    ============
1030    ParseCommandLine
1031    ============
1032  */
1033 void ParseCommandLine( char *lpCmdLine ){
1034         argc = 1;
1035         argv[0] = "programname";
1036
1037         while ( *lpCmdLine && ( argc < MAX_NUM_ARGVS ) )
1038         {
1039                 while ( *lpCmdLine && ( ( *lpCmdLine <= 32 ) || ( *lpCmdLine > 126 ) ) )
1040                         lpCmdLine++;
1041
1042                 if ( *lpCmdLine ) {
1043                         argv[argc] = lpCmdLine;
1044                         argc++;
1045
1046                         while ( *lpCmdLine && ( ( *lpCmdLine > 32 ) && ( *lpCmdLine <= 126 ) ) )
1047                                 lpCmdLine++;
1048
1049                         if ( *lpCmdLine ) {
1050                                 *lpCmdLine = 0;
1051                                 lpCmdLine++;
1052                         }
1053
1054                 }
1055         }
1056 }
1057
1058
1059
1060 /*
1061    =================
1062    CheckParm
1063
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
1066    =================
1067  */
1068 int CheckParm( const char *check ){
1069         int i;
1070
1071         for ( i = 1; i < argc; i++ )
1072         {
1073                 if ( stricmp( check, argv[i] ) ) {
1074                         return i;
1075                 }
1076         }
1077
1078         return 0;
1079 }
1080
1081
1082
1083
1084 /*
1085    ==============
1086    ParseNum / ParseHex
1087    ==============
1088  */
1089 int ParseHex( const char *hex ){
1090         const char    *str;
1091         int num;
1092
1093         num = 0;
1094         str = hex;
1095
1096         while ( *str )
1097         {
1098                 num <<= 4;
1099                 if ( *str >= '0' && *str <= '9' ) {
1100                         num += *str - '0';
1101                 }
1102                 else if ( *str >= 'a' && *str <= 'f' ) {
1103                         num += 10 + *str - 'a';
1104                 }
1105                 else if ( *str >= 'A' && *str <= 'F' ) {
1106                         num += 10 + *str - 'A';
1107                 }
1108                 else{
1109                         Error( "Bad hex number: %s",hex );
1110                 }
1111                 str++;
1112         }
1113
1114         return num;
1115 }
1116
1117
1118 int ParseNum( const char *str ){
1119         if ( str[0] == '$' ) {
1120                 return ParseHex( str + 1 );
1121         }
1122         if ( str[0] == '0' && str[1] == 'x' ) {
1123                 return ParseHex( str + 2 );
1124         }
1125         return atol( str );
1126 }
1127
1128 // BSP frontend plugin
1129 // global flag for BSP frontend plugin is g_qeglobals.bBSPFrontendPlugin
1130 _QERPlugBSPFrontendTable g_BSPFrontendTable;
1131
1132 // =============================================================================
1133 // Sys_ functions
1134
1135 bool Sys_AltDown(){
1136 #ifdef _WIN32
1137         return ( GetKeyState( VK_MENU ) & 0x8000 ) != 0;
1138 #endif
1139
1140 #if defined ( __linux__ ) || defined ( __APPLE__ )
1141         char keys[32];
1142         int x;
1143
1144         XQueryKeymap( GDK_DISPLAY(), keys );
1145
1146         x = XKeysymToKeycode( GDK_DISPLAY(), XK_Alt_L );
1147         if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
1148                 return true;
1149         }
1150
1151         x = XKeysymToKeycode( GDK_DISPLAY(), XK_Alt_R );
1152         if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
1153                 return true;
1154         }
1155
1156         return false;
1157 #endif
1158 }
1159
1160 bool Sys_ShiftDown(){
1161 #ifdef _WIN32
1162         return ( GetKeyState( VK_SHIFT ) & 0x8000 ) != 0;
1163 #endif
1164
1165 #if defined ( __linux__ ) || defined ( __APPLE__ )
1166         char keys[32];
1167         int x;
1168
1169         XQueryKeymap( GDK_DISPLAY(), keys );
1170
1171         x = XKeysymToKeycode( GDK_DISPLAY(), XK_Shift_L );
1172         if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
1173                 return true;
1174         }
1175
1176         x = XKeysymToKeycode( GDK_DISPLAY(), XK_Shift_R );
1177         if ( keys[x / 8] & ( 1 << ( x % 8 ) ) ) {
1178                 return true;
1179         }
1180
1181         return false;
1182 #endif
1183 }
1184
1185 void Sys_MarkMapModified( void ){
1186         char title[PATH_MAX];
1187
1188         if ( modified != 1 ) {
1189                 modified = true; // mark the map as changed
1190                 sprintf( title, "%s *", currentmap );
1191
1192                 QE_ConvertDOSToUnixName( title, title );
1193                 Sys_SetTitle( title );
1194         }
1195 }
1196
1197 void Sys_SetTitle( const char *text ){
1198         gtk_window_set_title( GTK_WINDOW( g_qeglobals_gui.d_main_window ), text );
1199 }
1200
1201 bool g_bWaitCursor = false;
1202
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;
1208 }
1209
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;
1215 }
1216
1217 void Sys_GetCursorPos( int *x, int *y ){
1218         // FIXME: not multihead safe
1219         gdk_window_get_pointer( NULL, x, y, NULL );
1220 }
1221
1222 void Sys_SetCursorPos( int x, int y ){
1223         // NOTE: coordinates are in GDK space, not OS space
1224 #ifdef _WIN32
1225         int sys_x = x - g_pParentWnd->GetGDKOffsetX();
1226         int sys_y = y - g_pParentWnd->GetGDKOffsetY();
1227
1228         SetCursorPos( sys_x, sys_y );
1229 #endif
1230
1231 #if defined ( __linux__ ) || defined ( __APPLE__ )
1232         XWarpPointer( GDK_DISPLAY(), None, GDK_ROOT_WINDOW(), 0, 0, 0, 0, x, y );
1233 #endif
1234 }
1235
1236 void Sys_Beep( void ){
1237 #if defined ( __linux__ ) || defined ( __APPLE__ )
1238         gdk_beep();
1239 #else
1240         MessageBeep( MB_ICONASTERISK );
1241 #endif
1242 }
1243
1244 double Sys_DoubleTime( void ){
1245         return clock() / 1000.0;
1246 }
1247
1248 /*
1249    ===============================================================
1250
1251    STATUS WINDOW
1252
1253    ===============================================================
1254  */
1255
1256 void Sys_UpdateStatusBar( void ){
1257         extern int g_numbrushes, g_numentities;
1258
1259         char numbrushbuffer[100] = "";
1260
1261         sprintf( numbrushbuffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities );
1262         g_pParentWnd->SetStatusText( 2, numbrushbuffer );
1263         //Sys_Status( numbrushbuffer, 2 );
1264 }
1265
1266 void Sys_Status( const char *psz, int part ){
1267         g_pParentWnd->SetStatusText( part, psz );
1268 }
1269
1270 // =============================================================================
1271 // MRU
1272
1273 #define MRU_MAX 4
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];
1278
1279 static char* MRU_GetText( int index ){
1280         return MRU_filenames[index];
1281 }
1282
1283 void buffer_write_escaped_mnemonic( char* buffer, const char* string ){
1284         while ( *string != '\0' )
1285         {
1286                 if ( *string == '_' ) {
1287                         *buffer++ = '_';
1288                 }
1289
1290                 *buffer++ = *string++;
1291         }
1292         *buffer = '\0';
1293 }
1294
1295 static void MRU_SetText( int index, const char *filename ){
1296         strcpy( MRU_filenames[index], filename );
1297
1298         char mnemonic[PATH_MAX * 2 + 4];
1299         mnemonic[0] = '_';
1300         sprintf( mnemonic + 1, "%d", index + 1 );
1301         mnemonic[2] = '-';
1302         mnemonic[3] = ' ';
1303         buffer_write_escaped_mnemonic( mnemonic + 4, filename );
1304         gtk_label_set_text_with_mnemonic( GTK_LABEL( GTK_BIN( MRU_items[index] )->child ), mnemonic );
1305 }
1306
1307 void MRU_Load(){
1308         int i = g_PrefsDlg.m_nMRUCount;
1309
1310         if ( i > 4 ) {
1311                 i = 4; //FIXME: make this a define
1312
1313         }
1314         for (; i > 0; i-- )
1315                 MRU_AddFile( g_PrefsDlg.m_strMRUFiles[i - 1].GetBuffer() );
1316 }
1317
1318 void MRU_Save(){
1319         g_PrefsDlg.m_nMRUCount = MRU_used;
1320
1321         for ( int i = 0; i < MRU_used; i++ )
1322                 g_PrefsDlg.m_strMRUFiles[i] = MRU_GetText( i );
1323 }
1324
1325 void MRU_AddWidget( GtkWidget *widget, int pos ){
1326         if ( pos < MRU_MAX ) {
1327                 MRU_items[pos] = widget;
1328         }
1329 }
1330
1331 void MRU_AddFile( const char *str ){
1332         int i;
1333         char* text;
1334
1335         // check if file is already in our list
1336         for ( i = 0; i < MRU_used; i++ )
1337         {
1338                 text = MRU_GetText( i );
1339
1340                 if ( strcmp( text, str ) == 0 ) {
1341                         // reorder menu
1342                         for (; i > 0; i-- )
1343                                 MRU_SetText( i, MRU_GetText( i - 1 ) );
1344
1345                         MRU_SetText( 0, str );
1346
1347                         return;
1348                 }
1349         }
1350
1351         if ( MRU_used < MRU_MAX ) {
1352                 MRU_used++;
1353         }
1354
1355         // move items down
1356         for ( i = MRU_used - 1; i > 0; i-- )
1357                 MRU_SetText( i, MRU_GetText( i - 1 ) );
1358
1359         MRU_SetText( 0, str );
1360         gtk_widget_set_sensitive( MRU_items[0], TRUE );
1361         gtk_widget_show( MRU_items[MRU_used - 1] );
1362 }
1363
1364 void MRU_Activate( int index ){
1365         char *text = MRU_GetText( index );
1366
1367         if ( access( text, R_OK ) == 0 ) {
1368                 text = strdup( text );
1369                 MRU_AddFile( text );
1370                 Map_LoadFile( text );
1371                 free( text );
1372         }
1373         else
1374         {
1375                 MRU_used--;
1376
1377                 for ( int i = index; i < MRU_used; i++ )
1378                         MRU_SetText( i, MRU_GetText( i + 1 ) );
1379
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 );
1383                 }
1384                 else
1385                 {
1386                         gtk_widget_hide( MRU_items[MRU_used] );
1387                 }
1388         }
1389 }
1390
1391 /*
1392    ======================================================================
1393
1394    FILE DIALOGS
1395
1396    ======================================================================
1397  */
1398
1399 qboolean ConfirmModified(){
1400         if ( !modified ) {
1401                 return true;
1402         }
1403
1404         if ( gtk_MessageBox( g_pParentWnd->m_pWidget, "This will lose changes to the map", "warning", MB_OKCANCEL ) == IDCANCEL ) {
1405                 return false;
1406         }
1407         return true;
1408 }
1409
1410 void ProjectDialog(){
1411         const char *filename;
1412         char buffer[NAME_MAX];
1413
1414         /*
1415          * Obtain the system directory name and
1416          * store it in buffer.
1417          */
1418
1419         strcpy( buffer, g_qeglobals.m_strHomeGame.GetBuffer() );
1420         strcat( buffer, g_pGameDescription->mBaseGame.GetBuffer() );
1421         strcat( buffer, "/scripts/" );
1422
1423         // Display the Open dialog box
1424         filename = file_dialog( NULL, TRUE, _( "Open File" ), buffer, "project" );
1425
1426         if ( filename == NULL ) {
1427                 return; // canceled
1428
1429         }
1430         // Open the file.
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 );
1434         }
1435         else{
1436                 // FIXME TTimo QE_Init is probably broken if you don't call it during startup right now ..
1437                 QE_Init();
1438         }
1439 }
1440
1441 /*
1442    =======================================================
1443
1444    Menu modifications
1445
1446    =======================================================
1447  */
1448
1449 /*
1450    ==================
1451    FillBSPMenu
1452
1453    ==================
1454  */
1455 char *bsp_commands[256];
1456
1457 void FillBSPMenu(){
1458         GtkWidget *item, *menu; // menu points to a GtkMenu (not an item)
1459         epair_t *ep;
1460         GList *lst;
1461         int i;
1462
1463         menu = GTK_WIDGET( g_object_get_data( G_OBJECT( g_qeglobals_gui.d_main_window ), "menu_bsp" ) );
1464
1465         while ( ( lst = gtk_container_children( GTK_CONTAINER( menu ) ) ) != NULL )
1466                 gtk_container_remove( GTK_CONTAINER( menu ), GTK_WIDGET( lst->data ) );
1467
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 );
1473         }
1474
1475         if ( g_qeglobals.bBSPFrontendPlugin ) {
1476                 CString str = g_BSPFrontendTable.m_pfnGetBSPMenu();
1477                 char cTemp[1024];
1478                 strcpy( cTemp, str );
1479                 char* token = strtok( cTemp, ",;" );
1480                 if ( token && *token == ' ' ) {
1481                         while ( *token == ' ' )
1482                                 token++;
1483                 }
1484                 i = 0;
1485
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 );
1489
1490                 token = strtok( NULL, ",;" );
1491                 while ( token != NULL )
1492                 {
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, ",;" );
1500                         i++;
1501                 }
1502         }
1503         else
1504         {
1505                 i = 0;
1506                 for ( ep = g_qeglobals.d_project_entity->epairs; ep; ep = ep->next )
1507                 {
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 ) );
1515                                 i++;
1516                         }
1517                 }
1518         }
1519 }
1520
1521 //==============================================
1522
1523 void AddSlash( CString& strPath ){
1524         if ( strPath.GetLength() > 0 ) {
1525                 if ( ( strPath.GetAt( strPath.GetLength() - 1 ) != '/' ) &&
1526                          ( strPath.GetAt( strPath.GetLength() - 1 ) != '\\' ) ) {
1527                         strPath += '/';
1528                 }
1529         }
1530 }
1531
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( '/' );
1539         }
1540         if ( nSlash >= 0 ) {
1541                 strPath = strPathName.Left( nSlash + 1 );
1542                 strFilename = strPathName.Right( strPathName.GetLength() - nSlash - 1 );
1543         }
1544         // TTimo: try forward slash, some are using forward
1545         else{
1546                 strFilename = pPath;
1547         }
1548         return true;
1549 }
1550
1551 //===========================================
1552
1553 //++timo FIXME: no longer used .. remove!
1554 char *TranslateString( char *buf ){
1555         static char buf2[32768];
1556         int i, l;
1557         char          *out;
1558
1559         l = strlen( buf );
1560         out = buf2;
1561         for ( i = 0 ; i < l ; i++ )
1562         {
1563                 if ( buf[i] == '\n' ) {
1564                         *out++ = '\r';
1565                         *out++ = '\n';
1566                 }
1567                 else{
1568                         *out++ = buf[i];
1569                 }
1570         }
1571         *out++ = 0;
1572
1573         return buf2;
1574 }
1575
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
1583                 Str name;
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 );
1588 #endif
1589 #ifdef _WIN32
1590                 g_qeglobals.hLogFile = _open( name.GetBuffer(), _O_TRUNC | _O_CREAT | _O_WRONLY, _S_IREAD | _S_IWRITE );
1591 #endif
1592                 if ( g_qeglobals.hLogFile ) {
1593                         Sys_Printf( "Started logging to %s\n", name.GetBuffer() );
1594                         time_t localtime;
1595                         time( &localtime );
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" );
1599                 }
1600                 else{
1601                         gtk_MessageBox( NULL, "Failed to create log file, check write permissions in Radiant directory.\n",
1602                                                         "Console logging", MB_OK );
1603                 }
1604         }
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
1607                 time_t localtime;
1608                 time( &localtime );
1609                 Sys_Printf( "Closing log file at %s\n", ctime( &localtime ) );
1610                 #ifdef _WIN32
1611                 _close( g_qeglobals.hLogFile );
1612                 #endif
1613                 #if defined ( __linux__ ) || defined ( __APPLE__ )
1614                 close( g_qeglobals.hLogFile );
1615                 #endif
1616                 g_qeglobals.hLogFile = 0;
1617         }
1618 }
1619
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 );
1623 }
1624
1625 // used to be around 32000, that should be way enough already
1626 #define BUFFER_SIZE 4096
1627
1628 extern "C" void Sys_FPrintf_VA( int level, const char *text, va_list args ) {
1629         char buf[BUFFER_SIZE];
1630
1631         buf[0] = 0;
1632         vsnprintf( buf, BUFFER_SIZE, text, args );
1633         buf[BUFFER_SIZE - 1] = 0;
1634         const unsigned int length = strlen( buf );
1635
1636         if ( g_qeglobals.hLogFile ) {
1637 #ifdef _WIN32
1638                 _write( g_qeglobals.hLogFile, buf, length );
1639                 _commit( g_qeglobals.hLogFile );
1640 #endif
1641 #if defined ( __linux__ ) || defined ( __APPLE__ )
1642                 write( g_qeglobals.hLogFile, buf, length );
1643 #endif
1644         }
1645
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 ) );
1650
1651                         GtkTextIter iter;
1652                         gtk_text_buffer_get_end_iter( buffer, &iter );
1653
1654                         static GtkTextMark* end = gtk_text_buffer_create_mark( buffer, "end", &iter, FALSE );
1655
1656                         const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 };
1657                         const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 };
1658                         const GdkColor black = { 0, 0x0000, 0x0000, 0x0000 };
1659
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 );
1663                         GtkTextTag* tag;
1664                         switch ( level )
1665                         {
1666                         case SYS_WRN:
1667                                 tag = warning_tag;
1668                                 break;
1669                         case SYS_ERR:
1670                                 tag = error_tag;
1671                                 break;
1672                         case SYS_STD:
1673                         case SYS_VRB:
1674                         default:
1675                                 tag = standard_tag;
1676                                 break;
1677                         }
1678                         gtk_text_buffer_insert_with_tags( buffer, &iter, buf, length, tag, NULL );
1679
1680                         gtk_text_view_scroll_mark_onscreen( GTK_TEXT_VIEW( g_qeglobals_gui.d_edit ), end );
1681
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 );
1685
1686                                 while ( gtk_events_pending() )
1687                                         gtk_main_iteration();
1688
1689                                 gtk_grab_remove( g_qeglobals_gui.d_edit );
1690                         }
1691                 }
1692         }
1693 }
1694
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 );
1699 }
1700
1701 extern "C" void Sys_Printf( const char *text, ... ) {
1702         va_list args;
1703
1704         va_start( args, text );
1705         Sys_FPrintf_VA( SYS_STD, text, args );
1706         va_end( args );
1707 }
1708
1709 extern "C" void Sys_FPrintf( int level, const char *text, ... ){
1710         va_list args;
1711
1712         va_start( args, text );
1713         Sys_FPrintf_VA( level, text, args );
1714         va_end( args );
1715 }