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