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