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