more eol-style
[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 #if defined (__linux__) || defined (__APPLE__)
33 #include <unistd.h>
34 #include <X11/keysym.h>
35 #include <gdk/gdkx.h>
36 #include <gdk/gdkprivate.h>
37 #endif
38 // for the logging part
39 #include <fcntl.h>
40 #include <sys/types.h>
41
42 QEGlobals_t  g_qeglobals;
43 QEGlobals_GUI_t g_qeglobals_gui;
44
45 // leo: Track memory allocations for debugging
46 // NOTE TTimo this was never used and probably not relevant
47 //   there are tools to do that
48 #ifdef MEM_DEBUG
49
50 static GList *memblocks;
51
52 void* debug_malloc (size_t size, const char* file, int line)
53 {
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 {
68   const char *f;
69   int l;
70
71   if (g_list_find (memblocks, buf))
72   {
73     memblocks = g_list_remove (memblocks, buf);
74
75     buf = (char*)buf - 4;
76     l = *((int*)buf);
77     buf = (char*)buf - 4;
78     f = *((const char**)buf);
79
80     Sys_FPrintf (SYS_DBG, "free: %s %d", file, line);
81     Sys_FPrintf (SYS_DBG, " allocated: %s %d\n", f, l);
82
83     g_free (buf);
84   }
85 //  else
86 //    free (buf); // from qmalloc, will leak unless we add this same hack to cmdlib
87 }
88
89 #endif
90
91 vec_t Rad_rint (vec_t in)
92 {
93   if (g_PrefsDlg.m_bNoClamp)
94     return in;
95   else
96     return (float)floor (in + 0.5);
97 }
98
99 void WINAPI QE_CheckOpenGLForErrors(void)
100 {
101   char strMsg[1024];
102   int i = qglGetError();
103   if (i != GL_NO_ERROR)
104   {
105     if (i == GL_OUT_OF_MEMORY)
106     {      
107       sprintf(strMsg, "OpenGL out of memory error %s\nDo you wish to save before exiting?", qgluErrorString((GLenum)i));
108       if (gtk_MessageBox(g_pParentWnd->m_pWidget, strMsg, "Radiant Error", MB_YESNO) == IDYES)
109       {
110         Map_SaveFile(NULL, false);
111       }
112       _exit(1);
113     }
114     else
115     {
116       Sys_Printf ("Warning: OpenGL Error %s\n", qgluErrorString((GLenum)i));
117     }
118   }
119 }
120
121 // NOTE: don't this function, use VFS instead
122 char *ExpandReletivePath (char *p)
123 {
124         static char     temp[1024];
125         const char      *base;
126
127         if (!p || !p[0])
128                 return NULL;
129         if (p[0] == '/' || p[0] == '\\')
130                 return p;
131
132         base = ValueForKey(g_qeglobals.d_project_entity, "basepath");
133         sprintf (temp, "%s/%s", base, p);
134         return temp;
135 }
136
137 char *copystring (char *s)
138 {
139         char    *b;
140         b = (char*)malloc(strlen(s)+1);
141         strcpy (b,s);
142         return b;
143 }
144
145
146 bool DoesFileExist(const char* pBuff, long& lSize)
147 {
148   FileStream file;
149   if (file.Open(pBuff, "r"))
150   {
151     lSize += file.GetLength();
152     file.Close();
153     return true;
154   }
155   return false;
156 }
157
158
159 void Map_Snapshot()
160 {
161   CString strMsg;
162
163   // I hope the modified flag is kept correctly up to date
164   if (!modified)
165     return;
166
167   // we need to do the following
168   // 1. make sure the snapshot directory exists (create it if it doesn't)
169   // 2. find out what the lastest save is based on number
170   // 3. inc that and save the map
171   CString strOrgPath, strOrgFile;
172   ExtractPath_and_Filename(currentmap, strOrgPath, strOrgFile);
173   AddSlash(strOrgPath);
174   strOrgPath += "snapshots";
175   bool bGo = true;
176   struct stat Stat;
177   if (stat(strOrgPath, &Stat) == -1)
178   {
179 #ifdef _WIN32
180     bGo = (_mkdir(strOrgPath) != -1);
181 #endif
182
183 #if defined (__linux__) || defined (__APPLE__)
184     bGo = (mkdir(strOrgPath,0755) != -1);
185 #endif
186   }
187   AddSlash(strOrgPath);
188   if (bGo)
189   {
190     int nCount = 0;
191     long lSize = 0;
192     CString strNewPath;
193     strNewPath = strOrgPath;
194     strNewPath += strOrgFile;
195     CString strFile;
196     while (bGo)
197     {
198       char buf[PATH_MAX];
199       sprintf( buf, "%s.%i", strNewPath.GetBuffer(), nCount );
200       strFile = buf;
201       bGo = DoesFileExist(strFile, lSize);
202       nCount++;
203     }
204     // strFile has the next available slot
205     Map_SaveFile(strFile, false);
206     // it is still a modified map (we enter this only if this is a modified map)
207     Sys_SetTitle (currentmap);
208     Sys_MarkMapModified();
209     if (lSize > 12 * 1024 * 1024) // total size of saves > 4 mb
210     {
211       Sys_Printf("The snapshot files in %s total more than 4 megabytes. You might consider cleaning up.", strOrgPath.GetBuffer());
212     }
213   }
214   else
215   {
216     strMsg.Format("Snapshot save failed.. unabled to create directory\n%s", strOrgPath.GetBuffer());
217     gtk_MessageBox(g_pParentWnd->m_pWidget, strMsg);
218   }
219   strOrgPath = "";
220   strOrgFile = "";
221 }
222 /*
223 ===============
224 QE_CheckAutoSave
225
226 If five minutes have passed since making a change
227 and the map hasn't been saved, save it out.
228 ===============
229 */
230
231
232 void QE_CheckAutoSave( void )
233 {
234   static time_t s_start;
235   time_t now;
236   time (&now);
237
238   if (modified != 1 || !s_start)
239   {
240     s_start = now;
241     return;
242   }
243
244   if ((now - s_start) > (60 * g_PrefsDlg.m_nAutoSave))
245   {
246     if (g_PrefsDlg.m_bAutoSave)
247     {
248       CString strMsg;
249       strMsg = g_PrefsDlg.m_bSnapShots ? "Autosaving snapshot..." : "Autosaving...";
250       Sys_Printf(strMsg);
251       Sys_Printf("\n");
252       Sys_Status (strMsg,0);
253
254       // only snapshot if not working on a default map
255       if (g_PrefsDlg.m_bSnapShots && stricmp(currentmap, "unnamed.map") != 0)
256       {
257         Map_Snapshot();
258       }
259       else
260       {
261         Map_SaveFile (ValueForKey(g_qeglobals.d_project_entity, "autosave"), false);
262       }
263
264       Sys_Status ("Autosaving...Saved.", 0 );
265       modified = 2;
266     }
267     else
268     {
269       Sys_Printf ("Autosave skipped...\n");
270       Sys_Status ("Autosave skipped...", 0 );
271     }
272     s_start = now;
273   }
274 }
275
276
277 // NOTE TTimo we don't like that BuildShortPathName too much
278 //   the VFS provides a vfsCleanFileName which should perform the cleanup tasks
279 //   in the long run I'd like to completely get rid of this
280
281 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=144
282 // used to be disabled, but caused problems
283
284 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=291
285 // can't work with long win32 names until the BSP commands are not working differently
286 #ifdef _WIN32
287 int BuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen)
288 {
289   char *pFile = NULL;
290   int nResult = GetFullPathName(pPath, nBufferLen, pBuffer, &pFile);
291   nResult = GetShortPathName(pPath, pBuffer, nBufferLen);
292   if (nResult == 0)
293     strcpy(pBuffer, pPath);                     // Use long filename
294   return nResult;
295 }
296 #endif
297
298 #if defined (__linux__) || defined (__APPLE__)
299 int BuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen)
300 {
301   // remove /../ from directories
302   const char *scr = pPath; char *dst = pBuffer;
303   for (int i = 0; (i < nBufferLen) && (*scr != 0); i++)
304   {
305     if (*scr == '/' && *(scr+1) == '.' && *(scr+2) == '.')
306     {
307       scr += 3;
308       while (dst != pBuffer && *(--dst) != '/')
309       {
310         i--;
311       }
312     }
313
314     *dst = *scr;
315
316     scr++; dst++;
317   }
318   *dst = 0;
319
320   return strlen (pBuffer);
321 }
322 #endif
323
324 /*
325 const char *g_pPathFixups[]=
326 {
327   "basepath",
328   "autosave",
329 };
330
331 const int g_nPathFixupCount = sizeof(g_pPathFixups) / sizeof(const char*);
332
333 void QE_CheckProjectEntity()
334 {
335   char *pFile;
336   char pBuff[PATH_MAX];
337   char pNewPath[PATH_MAX];
338   for (int i = 0; i < g_nPathFixupCount; i++)
339   {
340     char *pPath = ValueForKey (g_qeglobals.d_project_entity, g_pPathFixups[i]);
341
342     strcpy (pNewPath, pPath);
343     if (pPath[0] != '\\' && pPath[0] != '/')
344       if (GetFullPathName(pPath, PATH_MAX, pBuff, &pFile))
345               strcpy (pNewPath, pBuff);
346
347     BuildShortPathName (pNewPath, pBuff, PATH_MAX);
348
349     // check it's not ending with a filename seperator
350     if (pBuff[strlen(pBuff)-1] == '/' || pBuff[strlen(pBuff)-1] == '\\')
351     {
352       Sys_FPrintf(SYS_WRN, "WARNING: \"%s\" path in the project file has an ending file seperator, fixing.\n", g_pPathFixups[i]);
353       pBuff[strlen(pBuff)-1]=0;
354     }
355
356     SetKeyValue(g_qeglobals.d_project_entity, g_pPathFixups[i], pBuff);
357   }
358 }
359 */
360
361 void HandleXMLError( void* ctxt, const char* text, ... )
362 {
363   va_list argptr;
364   static char buf[32768];
365
366   va_start (argptr,text);
367   vsprintf (buf, text, argptr);
368   Sys_FPrintf (SYS_ERR, "XML %s\n", buf);
369   va_end (argptr);
370 }
371
372 #define DTD_BUFFER_LENGTH 1024
373 xmlDocPtr ParseXMLStream(IDataStream *stream, bool validate = false)
374 {
375   xmlDocPtr doc = NULL;
376   bool wellFormed = false, valid = false;
377   int res, size = 1024;
378   char chars[1024];
379   xmlParserCtxtPtr ctxt;
380
381   // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=433
382   //if(validate)
383   //  xmlDoValidityCheckingDefaultValue = 1;
384   //else
385     xmlDoValidityCheckingDefaultValue = 0;
386   xmlSetGenericErrorFunc(NULL, HandleXMLError);
387
388   // SPoG
389   // HACK: use AppPath to resolve DTD location
390   // do a buffer-safe string copy and concatenate
391   int i;
392   char* w;
393   const char* r;
394   char buf[DTD_BUFFER_LENGTH];
395
396   w = buf;
397   i = 0;
398   // copy
399   //assert(g_strAppPath.GetBuffer() != NULL);
400   for(r = g_strAppPath.GetBuffer(); i<DTD_BUFFER_LENGTH && *r != '\0'; i++, r++)  w[i] = *r;
401   // concatenate
402   for(r = "dtds/"; i<DTD_BUFFER_LENGTH && *r != '\0'; i++, r++)  w[i] = *r;
403   // terminate
404   w[i] = '\0';
405
406   if(i == DTD_BUFFER_LENGTH)
407   {
408     HandleXMLError(NULL, "ERROR: buffer overflow: DTD path length too large\n");
409     return NULL;
410   }
411
412   res = stream->Read(chars, 4);
413   if (res > 0)
414   {
415     ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, buf);
416
417     while ((res = stream->Read(chars, size)) > 0)
418     {
419       xmlParseChunk(ctxt, chars, res, 0);
420     }
421     xmlParseChunk(ctxt, chars, 0, 1);
422     doc = ctxt->myDoc;
423
424     wellFormed = (ctxt->wellFormed == 1);
425     valid = (ctxt->valid == 1);
426
427     xmlFreeParserCtxt(ctxt);
428   }
429
430   if(wellFormed && (!validate || (validate && valid)))
431     return doc;
432
433   if(doc != NULL)
434     xmlFreeDoc(doc);
435
436   return NULL;
437 }
438
439 xmlDocPtr ParseXMLFile(const char* filename, bool validate = false)
440 {
441   FileStream stream;
442   if (stream.Open(filename, "r"))
443     return ParseXMLStream(&stream, validate);
444   
445   Sys_FPrintf(SYS_ERR, "Failed to open file: %s\n",filename);
446   return NULL;
447 }
448
449 // copy a string r to a buffer w
450 // replace $string as appropriate
451 void ReplaceTemplates(char* w, const char* r)
452 {
453   const char *p;
454   const char *__ENGINEPATH = "TEMPLATEenginepath";
455   const char *__USERHOMEPATH = "TEMPLATEuserhomepath";
456   const char *__TOOLSPATH = "TEMPLATEtoolspath";
457   const char *__BASEDIR = "TEMPLATEbasedir";
458   const char *__APPPATH = "TEMPLATEapppath";
459
460   // iterate through string r
461   while(*r!='\0')
462   {
463     // check for special character
464     if(*r=='$')
465     {
466       if(strncmp(r+1, __ENGINEPATH, strlen(__ENGINEPATH)) == 0)
467       {
468         r+=strlen(__ENGINEPATH)+1;
469         p = g_pGameDescription->mEnginePath.GetBuffer();
470       }
471       else if(strncmp(r+1, __USERHOMEPATH, strlen(__USERHOMEPATH)) == 0)
472       {
473         r+=strlen(__USERHOMEPATH)+1;
474         p = g_qeglobals.m_strHomeGame.GetBuffer();
475       }
476       else if(strncmp(r+1, __BASEDIR, strlen(__BASEDIR)) == 0)
477       {
478         r+=strlen(__BASEDIR)+1;
479         p = g_pGameDescription->mBaseGame;
480       }
481       else if(strncmp(r+1, __TOOLSPATH, strlen(__TOOLSPATH)) == 0)
482       {
483         r+=strlen(__TOOLSPATH)+1;
484         p = g_strGameToolsPath.GetBuffer();
485       }
486       else if(strncmp(r+1, __APPPATH, strlen(__APPPATH)) == 0)
487       {
488         r+=strlen(__APPPATH)+1;
489         p = g_strAppPath.GetBuffer();
490       }
491       else
492       {
493         r++;
494         p = "$";
495       }
496           
497       while(*p!='\0') *w++ = *p++;
498     }
499     else *w++ = *r++;
500   }
501   *w = '\0';
502 }
503
504 /*
505 ===========
506 QE_LoadProject
507 TODO TODO TODO (don't think this got fully merged in)
508 TTimo: added project file "version", version 2 adds '#' chars to the BSP command strings
509 version 3 was .. I don't remember .. version 4 adds q3map2 commands
510 TTimo: when QE_LoadProject is called, the prefs are updated with path to the latest project and saved on disk
511 ===========
512 */
513 /*\todo decide on a sensible location/name for project files.*/
514 bool QE_LoadProject (const char *projectfile)
515 {
516   char buf[1024];
517   xmlDocPtr doc;
518   xmlNodePtr node, project;
519
520   Sys_Printf("Loading project file: \"%s\"\n", projectfile);
521   doc = ParseXMLFile(projectfile, true);
522
523   if(doc == NULL) return false;
524
525   node=doc->children;
526   while(node != NULL && node->type != XML_DTD_NODE) node=node->next;
527   if(node == NULL || strcmp((char*)node->name, "project") != 0)
528   {
529     Sys_FPrintf(SYS_ERR, "ERROR: invalid file type\n");
530     return false;
531   }
532
533   while(node->type != XML_ELEMENT_NODE) node=node->next;
534   // <project>
535   project = node;
536
537   if(g_qeglobals.d_project_entity != NULL) Entity_Free(g_qeglobals.d_project_entity);
538   g_qeglobals.d_project_entity = Entity_Alloc();
539
540   for(node = project->children; node != NULL; node=node->next)
541   {
542     if(node->type != XML_ELEMENT_NODE) continue;
543
544     // <key>
545     ReplaceTemplates(buf, (char*)node->properties->next->children->content);
546
547     SetKeyValue(g_qeglobals.d_project_entity, (char*)node->properties->children->content, buf);
548   }
549
550   xmlFreeDoc(doc);
551   
552   // project file version checking
553   // add a version checking to avoid people loading later versions of the project file and bitching
554   int ver = IntForKey( g_qeglobals.d_project_entity, "version" );
555   if (ver > PROJECT_VERSION)
556   {
557     char strMsg[1024];
558     sprintf (strMsg, "This is a version %d project file. This build only supports <=%d project files.\n"
559       "Please choose another project file or upgrade your version of Radiant.", ver, PROJECT_VERSION);
560     gtk_MessageBox (g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK);
561                 // set the project file to nothing so we are sure we'll ask next time?
562                 g_PrefsDlg.m_strLastProject = "";
563                 g_PrefsDlg.SavePrefs();
564     return false;
565   }
566   
567   // set here some default project settings you need
568   if ( strlen( ValueForKey( g_qeglobals.d_project_entity, "brush_primit" ) ) == 0 )
569   {
570     SetKeyValue( g_qeglobals.d_project_entity, "brush_primit", "0" );
571   }
572
573   g_qeglobals.m_bBrushPrimitMode = IntForKey( g_qeglobals.d_project_entity, "brush_primit" );
574
575   g_qeglobals.m_strHomeMaps = g_qeglobals.m_strHomeGame;
576   const char* str = ValueForKey(g_qeglobals.d_project_entity, "gamename");
577   if(str[0] == '\0') str = g_pGameDescription->mBaseGame.GetBuffer();
578   g_qeglobals.m_strHomeMaps += str;
579   g_qeglobals.m_strHomeMaps += '/';
580
581   // don't forget to create the dirs
582   Q_mkdir(g_qeglobals.m_strHomeGame.GetBuffer(), 0775);
583   Q_mkdir(g_qeglobals.m_strHomeMaps.GetBuffer(), 0775);
584   
585   // usefull for the log file and debuggin fucked up configurations from users:
586   // output the basic information of the .qe4 project file
587   // SPoG
588   // all these paths should be unix format, with a trailing slash at the end
589   // if not.. to debug, check that the project file paths are set up correctly
590   Sys_Printf("basepath    : %s\n", ValueForKey( g_qeglobals.d_project_entity, "basepath") );
591   Sys_Printf("entitypath  : %s\n", ValueForKey( g_qeglobals.d_project_entity, "entitypath" ) );
592
593
594   // check whether user_project key exists..
595   // if not, save the current project under a new name
596   if (ValueForKey(g_qeglobals.d_project_entity, "user_project")[0] == '\0')
597   {
598     Sys_Printf("Loaded a template project file\n");
599     
600     // create the user_project key
601     SetKeyValue( g_qeglobals.d_project_entity, "user_project", "1" );
602     
603     // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=672
604     if (IntForKey( g_qeglobals.d_project_entity, "version" ) != PROJECT_VERSION)
605     {
606       char strMsg[2048];
607       sprintf(strMsg, 
608         "The template project '%s' has version %d. The editor binary is configured for version %d.\n"
609         "This indicates a problem in your setup. See http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=672\n"
610         "I will keep going with this project till you fix this",
611         projectfile, IntForKey( g_qeglobals.d_project_entity, "version" ), PROJECT_VERSION);
612       gtk_MessageBox (g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK);
613     }
614     
615     // create the writable project file path
616     strcpy(buf, g_qeglobals.m_strHomeGame.GetBuffer());
617     strcat(buf, g_pGameDescription->mBaseGame.GetBuffer());
618     strcat(buf, "/scripts/");
619     // while the filename is already in use, increment the number we add to the end
620     int counter = 0;
621     char pUser[PATH_MAX];
622     while (1)
623     {      
624       sprintf( pUser, "%suser%d." PROJECT_FILETYPE, buf, counter );
625       counter++;
626       if (access( pUser, R_OK) != 0)
627       {
628         // this is the one
629         strcpy( buf, pUser );
630         break;
631       }
632     }
633     // saving project will cause a save prefs
634     g_PrefsDlg.m_strLastProject = buf;
635     g_PrefsDlg.m_nLastProjectVer = IntForKey( g_qeglobals.d_project_entity, "version" );    
636     QE_SaveProject(buf);
637   }
638   else
639   {
640     // update preferences::LastProject with path of this successfully-loaded project
641     // save preferences
642     Sys_Printf("Setting current project in prefs to \"%s\"\n", g_PrefsDlg.m_strLastProject.GetBuffer() );
643     g_PrefsDlg.m_strLastProject = projectfile;
644     g_PrefsDlg.SavePrefs();
645   }
646
647   return true;
648 }
649
650 /*
651 ===========
652 QE_SaveProject
653 TTimo: whenever QE_SaveProject is called, prefs are updated and saved with the path to the project
654 ===========
655 */
656 qboolean QE_SaveProject (const char* filename)
657 {
658   Sys_Printf("Save project file '%s'\n", filename);
659
660   xmlNodePtr node;
661   xmlDocPtr doc = xmlNewDoc((xmlChar *)"1.0");
662   // create DTD node
663   xmlCreateIntSubset(doc, (xmlChar *)"project", NULL, (xmlChar *)"project.dtd");
664   // create project node
665   doc->children->next = xmlNewDocNode(doc, NULL, (xmlChar *)"project", NULL);
666
667   for(epair_t* epair = g_qeglobals.d_project_entity->epairs; epair != NULL; epair = epair->next)
668   {
669     node = xmlNewChild(doc->children->next, NULL, (xmlChar *)"key", NULL);
670     xmlSetProp(node, (xmlChar*)"name", (xmlChar*)epair->key);
671     xmlSetProp(node, (xmlChar*)"value", (xmlChar*)epair->value);
672   }
673
674   CreateDirectoryPath(filename);
675   if (xmlSaveFormatFile(filename, doc, 1) != -1)
676   {
677     xmlFreeDoc(doc);
678     Sys_Printf("Setting current project in prefs to \"%s\"\n", filename );
679     g_PrefsDlg.m_strLastProject = filename;
680     g_PrefsDlg.SavePrefs();
681     return TRUE;
682   }
683   else
684   {
685     xmlFreeDoc(doc);
686     Sys_FPrintf(SYS_ERR, "failed to save project file: \"%s\"\n", filename);
687     return FALSE;
688   }
689 }
690
691
692
693 /*
694 ===========
695 QE_KeyDown
696 ===========
697 */
698 #define SPEED_MOVE      32
699 #define SPEED_TURN      22.5
700
701
702 /*
703 ===============
704 ConnectEntities
705
706 Sets target / targetname on the two entities selected
707 from the first selected to the secon
708 ===============
709 */
710 void ConnectEntities (void)
711 {
712         entity_t        *e1, *e2;
713         const char              *target;
714         char            *newtarg = NULL;
715
716         if (g_qeglobals.d_select_count != 2)
717         {
718                 Sys_Status ("Must have two brushes selected", 0);
719                 Sys_Beep ();
720                 return;
721         }
722
723         e1 = g_qeglobals.d_select_order[0]->owner;
724         e2 = g_qeglobals.d_select_order[1]->owner;
725
726         if (e1 == world_entity || e2 == world_entity)
727         {
728                 Sys_Status ("Can't connect to the world", 0);
729                 Sys_Beep ();
730                 return;
731         }
732
733         if (e1 == e2)
734         {
735                 Sys_Status ("Brushes are from same entity", 0);
736                 Sys_Beep ();
737                 return;
738         }
739     
740   target = ValueForKey (e1, "target");
741   if (target && target[0])
742     newtarg = g_strdup(target);
743   else
744   {
745     target = ValueForKey(e2, "targetname");
746     if(target && target[0])
747       newtarg = g_strdup(target);
748     else
749       Entity_Connect(e1, e2);
750   }
751
752   if(newtarg != NULL)
753   {
754     SetKeyValue(e1, "target", newtarg);
755     SetKeyValue(e2, "targetname", newtarg);
756     g_free(newtarg);
757   }
758   
759         Sys_UpdateWindows (W_XY | W_CAMERA);
760
761         Select_Deselect();
762         Select_Brush (g_qeglobals.d_select_order[1]);
763 }
764
765 qboolean QE_SingleBrush (bool bQuiet)
766 {
767         if ( (selected_brushes.next == &selected_brushes)
768                 || (selected_brushes.next->next != &selected_brushes) )
769         {
770           if (!bQuiet)
771     {
772                 Sys_Printf ("Error: you must have a single brush selected\n");
773           }
774                 return false;
775         }
776         if (selected_brushes.next->owner->eclass->fixedsize)
777         {
778           if (!bQuiet)
779           {
780                   Sys_Printf ("Error: you cannot manipulate fixed size entities\n");
781           }
782                 return false;
783         }
784
785         return true;
786 }
787
788 void QE_InitVFS (void)
789 {
790   // VFS initialization -----------------------
791   // we will call vfsInitDirectory, giving the directories to look in (for files in pk3's and for standalone files)
792   // we need to call in order, the mod ones first, then the base ones .. they will be searched in this order
793   // *nix systems have a dual filesystem in ~/.q3a, which is searched first .. so we need to add that too
794   Str directory,prefabs;
795
796   // TTimo: let's leave this to HL mode for now
797   if (g_pGameDescription->mGameFile == "hl.game")
798   {
799     // Hydra: we search the "gametools" path first so that we can provide editor
800     // specific pk3's wads and misc files for use by the editor.
801     // the relevant map compiler tools will NOT use this directory, so this helps
802     // to ensure that editor files are not used/required in release versions of maps
803     // it also helps keep your editor files all in once place, with the editor modules,
804     // plugins, scripts and config files.
805     // it also helps when testing maps, as you'll know your files won't/can't be used
806     // by the game engine itself.
807   
808     // <gametools>
809     directory = g_pGameDescription->mGameToolsPath;
810     vfsInitDirectory(directory.GetBuffer());
811   }
812
813   // NOTE TTimo about the mymkdir calls .. this is a bit dirty, but a safe thing on *nix
814
815   // if we have a mod dir
816   if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
817   {
818
819 #if defined (__linux__) || defined (__APPLE__)
820     // ~/.<gameprefix>/<fs_game>
821     directory = g_qeglobals.m_strHomeGame.GetBuffer();
822     Q_mkdir (directory.GetBuffer (), 0775);
823     directory += ValueForKey(g_qeglobals.d_project_entity, "gamename");
824     Q_mkdir (directory.GetBuffer (), 0775);
825     vfsInitDirectory(directory.GetBuffer());
826     AddSlash (directory);
827     prefabs = directory;
828     // also create the maps dir, it will be used as prompt for load/save
829     directory += "/maps";
830     Q_mkdir (directory, 0775);
831     // and the prefabs dir
832     prefabs += "/prefabs";
833     Q_mkdir (prefabs, 0775);
834
835 #endif
836
837     // <fs_basepath>/<fs_game>
838     directory = g_pGameDescription->mEnginePath;
839     directory += ValueForKey(g_qeglobals.d_project_entity, "gamename");
840     Q_mkdir (directory.GetBuffer (), 0775);
841     vfsInitDirectory(directory.GetBuffer());
842     AddSlash(directory);
843     prefabs = directory;
844     // also create the maps dir, it will be used as prompt for load/save
845     directory += "/maps";
846     Q_mkdir (directory.GetBuffer (), 0775);
847     // and the prefabs dir
848     prefabs += "/prefabs";
849     Q_mkdir (prefabs, 0775);
850   }
851
852 #if defined (__linux__) || defined (__APPLE__)
853   // ~/.<gameprefix>/<fs_main>
854   directory = g_qeglobals.m_strHomeGame.GetBuffer();
855   directory += g_pGameDescription->mBaseGame;
856   vfsInitDirectory (directory.GetBuffer ());
857 #endif
858
859   // <fs_basepath>/<fs_main>
860   directory = g_pGameDescription->mEnginePath;
861   directory += g_pGameDescription->mBaseGame;
862   vfsInitDirectory(directory.GetBuffer());
863 }
864
865 void QE_Init (void)
866 {
867         /*
868         ** initialize variables
869         */
870         g_qeglobals.d_gridsize = 8;
871         g_qeglobals.d_showgrid = true;
872
873   QE_InitVFS();
874
875   Eclass_Init();
876   FillClassList();              // list in entity window
877   Map_Init();
878
879   FillTextureMenu();
880   FillBSPMenu();
881
882         /*
883         ** other stuff
884         */
885         Z_Init ();
886 }
887
888 void WINAPI QE_ConvertDOSToUnixName( char *dst, const char *src )
889 {
890         while ( *src )
891         {
892                 if ( *src == '\\' )
893                         *dst = '/';
894                 else
895                         *dst = *src;
896                 dst++; src++;
897         }
898         *dst = 0;
899 }
900
901 int g_numbrushes, g_numentities;
902
903 void QE_CountBrushesAndUpdateStatusBar( void )
904 {
905         static int      s_lastbrushcount, s_lastentitycount;
906         static qboolean s_didonce;
907         
908         //entity_t   *e;
909         brush_t    *b, *next;
910
911         g_numbrushes = 0;
912         g_numentities = 0;
913         
914         if ( active_brushes.next != NULL )
915         {
916                 for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; b=next)
917                 {
918                         next = b->next;
919                         if (b->brush_faces )
920                         {
921                                 if ( !b->owner->eclass->fixedsize)
922                                         g_numbrushes++;
923                                 else
924                                         g_numentities++;
925                         }
926                 }
927         }
928 /*
929         if ( entities.next != NULL )
930         {
931                 for ( e = entities.next ; e != &entities && g_numentities != MAX_MAP_ENTITIES ; e = e->next)
932                 {
933                         g_numentities++;
934                 }
935         }
936 */
937         if ( ( ( g_numbrushes != s_lastbrushcount ) || ( g_numentities != s_lastentitycount ) ) || ( !s_didonce ) )
938         {
939                 Sys_UpdateStatusBar();
940
941                 s_lastbrushcount = g_numbrushes;
942                 s_lastentitycount = g_numentities;
943                 s_didonce = true;
944         }
945 }
946
947 char            com_token[1024];
948 qboolean        com_eof;
949
950 /*
951 ================
952 I_FloatTime
953 ================
954 */
955 double I_FloatTime (void)
956 {
957         time_t  t;
958         
959         time (&t);
960         
961         return t;
962 #if 0
963 // more precise, less portable
964         struct timeval tp;
965         struct timezone tzp;
966         static int              secbase;
967
968         gettimeofday(&tp, &tzp);
969         
970         if (!secbase)
971         {
972                 secbase = tp.tv_sec;
973                 return tp.tv_usec/1000000.0;
974         }
975         
976         return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0;
977 #endif
978 }
979
980
981 /*
982 ==============
983 COM_Parse
984
985 Parse a token out of a string
986 ==============
987 */
988 char *COM_Parse (char *data)
989 {
990         int             c;
991         int             len;
992
993         len = 0;
994         com_token[0] = 0;
995         
996         if (!data)
997                 return NULL;
998                 
999 // skip whitespace
1000 skipwhite:
1001         while ( (c = *data) <= ' ')
1002         {
1003                 if (c == 0)
1004                 {
1005                         com_eof = true;
1006                         return NULL;                    // end of file;
1007                 }
1008                 data++;
1009         }
1010         
1011 // skip // comments
1012         if (c=='/' && data[1] == '/')
1013         {
1014                 while (*data && *data != '\n')
1015                         data++;
1016                 goto skipwhite;
1017         }
1018         
1019
1020 // handle quoted strings specially
1021         if (c == '\"')
1022         {
1023                 data++;
1024                 do
1025                 {
1026                         c = *data++;
1027                         if (c=='\"')
1028                         {
1029                                 com_token[len] = 0;
1030                                 return data;
1031                         }
1032                         com_token[len] = c;
1033                         len++;
1034                 } while (1);
1035         }
1036
1037 // parse single characters
1038         if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
1039         {
1040                 com_token[len] = c;
1041                 len++;
1042                 com_token[len] = 0;
1043                 return data+1;
1044         }
1045
1046 // parse a regular word
1047         do
1048         {
1049                 com_token[len] = c;
1050                 data++;
1051                 len++;
1052                 c = *data;
1053         if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
1054                         break;
1055         } while (c>32);
1056         
1057         com_token[len] = 0;
1058         return data;
1059 }
1060
1061 char* Get_COM_Token()
1062 {
1063   return com_token;
1064 }
1065
1066 /*
1067 =============================================================================
1068
1069                                                 MISC FUNCTIONS
1070
1071 =============================================================================
1072 */
1073
1074
1075 int             argc;
1076 char    *argv[MAX_NUM_ARGVS];
1077
1078 /*
1079 ============
1080 ParseCommandLine
1081 ============
1082 */
1083 void ParseCommandLine (char *lpCmdLine)
1084 {
1085         argc = 1;
1086         argv[0] = "programname";
1087
1088         while (*lpCmdLine && (argc < MAX_NUM_ARGVS))
1089         {
1090                 while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))
1091                         lpCmdLine++;
1092
1093                 if (*lpCmdLine)
1094                 {
1095                         argv[argc] = lpCmdLine;
1096                         argc++;
1097
1098                         while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))
1099                                 lpCmdLine++;
1100
1101                         if (*lpCmdLine)
1102                         {
1103                                 *lpCmdLine = 0;
1104                                 lpCmdLine++;
1105                         }
1106                         
1107                 }
1108         }
1109 }
1110
1111
1112
1113 /*
1114 =================
1115 CheckParm
1116
1117 Checks for the given parameter in the program's command line arguments
1118 Returns the argument number (1 to argc-1) or 0 if not present
1119 =================
1120 */
1121 int CheckParm (char *check)
1122 {
1123         int             i;
1124
1125         for (i = 1;i<argc;i++)
1126         {
1127                 if ( stricmp(check, argv[i]) )
1128                         return i;
1129         }
1130
1131         return 0;
1132 }
1133
1134
1135
1136
1137 /*
1138 ==============
1139 ParseNum / ParseHex
1140 ==============
1141 */
1142 int ParseHex (char *hex)
1143 {
1144         char    *str;
1145         int    num;
1146
1147         num = 0;
1148         str = hex;
1149
1150         while (*str)
1151         {
1152                 num <<= 4;
1153                 if (*str >= '0' && *str <= '9')
1154                         num += *str-'0';
1155                 else if (*str >= 'a' && *str <= 'f')
1156                         num += 10 + *str-'a';
1157                 else if (*str >= 'A' && *str <= 'F')
1158                         num += 10 + *str-'A';
1159                 else
1160                         Error ("Bad hex number: %s",hex);
1161                 str++;
1162         }
1163
1164         return num;
1165 }
1166
1167
1168 int ParseNum (char *str)
1169 {
1170         if (str[0] == '$')
1171                 return ParseHex (str+1);
1172         if (str[0] == '0' && str[1] == 'x')
1173                 return ParseHex (str+2);
1174         return atol (str);
1175 }
1176
1177 // BSP frontend plugin
1178 // global flag for BSP frontend plugin is g_qeglobals.bBSPFrontendPlugin
1179 _QERPlugBSPFrontendTable g_BSPFrontendTable;
1180
1181 // =============================================================================
1182 // Sys_ functions
1183
1184 bool Sys_AltDown ()
1185 {
1186 #ifdef _WIN32
1187   return (GetKeyState(VK_MENU) & 0x8000) != 0;
1188 #endif
1189
1190 #if defined (__linux__) || defined (__APPLE__)
1191   char keys[32];
1192   int x;
1193
1194   XQueryKeymap(GDK_DISPLAY(), keys);
1195
1196   x = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L);
1197   if (keys[x/8] & (1 << (x % 8)))
1198     return true;
1199
1200   x = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_R);
1201   if (keys[x/8] & (1 << (x % 8)))
1202     return true;
1203
1204   return false;
1205 #endif
1206 }
1207
1208 bool Sys_ShiftDown ()
1209 {
1210 #ifdef _WIN32
1211   return (GetKeyState(VK_SHIFT) & 0x8000) != 0;
1212 #endif
1213
1214 #if defined (__linux__) || defined (__APPLE__)
1215   char keys[32];
1216   int x;
1217
1218   XQueryKeymap(GDK_DISPLAY(), keys);
1219
1220   x = XKeysymToKeycode (GDK_DISPLAY(), XK_Shift_L);
1221   if (keys[x/8] & (1 << (x % 8)))
1222     return true;
1223
1224   x = XKeysymToKeycode (GDK_DISPLAY(), XK_Shift_R);
1225   if (keys[x/8] & (1 << (x % 8)))
1226     return true;
1227
1228   return false;
1229 #endif
1230 }
1231
1232 void Sys_MarkMapModified (void)
1233 {
1234   char title[PATH_MAX];
1235
1236   if (modified != 1)
1237   {
1238     modified = true;    // mark the map as changed
1239     sprintf (title, "%s *", currentmap);
1240
1241     QE_ConvertDOSToUnixName( title, title );
1242     Sys_SetTitle (title);
1243   }
1244 }
1245
1246 void Sys_SetTitle (const char *text)
1247 {
1248   gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_main_window), text);
1249 }
1250
1251 bool g_bWaitCursor = false;
1252
1253 void WINAPI Sys_BeginWait (void)
1254 {
1255   GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
1256   gdk_window_set_cursor (g_pParentWnd->m_pWidget->window, cursor);
1257   gdk_cursor_unref (cursor);
1258   g_bWaitCursor = true;
1259 }
1260
1261 void WINAPI Sys_EndWait (void)
1262 {
1263   GdkCursor *cursor = gdk_cursor_new (GDK_LEFT_PTR);
1264   gdk_window_set_cursor (g_pParentWnd->m_pWidget->window, cursor);
1265   gdk_cursor_unref (cursor);
1266   g_bWaitCursor = false;
1267 }
1268
1269 void Sys_GetCursorPos (int *x, int *y)
1270 {
1271   // FIXME: not multihead safe
1272   gdk_window_get_pointer (NULL, x, y, NULL);
1273 }
1274
1275 void Sys_SetCursorPos (int x, int y)
1276 {
1277   // NOTE: coordinates are in GDK space, not OS space
1278 #ifdef _WIN32
1279   int sys_x = x - g_pParentWnd->GetGDKOffsetX();
1280   int sys_y = y - g_pParentWnd->GetGDKOffsetY();
1281
1282   SetCursorPos (sys_x, sys_y);
1283 #endif
1284
1285 #if defined (__linux__) || defined (__APPLE__)
1286   XWarpPointer (GDK_DISPLAY(), None, GDK_ROOT_WINDOW(), 0, 0, 0, 0, x, y);
1287 #endif
1288 }
1289
1290 void Sys_Beep (void)
1291 {
1292 #if defined (__linux__) || defined (__APPLE__)
1293   gdk_beep ();
1294 #else
1295         MessageBeep (MB_ICONASTERISK);  
1296 #endif
1297 }
1298
1299 double Sys_DoubleTime (void)
1300 {
1301   return clock()/ 1000.0;
1302 }
1303
1304 /*
1305 ===============================================================
1306
1307   STATUS WINDOW
1308
1309 ===============================================================
1310 */
1311
1312 void Sys_UpdateStatusBar( void )
1313 {
1314   extern int   g_numbrushes, g_numentities;
1315
1316   char numbrushbuffer[100]="";
1317
1318   sprintf( numbrushbuffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities );
1319   g_pParentWnd->SetStatusText(2, numbrushbuffer);
1320   //Sys_Status( numbrushbuffer, 2 );
1321 }
1322
1323 void Sys_Status(const char *psz, int part )
1324 {
1325   g_pParentWnd->SetStatusText (part, psz);
1326 }
1327
1328 // =============================================================================
1329 // MRU
1330
1331 #define MRU_MAX 4
1332 static GtkWidget *MRU_items[MRU_MAX];
1333 static int MRU_used;
1334 typedef char MRU_filename_t[PATH_MAX];
1335 MRU_filename_t MRU_filenames[MRU_MAX];
1336
1337 static char* MRU_GetText (int index)
1338 {
1339   return MRU_filenames[index];
1340 }
1341
1342 void buffer_write_escaped_mnemonic(char* buffer, const char* string)
1343 {
1344   while(*string != '\0')
1345   {
1346     if(*string == '_')
1347     {
1348       *buffer++ = '_';
1349     }
1350
1351     *buffer++ = *string++;
1352   }
1353   *buffer = '\0';
1354 }
1355
1356 static void MRU_SetText (int index, const char *filename)
1357 {
1358   strcpy(MRU_filenames[index], filename);
1359
1360   char mnemonic[PATH_MAX * 2 + 4];
1361   mnemonic[0] = '_';
1362   sprintf(mnemonic+1, "%d", index+1);
1363   mnemonic[2] = '-';
1364   mnemonic[3] = ' ';
1365   buffer_write_escaped_mnemonic(mnemonic+4, filename);
1366   gtk_label_set_text_with_mnemonic(GTK_LABEL (GTK_BIN (MRU_items[index])->child), mnemonic);
1367 }
1368
1369 void MRU_Load ()
1370 {
1371   int i = g_PrefsDlg.m_nMRUCount;
1372
1373   if(i > 4)
1374     i = 4; //FIXME: make this a define
1375
1376   for (; i > 0; i--)
1377     MRU_AddFile (g_PrefsDlg.m_strMRUFiles[i-1].GetBuffer());
1378 }
1379
1380 void MRU_Save ()
1381 {
1382   g_PrefsDlg.m_nMRUCount = MRU_used;
1383
1384   for (int i = 0; i < MRU_used; i++)
1385     g_PrefsDlg.m_strMRUFiles[i] = MRU_GetText (i);
1386 }
1387
1388 void MRU_AddWidget (GtkWidget *widget, int pos)
1389 {
1390   if (pos < MRU_MAX)
1391     MRU_items[pos] = widget;
1392 }
1393
1394 void MRU_AddFile (const char *str)
1395 {
1396   int i;
1397   char* text;
1398
1399   // check if file is already in our list
1400   for (i = 0; i < MRU_used; i++)
1401   {
1402     text = MRU_GetText (i);
1403
1404     if (strcmp (text, str) == 0)
1405     {
1406       // reorder menu
1407       for (; i > 0; i--)
1408         MRU_SetText (i, MRU_GetText (i-1));
1409
1410       MRU_SetText (0, str);
1411
1412       return;
1413     }
1414   }
1415
1416   if (MRU_used < MRU_MAX)
1417     MRU_used++;
1418
1419   // move items down
1420   for (i = MRU_used-1; i > 0; i--)
1421     MRU_SetText (i, MRU_GetText (i-1));
1422
1423   MRU_SetText (0, str);
1424   gtk_widget_set_sensitive (MRU_items[0], TRUE);
1425   gtk_widget_show (MRU_items[MRU_used-1]);
1426 }
1427
1428 void MRU_Activate (int index)
1429 {
1430   char *text = MRU_GetText (index);
1431
1432   if (access (text, R_OK) == 0)
1433   {
1434     text = strdup (text);
1435     MRU_AddFile (text);
1436     Map_LoadFile (text);
1437     free (text);
1438   }
1439   else
1440   {
1441     MRU_used--;
1442
1443     for (int i = index; i < MRU_used; i++)
1444       MRU_SetText (i, MRU_GetText (i+1));
1445
1446     if (MRU_used == 0)
1447     {
1448       gtk_label_set_text (GTK_LABEL (GTK_BIN (MRU_items[0])->child), "Recent Files");
1449       gtk_widget_set_sensitive (MRU_items[0], FALSE);
1450     }
1451     else
1452     {
1453       gtk_widget_hide (MRU_items[MRU_used]);
1454     }
1455   }
1456 }
1457
1458 /*
1459 ======================================================================
1460
1461 FILE DIALOGS
1462
1463 ======================================================================
1464 */
1465  
1466 qboolean ConfirmModified ()
1467 {
1468   if (!modified)
1469     return true;
1470
1471   if (gtk_MessageBox (g_pParentWnd->m_pWidget, "This will lose changes to the map", "warning", MB_OKCANCEL) == IDCANCEL)
1472     return false;
1473   return true;
1474 }
1475
1476 void ProjectDialog ()
1477 {
1478   const char *filename;
1479   char buffer[NAME_MAX];
1480
1481   /* 
1482    * Obtain the system directory name and 
1483    * store it in buffer. 
1484    */ 
1485
1486   strcpy(buffer, g_qeglobals.m_strHomeGame.GetBuffer());
1487   strcat(buffer, g_pGameDescription->mBaseGame.GetBuffer());
1488   strcat (buffer, "/scripts/");
1489
1490   // Display the Open dialog box
1491   filename = file_dialog (NULL, TRUE, "Open File", buffer, "project");
1492
1493   if (filename == NULL)
1494     return;     // canceled
1495
1496   // Open the file.
1497   // NOTE: QE_LoadProject takes care of saving prefs with new path to the project file
1498   if (!QE_LoadProject(filename))
1499     Sys_Printf ("Failed to load project from file: %s\n", filename);
1500   else
1501     // FIXME TTimo QE_Init is probably broken if you don't call it during startup right now .. 
1502     QE_Init();
1503 }
1504
1505 /*
1506 =======================================================
1507
1508 Menu modifications
1509
1510 =======================================================
1511 */
1512
1513 /*
1514 ==================
1515 FillBSPMenu
1516
1517 ==================
1518 */
1519 char *bsp_commands[256];
1520
1521 void FillBSPMenu ()
1522 {
1523   GtkWidget *item, *menu; // menu points to a GtkMenu (not an item)
1524   epair_t *ep;
1525   GList *lst;
1526   int i;
1527
1528   menu = GTK_WIDGET (g_object_get_data (G_OBJECT (g_qeglobals_gui.d_main_window), "menu_bsp"));
1529
1530   while ((lst = gtk_container_children (GTK_CONTAINER (menu))) != NULL)
1531     gtk_container_remove (GTK_CONTAINER (menu), GTK_WIDGET (lst->data));
1532
1533   if (g_PrefsDlg.m_bDetachableMenus) {
1534     item = gtk_tearoff_menu_item_new ();
1535     gtk_menu_append (GTK_MENU (menu), item);
1536     gtk_widget_set_sensitive (item, TRUE);
1537     gtk_widget_show (item);
1538   }
1539
1540   if (g_qeglobals.bBSPFrontendPlugin)
1541   {
1542     CString str = g_BSPFrontendTable.m_pfnGetBSPMenu();
1543     char cTemp[1024];
1544     strcpy(cTemp, str);
1545     char* token = strtok(cTemp, ",;");
1546     if (token && *token == ' ')
1547     {
1548       while (*token == ' ')
1549         token++;
1550     }
1551     i = 0;
1552
1553     // first token is menu name
1554     item = gtk_menu_get_attach_widget (GTK_MENU (menu));
1555     gtk_label_set_text (GTK_LABEL (GTK_BIN (item)->child), token);
1556
1557     token = strtok(NULL, ",;");
1558     while (token != NULL)
1559     {
1560       g_BSPFrontendCommands = g_slist_append (g_BSPFrontendCommands, g_strdup (token));
1561       item = gtk_menu_item_new_with_label (token);
1562       gtk_widget_show (item);
1563       gtk_container_add (GTK_CONTAINER (menu), item);
1564       gtk_signal_connect (GTK_OBJECT (item), "activate",
1565                           GTK_SIGNAL_FUNC (HandleCommand), GINT_TO_POINTER (CMD_BSPCOMMAND+i));
1566       token = strtok(NULL, ",;");
1567       i++;
1568     }
1569   }
1570   else
1571   {
1572     i = 0;
1573     for (ep = g_qeglobals.d_project_entity->epairs; ep; ep = ep->next)
1574     {
1575       if (strncmp(ep->key, "bsp_", 4)==0)
1576       {
1577         bsp_commands[i] = ep->key;      
1578         item = gtk_menu_item_new_with_label (ep->key+4);
1579         gtk_widget_show (item);
1580         gtk_container_add (GTK_CONTAINER (menu), item);
1581         gtk_signal_connect (GTK_OBJECT (item), "activate",
1582           GTK_SIGNAL_FUNC (HandleCommand), GINT_TO_POINTER (CMD_BSPCOMMAND+i));
1583         i++;
1584       }
1585     }
1586   }
1587 }
1588
1589 //==============================================
1590
1591 void AddSlash(CString& strPath)
1592 {
1593   if (strPath.GetLength() > 0)
1594   {
1595     if ((strPath.GetAt(strPath.GetLength()-1) != '/') &&
1596         (strPath.GetAt(strPath.GetLength()-1) != '\\'))
1597       strPath += '/';
1598   }
1599 }
1600
1601 bool ExtractPath_and_Filename(const char* pPath, CString& strPath, CString& strFilename)
1602 {
1603   CString strPathName;
1604   strPathName = pPath;
1605   int nSlash = strPathName.ReverseFind('\\');
1606   if (nSlash == -1)
1607     // TTimo: try forward slash, some are using forward
1608     nSlash = strPathName.ReverseFind('/');
1609   if (nSlash >= 0)
1610   {
1611     strPath = strPathName.Left(nSlash+1);
1612     strFilename = strPathName.Right(strPathName.GetLength() - nSlash - 1);
1613   }
1614   // TTimo: try forward slash, some are using forward
1615   else
1616     strFilename = pPath;
1617   return true;
1618 }
1619
1620 //===========================================
1621
1622 //++timo FIXME: no longer used .. remove!
1623 char *TranslateString (char *buf)
1624 {
1625   static char   buf2[32768];
1626   int           i, l;
1627   char          *out;
1628
1629   l = strlen(buf);
1630   out = buf2;
1631   for (i=0 ; i<l ; i++)
1632   {
1633     if (buf[i] == '\n')
1634     {
1635       *out++ = '\r';
1636       *out++ = '\n';
1637     }
1638     else
1639       *out++ = buf[i];
1640   }
1641   *out++ = 0;
1642
1643   return buf2;
1644 }
1645
1646 // called whenever we need to open/close/check the console log file
1647 void Sys_LogFile (void)
1648 {
1649   if (g_PrefsDlg.mGamesDialog.m_bLogConsole && !g_qeglobals.hLogFile)
1650   {
1651         // settings say we should be logging and we don't have a log file .. so create it
1652     // open a file to log the console (if user prefs say so)
1653     // the file handle is g_qeglobals.hLogFile
1654     // the log file is erased
1655     Str name;
1656     name = g_strTempPath;    
1657     name += "radiant.log";
1658 #if defined (__linux__) || defined (__APPLE__)
1659     g_qeglobals.hLogFile = open( name.GetBuffer(), O_TRUNC | O_CREAT | O_WRONLY, S_IREAD | S_IWRITE );
1660 #endif
1661 #ifdef _WIN32
1662     g_qeglobals.hLogFile = _open( name.GetBuffer(), _O_TRUNC | _O_CREAT | _O_WRONLY, _S_IREAD | _S_IWRITE );
1663 #endif
1664     if (g_qeglobals.hLogFile)
1665     {
1666       Sys_Printf("Started logging to %s\n", name.GetBuffer());
1667       time_t localtime;
1668       time(&localtime);
1669       Sys_Printf("Today is: %s", ctime(&localtime));
1670       Sys_Printf("This is GtkRadiant '" RADIANT_VERSION "' compiled " __DATE__ "\n");
1671       Sys_Printf(RADIANT_ABOUTMSG "\n");
1672     }
1673     else
1674       gtk_MessageBox (NULL, "Failed to create log file, check write permissions in Radiant directory.\n",
1675                       "Console logging", MB_OK );
1676   }
1677   else if (!g_PrefsDlg.mGamesDialog.m_bLogConsole && g_qeglobals.hLogFile)
1678   {
1679         // settings say we should not be logging but still we have an active logfile .. close it
1680     time_t localtime;
1681     time(&localtime);
1682     Sys_Printf("Closing log file at %s\n", ctime(&localtime));
1683                 #ifdef _WIN32
1684     _close( g_qeglobals.hLogFile );
1685                 #endif
1686                 #if defined (__linux__) || defined (__APPLE__)
1687                 close( g_qeglobals.hLogFile );
1688                 #endif
1689     g_qeglobals.hLogFile = 0;
1690   }
1691 }
1692
1693 void Sys_ClearPrintf (void)
1694 {
1695   GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit));
1696   gtk_text_buffer_set_text(buffer, "", -1);
1697 }
1698
1699 // used to be around 32000, that should be way enough already
1700 #define BUFFER_SIZE 4096
1701
1702 extern "C" void Sys_FPrintf_VA (int level, const char *text, va_list args)
1703 {
1704   char buf[BUFFER_SIZE];
1705   
1706   buf[0] = 0;
1707   vsnprintf(buf, BUFFER_SIZE, text, args);
1708   buf[BUFFER_SIZE-1] = 0;
1709   const unsigned int length = strlen(buf);
1710
1711   if (g_qeglobals.hLogFile)
1712   {
1713 #ifdef _WIN32
1714     _write(g_qeglobals.hLogFile, buf,length);
1715     _commit(g_qeglobals.hLogFile);
1716 #endif
1717 #if defined (__linux__) || defined (__APPLE__)
1718     write(g_qeglobals.hLogFile, buf, length);
1719 #endif
1720   }
1721
1722   if (level != SYS_NOCON)
1723   {
1724     // TTimo: FIXME: killed the console to avoid GDI leak fuckup
1725     if (g_qeglobals_gui.d_edit != NULL)
1726     {
1727       GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit));
1728       
1729       GtkTextIter iter;
1730       gtk_text_buffer_get_end_iter(buffer, &iter);
1731
1732       static GtkTextMark* end = gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE);
1733
1734       const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 };
1735       const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 };
1736       const GdkColor black = { 0, 0x0000, 0x0000, 0x0000 };
1737
1738       static GtkTextTag* error_tag = gtk_text_buffer_create_tag (buffer, "red_foreground", "foreground-gdk", &red, NULL);
1739       static GtkTextTag* warning_tag = gtk_text_buffer_create_tag (buffer, "yellow_foreground", "foreground-gdk", &yellow, NULL);
1740       static GtkTextTag* standard_tag = gtk_text_buffer_create_tag (buffer, "black_foreground", "foreground-gdk", &black, NULL);
1741       GtkTextTag* tag;
1742       switch (level)
1743       {
1744       case SYS_WRN:
1745         tag = warning_tag;
1746         break;
1747       case SYS_ERR:
1748         tag = error_tag;
1749         break;
1750       case SYS_STD:
1751       case SYS_VRB:
1752       default:
1753         tag = standard_tag;
1754         break;
1755       }
1756       gtk_text_buffer_insert_with_tags(buffer, &iter, buf, length, tag, NULL);
1757
1758       gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit), end);
1759
1760       // update console widget immediatly if we're doing something time-consuming
1761       if( !g_bScreenUpdates && GTK_WIDGET_REALIZED( g_qeglobals_gui.d_edit ) )
1762       {
1763         gtk_grab_add(g_qeglobals_gui.d_edit);
1764
1765         while(gtk_events_pending())
1766           gtk_main_iteration();
1767
1768         gtk_grab_remove(g_qeglobals_gui.d_edit);
1769       }
1770     }
1771   }  
1772 }
1773
1774 // NOTE: this is the handler sent to synapse
1775 // must match PFN_SYN_PRINTF_VA
1776 extern "C" void Sys_Printf_VA (const char *text, va_list args)
1777 {
1778   Sys_FPrintf_VA (SYS_STD, text, args);
1779 }
1780
1781 extern "C" void Sys_Printf (const char *text, ...)
1782 {
1783   va_list args;
1784
1785   va_start (args, text);
1786   Sys_FPrintf_VA (SYS_STD, text, args);
1787   va_end (args);
1788 }
1789
1790 extern "C" void Sys_FPrintf (int level, const char *text, ...)
1791 {
1792   va_list args;
1793
1794   va_start (args, text);
1795   Sys_FPrintf_VA (level, text, args);
1796   va_end (args);
1797 }