-/*\r
-Copyright (C) 1999-2007 id Software, Inc. and contributors.\r
-For a list of contributors, see the accompanying CONTRIBUTORS file.\r
-\r
-This file is part of GtkRadiant.\r
-\r
-GtkRadiant is free software; you can redistribute it and/or modify\r
-it under the terms of the GNU General Public License as published by\r
-the Free Software Foundation; either version 2 of the License, or\r
-(at your option) any later version.\r
-\r
-GtkRadiant is distributed in the hope that it will be useful,\r
-but WITHOUT ANY WARRANTY; without even the implied warranty of\r
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
-GNU General Public License for more details.\r
-\r
-You should have received a copy of the GNU General Public License\r
-along with GtkRadiant; if not, write to the Free Software\r
-Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA\r
-*/\r
-\r
-//\r
-// Linux stuff\r
-//\r
-// Leonardo Zide (leo@lokigames.com)\r
-//\r
-\r
-#include "stdafx.h"\r
-#include <gtk/gtk.h>\r
-#include <sys/stat.h> \r
-#include "gtkmisc.h"\r
-#if defined (__linux__) || defined (__APPLE__)\r
-#include <unistd.h>\r
-#include <X11/keysym.h>\r
-#include <gdk/gdkx.h>\r
-#include <gdk/gdkprivate.h>\r
-#endif\r
-// for the logging part\r
-#include <fcntl.h>\r
-#include <sys/types.h>\r
-\r
-QEGlobals_t g_qeglobals;\r
-QEGlobals_GUI_t g_qeglobals_gui;\r
-\r
-// leo: Track memory allocations for debugging\r
-// NOTE TTimo this was never used and probably not relevant\r
-// there are tools to do that\r
-#ifdef MEM_DEBUG\r
-\r
-static GList *memblocks;\r
-\r
-void* debug_malloc (size_t size, const char* file, int line)\r
-{\r
- void *buf = g_malloc (size + 8);\r
-\r
- *((const char**)buf) = file;\r
- buf = (char*)buf + 4;\r
- *((int*)buf) = line;\r
- buf = (char*)buf + 4;\r
-\r
- memblocks = g_list_append (memblocks, buf);\r
-\r
- return buf;\r
-}\r
-\r
-void debug_free (void *buf, const char* file, int line)\r
-{\r
- const char *f;\r
- int l;\r
-\r
- if (g_list_find (memblocks, buf))\r
- {\r
- memblocks = g_list_remove (memblocks, buf);\r
-\r
- buf = (char*)buf - 4;\r
- l = *((int*)buf);\r
- buf = (char*)buf - 4;\r
- f = *((const char**)buf);\r
-\r
- Sys_FPrintf (SYS_DBG, "free: %s %d", file, line);\r
- Sys_FPrintf (SYS_DBG, " allocated: %s %d\n", f, l);\r
-\r
- g_free (buf);\r
- }\r
-// else\r
-// free (buf); // from qmalloc, will leak unless we add this same hack to cmdlib\r
-}\r
-\r
-#endif\r
-\r
-vec_t Rad_rint (vec_t in)\r
-{\r
- if (g_PrefsDlg.m_bNoClamp)\r
- return in;\r
- else\r
- return (float)floor (in + 0.5);\r
-}\r
-\r
-void WINAPI QE_CheckOpenGLForErrors(void)\r
-{\r
- char strMsg[1024];\r
- int i = qglGetError();\r
- if (i != GL_NO_ERROR)\r
- {\r
- if (i == GL_OUT_OF_MEMORY)\r
- { \r
- sprintf(strMsg, "OpenGL out of memory error %s\nDo you wish to save before exiting?", qgluErrorString((GLenum)i));\r
- if (gtk_MessageBox(g_pParentWnd->m_pWidget, strMsg, "Radiant Error", MB_YESNO) == IDYES)\r
- {\r
- Map_SaveFile(NULL, false);\r
- }\r
- _exit(1);\r
- }\r
- else\r
- {\r
- Sys_Printf ("Warning: OpenGL Error %s\n", qgluErrorString((GLenum)i));\r
- }\r
- }\r
-}\r
-\r
-// NOTE: don't this function, use VFS instead\r
-char *ExpandReletivePath (char *p)\r
-{\r
- static char temp[1024];\r
- const char *base;\r
-\r
- if (!p || !p[0])\r
- return NULL;\r
- if (p[0] == '/' || p[0] == '\\')\r
- return p;\r
-\r
- base = ValueForKey(g_qeglobals.d_project_entity, "basepath");\r
- sprintf (temp, "%s/%s", base, p);\r
- return temp;\r
-}\r
-\r
-char *copystring (char *s)\r
-{\r
- char *b;\r
- b = (char*)malloc(strlen(s)+1);\r
- strcpy (b,s);\r
- return b;\r
-}\r
-\r
-\r
-bool DoesFileExist(const char* pBuff, long& lSize)\r
-{\r
- FileStream file;\r
- if (file.Open(pBuff, "r"))\r
- {\r
- lSize += file.GetLength();\r
- file.Close();\r
- return true;\r
- }\r
- return false;\r
-}\r
-\r
-\r
-void Map_Snapshot()\r
-{\r
- CString strMsg;\r
-\r
- // I hope the modified flag is kept correctly up to date\r
- if (!modified)\r
- return;\r
-\r
- // we need to do the following\r
- // 1. make sure the snapshot directory exists (create it if it doesn't)\r
- // 2. find out what the lastest save is based on number\r
- // 3. inc that and save the map\r
- CString strOrgPath, strOrgFile;\r
- ExtractPath_and_Filename(currentmap, strOrgPath, strOrgFile);\r
- AddSlash(strOrgPath);\r
- strOrgPath += "snapshots";\r
- bool bGo = true;\r
- struct stat Stat;\r
- if (stat(strOrgPath, &Stat) == -1)\r
- {\r
-#ifdef _WIN32\r
- bGo = (_mkdir(strOrgPath) != -1);\r
-#endif\r
-\r
-#if defined (__linux__) || defined (__APPLE__)\r
- bGo = (mkdir(strOrgPath,0755) != -1);\r
-#endif\r
- }\r
- AddSlash(strOrgPath);\r
- if (bGo)\r
- {\r
- int nCount = 0;\r
- long lSize = 0;\r
- CString strNewPath;\r
- strNewPath = strOrgPath;\r
- strNewPath += strOrgFile;\r
- CString strFile;\r
- while (bGo)\r
- {\r
- char buf[PATH_MAX];\r
- sprintf( buf, "%s.%i", strNewPath.GetBuffer(), nCount );\r
- strFile = buf;\r
- bGo = DoesFileExist(strFile, lSize);\r
- nCount++;\r
- }\r
- // strFile has the next available slot\r
- Map_SaveFile(strFile, false);\r
- // it is still a modified map (we enter this only if this is a modified map)\r
- Sys_SetTitle (currentmap);\r
- Sys_MarkMapModified();\r
- if (lSize > 12 * 1024 * 1024) // total size of saves > 4 mb\r
- {\r
- Sys_Printf("The snapshot files in %s total more than 4 megabytes. You might consider cleaning up.", strOrgPath.GetBuffer());\r
- }\r
- }\r
- else\r
- {\r
- strMsg.Format("Snapshot save failed.. unabled to create directory\n%s", strOrgPath.GetBuffer());\r
- gtk_MessageBox(g_pParentWnd->m_pWidget, strMsg);\r
- }\r
- strOrgPath = "";\r
- strOrgFile = "";\r
-}\r
-/*\r
-===============\r
-QE_CheckAutoSave\r
-\r
-If five minutes have passed since making a change\r
-and the map hasn't been saved, save it out.\r
-===============\r
-*/\r
-\r
-\r
-void QE_CheckAutoSave( void )\r
-{\r
- static time_t s_start;\r
- time_t now;\r
- time (&now);\r
-\r
- if (modified != 1 || !s_start)\r
- {\r
- s_start = now;\r
- return;\r
- }\r
-\r
- if ((now - s_start) > (60 * g_PrefsDlg.m_nAutoSave))\r
- {\r
- if (g_PrefsDlg.m_bAutoSave)\r
- {\r
- CString strMsg;\r
- strMsg = g_PrefsDlg.m_bSnapShots ? "Autosaving snapshot..." : "Autosaving...";\r
- Sys_Printf(strMsg);\r
- Sys_Printf("\n");\r
- Sys_Status (strMsg,0);\r
-\r
- // only snapshot if not working on a default map\r
- if (g_PrefsDlg.m_bSnapShots && stricmp(currentmap, "unnamed.map") != 0)\r
- {\r
- Map_Snapshot();\r
- }\r
- else\r
- {\r
- Map_SaveFile (ValueForKey(g_qeglobals.d_project_entity, "autosave"), false);\r
- }\r
-\r
- Sys_Status ("Autosaving...Saved.", 0 );\r
- modified = 2;\r
- }\r
- else\r
- {\r
- Sys_Printf ("Autosave skipped...\n");\r
- Sys_Status ("Autosave skipped...", 0 );\r
- }\r
- s_start = now;\r
- }\r
-}\r
-\r
-\r
-// NOTE TTimo we don't like that BuildShortPathName too much\r
-// the VFS provides a vfsCleanFileName which should perform the cleanup tasks\r
-// in the long run I'd like to completely get rid of this\r
-\r
-// http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=144\r
-// used to be disabled, but caused problems\r
-\r
-// http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=291\r
-// can't work with long win32 names until the BSP commands are not working differently\r
-#ifdef _WIN32\r
-int BuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen)\r
-{\r
- char *pFile = NULL;\r
- int nResult = GetFullPathName(pPath, nBufferLen, pBuffer, &pFile);\r
- nResult = GetShortPathName(pPath, pBuffer, nBufferLen);\r
- if (nResult == 0)\r
- strcpy(pBuffer, pPath); // Use long filename\r
- return nResult;\r
-}\r
-#endif\r
-\r
-#if defined (__linux__) || defined (__APPLE__)\r
-int BuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen)\r
-{\r
- // remove /../ from directories\r
- const char *scr = pPath; char *dst = pBuffer;\r
- for (int i = 0; (i < nBufferLen) && (*scr != 0); i++)\r
- {\r
- if (*scr == '/' && *(scr+1) == '.' && *(scr+2) == '.')\r
- {\r
- scr += 3;\r
- while (dst != pBuffer && *(--dst) != '/')\r
- {\r
- i--;\r
- }\r
- }\r
-\r
- *dst = *scr;\r
-\r
- scr++; dst++;\r
- }\r
- *dst = 0;\r
-\r
- return strlen (pBuffer);\r
-}\r
-#endif\r
-\r
-/*\r
-const char *g_pPathFixups[]=\r
-{\r
- "basepath",\r
- "autosave",\r
-};\r
-\r
-const int g_nPathFixupCount = sizeof(g_pPathFixups) / sizeof(const char*);\r
-\r
-void QE_CheckProjectEntity()\r
-{\r
- char *pFile;\r
- char pBuff[PATH_MAX];\r
- char pNewPath[PATH_MAX];\r
- for (int i = 0; i < g_nPathFixupCount; i++)\r
- {\r
- char *pPath = ValueForKey (g_qeglobals.d_project_entity, g_pPathFixups[i]);\r
-\r
- strcpy (pNewPath, pPath);\r
- if (pPath[0] != '\\' && pPath[0] != '/')\r
- if (GetFullPathName(pPath, PATH_MAX, pBuff, &pFile))\r
- strcpy (pNewPath, pBuff);\r
-\r
- BuildShortPathName (pNewPath, pBuff, PATH_MAX);\r
-\r
- // check it's not ending with a filename seperator\r
- if (pBuff[strlen(pBuff)-1] == '/' || pBuff[strlen(pBuff)-1] == '\\')\r
- {\r
- Sys_FPrintf(SYS_WRN, "WARNING: \"%s\" path in the project file has an ending file seperator, fixing.\n", g_pPathFixups[i]);\r
- pBuff[strlen(pBuff)-1]=0;\r
- }\r
-\r
- SetKeyValue(g_qeglobals.d_project_entity, g_pPathFixups[i], pBuff);\r
- }\r
-}\r
-*/\r
-\r
-void HandleXMLError( void* ctxt, const char* text, ... )\r
-{\r
- va_list argptr;\r
- static char buf[32768];\r
-\r
- va_start (argptr,text);\r
- vsprintf (buf, text, argptr);\r
- Sys_FPrintf (SYS_ERR, "XML %s\n", buf);\r
- va_end (argptr);\r
-}\r
-\r
-#define DTD_BUFFER_LENGTH 1024\r
-xmlDocPtr ParseXMLStream(IDataStream *stream, bool validate = false)\r
-{\r
- xmlDocPtr doc = NULL;\r
- bool wellFormed = false, valid = false;\r
- int res, size = 1024;\r
- char chars[1024];\r
- xmlParserCtxtPtr ctxt;\r
-\r
- // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=433\r
- //if(validate)\r
- // xmlDoValidityCheckingDefaultValue = 1;\r
- //else\r
- xmlDoValidityCheckingDefaultValue = 0;\r
- xmlSetGenericErrorFunc(NULL, HandleXMLError);\r
-\r
- // SPoG\r
- // HACK: use AppPath to resolve DTD location\r
- // do a buffer-safe string copy and concatenate\r
- int i;\r
- char* w;\r
- const char* r;\r
- char buf[DTD_BUFFER_LENGTH];\r
-\r
- w = buf;\r
- i = 0;\r
- // copy\r
- //assert(g_strAppPath.GetBuffer() != NULL);\r
- for(r = g_strAppPath.GetBuffer(); i<DTD_BUFFER_LENGTH && *r != '\0'; i++, r++) w[i] = *r;\r
- // concatenate\r
- for(r = "dtds/"; i<DTD_BUFFER_LENGTH && *r != '\0'; i++, r++) w[i] = *r;\r
- // terminate\r
- w[i] = '\0';\r
-\r
- if(i == DTD_BUFFER_LENGTH)\r
- {\r
- HandleXMLError(NULL, "ERROR: buffer overflow: DTD path length too large\n");\r
- return NULL;\r
- }\r
-\r
- res = stream->Read(chars, 4);\r
- if (res > 0)\r
- {\r
- ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, buf);\r
-\r
- while ((res = stream->Read(chars, size)) > 0)\r
- {\r
- xmlParseChunk(ctxt, chars, res, 0);\r
- }\r
- xmlParseChunk(ctxt, chars, 0, 1);\r
- doc = ctxt->myDoc;\r
-\r
- wellFormed = (ctxt->wellFormed == 1);\r
- valid = (ctxt->valid == 1);\r
-\r
- xmlFreeParserCtxt(ctxt);\r
- }\r
-\r
- if(wellFormed && (!validate || (validate && valid)))\r
- return doc;\r
-\r
- if(doc != NULL)\r
- xmlFreeDoc(doc);\r
-\r
- return NULL;\r
-}\r
-\r
-xmlDocPtr ParseXMLFile(const char* filename, bool validate = false)\r
-{\r
- FileStream stream;\r
- if (stream.Open(filename, "r"))\r
- return ParseXMLStream(&stream, validate);\r
- \r
- Sys_FPrintf(SYS_ERR, "Failed to open file: %s\n",filename);\r
- return NULL;\r
-}\r
-\r
-// copy a string r to a buffer w\r
-// replace $string as appropriate\r
-void ReplaceTemplates(char* w, const char* r)\r
-{\r
- const char *p;\r
- const char *__ENGINEPATH = "TEMPLATEenginepath";\r
- const char *__USERHOMEPATH = "TEMPLATEuserhomepath";\r
- const char *__TOOLSPATH = "TEMPLATEtoolspath";\r
- const char *__BASEDIR = "TEMPLATEbasedir";\r
- const char *__APPPATH = "TEMPLATEapppath";\r
-\r
- // iterate through string r\r
- while(*r!='\0')\r
- {\r
- // check for special character\r
- if(*r=='$')\r
- {\r
- if(strncmp(r+1, __ENGINEPATH, strlen(__ENGINEPATH)) == 0)\r
- {\r
- r+=strlen(__ENGINEPATH)+1;\r
- p = g_pGameDescription->mEnginePath.GetBuffer();\r
- }\r
- else if(strncmp(r+1, __USERHOMEPATH, strlen(__USERHOMEPATH)) == 0)\r
- {\r
- r+=strlen(__USERHOMEPATH)+1;\r
- p = g_qeglobals.m_strHomeGame.GetBuffer();\r
- }\r
- else if(strncmp(r+1, __BASEDIR, strlen(__BASEDIR)) == 0)\r
- {\r
- r+=strlen(__BASEDIR)+1;\r
- p = g_pGameDescription->mBaseGame;\r
- }\r
- else if(strncmp(r+1, __TOOLSPATH, strlen(__TOOLSPATH)) == 0)\r
- {\r
- r+=strlen(__TOOLSPATH)+1;\r
- p = g_strGameToolsPath.GetBuffer();\r
- }\r
- else if(strncmp(r+1, __APPPATH, strlen(__APPPATH)) == 0)\r
- {\r
- r+=strlen(__APPPATH)+1;\r
- p = g_strAppPath.GetBuffer();\r
- }\r
- else\r
- {\r
- r++;\r
- p = "$";\r
- }\r
- \r
- while(*p!='\0') *w++ = *p++;\r
- }\r
- else *w++ = *r++;\r
- }\r
- *w = '\0';\r
-}\r
-\r
-/*\r
-===========\r
-QE_LoadProject\r
-TODO TODO TODO (don't think this got fully merged in)\r
-TTimo: added project file "version", version 2 adds '#' chars to the BSP command strings\r
-version 3 was .. I don't remember .. version 4 adds q3map2 commands\r
-TTimo: when QE_LoadProject is called, the prefs are updated with path to the latest project and saved on disk\r
-===========\r
-*/\r
-/*\todo decide on a sensible location/name for project files.*/\r
-bool QE_LoadProject (const char *projectfile)\r
-{\r
- char buf[1024];\r
- xmlDocPtr doc;\r
- xmlNodePtr node, project;\r
-\r
- Sys_Printf("Loading project file: \"%s\"\n", projectfile);\r
- doc = ParseXMLFile(projectfile, true);\r
-\r
- if(doc == NULL) return false;\r
-\r
- node=doc->children;\r
- while(node != NULL && node->type != XML_DTD_NODE) node=node->next;\r
- if(node == NULL || strcmp((char*)node->name, "project") != 0)\r
- {\r
- Sys_FPrintf(SYS_ERR, "ERROR: invalid file type\n");\r
- return false;\r
- }\r
-\r
- while(node->type != XML_ELEMENT_NODE) node=node->next;\r
- // <project>\r
- project = node;\r
-\r
- if(g_qeglobals.d_project_entity != NULL) Entity_Free(g_qeglobals.d_project_entity);\r
- g_qeglobals.d_project_entity = Entity_Alloc();\r
-\r
- for(node = project->children; node != NULL; node=node->next)\r
- {\r
- if(node->type != XML_ELEMENT_NODE) continue;\r
-\r
- // <key>\r
- ReplaceTemplates(buf, (char*)node->properties->next->children->content);\r
-\r
- SetKeyValue(g_qeglobals.d_project_entity, (char*)node->properties->children->content, buf);\r
- }\r
-\r
- xmlFreeDoc(doc);\r
- \r
- // project file version checking\r
- // add a version checking to avoid people loading later versions of the project file and bitching\r
- int ver = IntForKey( g_qeglobals.d_project_entity, "version" );\r
- if (ver > PROJECT_VERSION)\r
- {\r
- char strMsg[1024];\r
- sprintf (strMsg, "This is a version %d project file. This build only supports <=%d project files.\n"\r
- "Please choose another project file or upgrade your version of Radiant.", ver, PROJECT_VERSION);\r
- gtk_MessageBox (g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK);\r
- // set the project file to nothing so we are sure we'll ask next time?\r
- g_PrefsDlg.m_strLastProject = "";\r
- g_PrefsDlg.SavePrefs();\r
- return false;\r
- }\r
- \r
- // set here some default project settings you need\r
- if ( strlen( ValueForKey( g_qeglobals.d_project_entity, "brush_primit" ) ) == 0 )\r
- {\r
- SetKeyValue( g_qeglobals.d_project_entity, "brush_primit", "0" );\r
- }\r
-\r
- g_qeglobals.m_bBrushPrimitMode = IntForKey( g_qeglobals.d_project_entity, "brush_primit" );\r
-\r
- g_qeglobals.m_strHomeMaps = g_qeglobals.m_strHomeGame;\r
- const char* str = ValueForKey(g_qeglobals.d_project_entity, "gamename");\r
- if(str[0] == '\0') str = g_pGameDescription->mBaseGame.GetBuffer();\r
- g_qeglobals.m_strHomeMaps += str;\r
- g_qeglobals.m_strHomeMaps += '/';\r
-\r
- // don't forget to create the dirs\r
- Q_mkdir(g_qeglobals.m_strHomeGame.GetBuffer(), 0775);\r
- Q_mkdir(g_qeglobals.m_strHomeMaps.GetBuffer(), 0775);\r
- \r
- // usefull for the log file and debuggin fucked up configurations from users:\r
- // output the basic information of the .qe4 project file\r
- // SPoG\r
- // all these paths should be unix format, with a trailing slash at the end\r
- // if not.. to debug, check that the project file paths are set up correctly\r
- Sys_Printf("basepath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "basepath") );\r
- Sys_Printf("entitypath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "entitypath" ) );\r
-\r
-\r
- // check whether user_project key exists..\r
- // if not, save the current project under a new name\r
- if (ValueForKey(g_qeglobals.d_project_entity, "user_project")[0] == '\0')\r
- {\r
- Sys_Printf("Loaded a template project file\n");\r
- \r
- // create the user_project key\r
- SetKeyValue( g_qeglobals.d_project_entity, "user_project", "1" );\r
- \r
- // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=672\r
- if (IntForKey( g_qeglobals.d_project_entity, "version" ) != PROJECT_VERSION)\r
- {\r
- char strMsg[2048];\r
- sprintf(strMsg, \r
- "The template project '%s' has version %d. The editor binary is configured for version %d.\n"\r
- "This indicates a problem in your setup. See http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=672\n"\r
- "I will keep going with this project till you fix this",\r
- projectfile, IntForKey( g_qeglobals.d_project_entity, "version" ), PROJECT_VERSION);\r
- gtk_MessageBox (g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK);\r
- }\r
- \r
- // create the writable project file path\r
- strcpy(buf, g_qeglobals.m_strHomeGame.GetBuffer());\r
- strcat(buf, g_pGameDescription->mBaseGame.GetBuffer());\r
- strcat(buf, "/scripts/");\r
- // while the filename is already in use, increment the number we add to the end\r
- int counter = 0;\r
- char pUser[PATH_MAX];\r
- while (1)\r
- { \r
- sprintf( pUser, "%suser%d." PROJECT_FILETYPE, buf, counter );\r
- counter++;\r
- if (access( pUser, R_OK) != 0)\r
- {\r
- // this is the one\r
- strcpy( buf, pUser );\r
- break;\r
- }\r
- }\r
- // saving project will cause a save prefs\r
- g_PrefsDlg.m_strLastProject = buf;\r
- g_PrefsDlg.m_nLastProjectVer = IntForKey( g_qeglobals.d_project_entity, "version" ); \r
- QE_SaveProject(buf);\r
- }\r
- else\r
- {\r
- // update preferences::LastProject with path of this successfully-loaded project\r
- // save preferences\r
- Sys_Printf("Setting current project in prefs to \"%s\"\n", g_PrefsDlg.m_strLastProject.GetBuffer() );\r
- g_PrefsDlg.m_strLastProject = projectfile;\r
- g_PrefsDlg.SavePrefs();\r
- }\r
-\r
- return true;\r
-}\r
-\r
-/*\r
-===========\r
-QE_SaveProject\r
-TTimo: whenever QE_SaveProject is called, prefs are updated and saved with the path to the project\r
-===========\r
-*/\r
-qboolean QE_SaveProject (const char* filename)\r
-{\r
- Sys_Printf("Save project file '%s'\n", filename);\r
-\r
- xmlNodePtr node;\r
- xmlDocPtr doc = xmlNewDoc((xmlChar *)"1.0");\r
- // create DTD node\r
- xmlCreateIntSubset(doc, (xmlChar *)"project", NULL, (xmlChar *)"project.dtd");\r
- // create project node\r
- doc->children->next = xmlNewDocNode(doc, NULL, (xmlChar *)"project", NULL);\r
-\r
- for(epair_t* epair = g_qeglobals.d_project_entity->epairs; epair != NULL; epair = epair->next)\r
- {\r
- node = xmlNewChild(doc->children->next, NULL, (xmlChar *)"key", NULL);\r
- xmlSetProp(node, (xmlChar*)"name", (xmlChar*)epair->key);\r
- xmlSetProp(node, (xmlChar*)"value", (xmlChar*)epair->value);\r
- }\r
-\r
- CreateDirectoryPath(filename);\r
- if (xmlSaveFormatFile(filename, doc, 1) != -1)\r
- {\r
- xmlFreeDoc(doc);\r
- Sys_Printf("Setting current project in prefs to \"%s\"\n", filename );\r
- g_PrefsDlg.m_strLastProject = filename;\r
- g_PrefsDlg.SavePrefs();\r
- return TRUE;\r
- }\r
- else\r
- {\r
- xmlFreeDoc(doc);\r
- Sys_FPrintf(SYS_ERR, "failed to save project file: \"%s\"\n", filename);\r
- return FALSE;\r
- }\r
-}\r
-\r
-\r
-\r
-/*\r
-===========\r
-QE_KeyDown\r
-===========\r
-*/\r
-#define SPEED_MOVE 32\r
-#define SPEED_TURN 22.5\r
-\r
-\r
-/*\r
-===============\r
-ConnectEntities\r
-\r
-Sets target / targetname on the two entities selected\r
-from the first selected to the secon\r
-===============\r
-*/\r
-void ConnectEntities (void)\r
-{\r
- entity_t *e1, *e2;\r
- const char *target;\r
- char *newtarg = NULL;\r
-\r
- if (g_qeglobals.d_select_count != 2)\r
- {\r
- Sys_Status ("Must have two brushes selected", 0);\r
- Sys_Beep ();\r
- return;\r
- }\r
-\r
- e1 = g_qeglobals.d_select_order[0]->owner;\r
- e2 = g_qeglobals.d_select_order[1]->owner;\r
-\r
- if (e1 == world_entity || e2 == world_entity)\r
- {\r
- Sys_Status ("Can't connect to the world", 0);\r
- Sys_Beep ();\r
- return;\r
- }\r
-\r
- if (e1 == e2)\r
- {\r
- Sys_Status ("Brushes are from same entity", 0);\r
- Sys_Beep ();\r
- return;\r
- }\r
- \r
- target = ValueForKey (e1, "target");\r
- if (target && target[0])\r
- newtarg = g_strdup(target);\r
- else\r
- {\r
- target = ValueForKey(e2, "targetname");\r
- if(target && target[0])\r
- newtarg = g_strdup(target);\r
- else\r
- Entity_Connect(e1, e2);\r
- }\r
-\r
- if(newtarg != NULL)\r
- {\r
- SetKeyValue(e1, "target", newtarg);\r
- SetKeyValue(e2, "targetname", newtarg);\r
- g_free(newtarg);\r
- }\r
- \r
- Sys_UpdateWindows (W_XY | W_CAMERA);\r
-\r
- Select_Deselect();\r
- Select_Brush (g_qeglobals.d_select_order[1]);\r
-}\r
-\r
-qboolean QE_SingleBrush (bool bQuiet)\r
-{\r
- if ( (selected_brushes.next == &selected_brushes)\r
- || (selected_brushes.next->next != &selected_brushes) )\r
- {\r
- if (!bQuiet)\r
- {\r
- Sys_Printf ("Error: you must have a single brush selected\n");\r
- }\r
- return false;\r
- }\r
- if (selected_brushes.next->owner->eclass->fixedsize)\r
- {\r
- if (!bQuiet)\r
- {\r
- Sys_Printf ("Error: you cannot manipulate fixed size entities\n");\r
- }\r
- return false;\r
- }\r
-\r
- return true;\r
-}\r
-\r
-void QE_InitVFS (void)\r
-{\r
- // VFS initialization -----------------------\r
- // we will call vfsInitDirectory, giving the directories to look in (for files in pk3's and for standalone files)\r
- // we need to call in order, the mod ones first, then the base ones .. they will be searched in this order\r
- // *nix systems have a dual filesystem in ~/.q3a, which is searched first .. so we need to add that too\r
- Str directory,prefabs;\r
-\r
- // TTimo: let's leave this to HL mode for now\r
- if (g_pGameDescription->mGameFile == "hl.game")\r
- {\r
- // Hydra: we search the "gametools" path first so that we can provide editor\r
- // specific pk3's wads and misc files for use by the editor.\r
- // the relevant map compiler tools will NOT use this directory, so this helps\r
- // to ensure that editor files are not used/required in release versions of maps\r
- // it also helps keep your editor files all in once place, with the editor modules,\r
- // plugins, scripts and config files.\r
- // it also helps when testing maps, as you'll know your files won't/can't be used\r
- // by the game engine itself.\r
- \r
- // <gametools>\r
- directory = g_pGameDescription->mGameToolsPath;\r
- vfsInitDirectory(directory.GetBuffer());\r
- }\r
-\r
- // NOTE TTimo about the mymkdir calls .. this is a bit dirty, but a safe thing on *nix\r
-\r
- // if we have a mod dir\r
- if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')\r
- {\r
-\r
-#if defined (__linux__) || defined (__APPLE__)\r
- // ~/.<gameprefix>/<fs_game>\r
- directory = g_qeglobals.m_strHomeGame.GetBuffer();\r
- Q_mkdir (directory.GetBuffer (), 0775);\r
- directory += ValueForKey(g_qeglobals.d_project_entity, "gamename");\r
- Q_mkdir (directory.GetBuffer (), 0775);\r
- vfsInitDirectory(directory.GetBuffer());\r
- AddSlash (directory);\r
- prefabs = directory;\r
- // also create the maps dir, it will be used as prompt for load/save\r
- directory += "/maps";\r
- Q_mkdir (directory, 0775);\r
- // and the prefabs dir\r
- prefabs += "/prefabs";\r
- Q_mkdir (prefabs, 0775);\r
-\r
-#endif\r
-\r
- // <fs_basepath>/<fs_game>\r
- directory = g_pGameDescription->mEnginePath;\r
- directory += ValueForKey(g_qeglobals.d_project_entity, "gamename");\r
- Q_mkdir (directory.GetBuffer (), 0775);\r
- vfsInitDirectory(directory.GetBuffer());\r
- AddSlash(directory);\r
- prefabs = directory;\r
- // also create the maps dir, it will be used as prompt for load/save\r
- directory += "/maps";\r
- Q_mkdir (directory.GetBuffer (), 0775);\r
- // and the prefabs dir\r
- prefabs += "/prefabs";\r
- Q_mkdir (prefabs, 0775);\r
- }\r
-\r
-#if defined (__linux__) || defined (__APPLE__)\r
- // ~/.<gameprefix>/<fs_main>\r
- directory = g_qeglobals.m_strHomeGame.GetBuffer();\r
- directory += g_pGameDescription->mBaseGame;\r
- vfsInitDirectory (directory.GetBuffer ());\r
-#endif\r
-\r
- // <fs_basepath>/<fs_main>\r
- directory = g_pGameDescription->mEnginePath;\r
- directory += g_pGameDescription->mBaseGame;\r
- vfsInitDirectory(directory.GetBuffer());\r
-}\r
-\r
-void QE_Init (void)\r
-{\r
- /*\r
- ** initialize variables\r
- */\r
- g_qeglobals.d_gridsize = 8;\r
- g_qeglobals.d_showgrid = true;\r
-\r
- QE_InitVFS();\r
-\r
- Eclass_Init();\r
- FillClassList(); // list in entity window\r
- Map_Init();\r
-\r
- FillTextureMenu();\r
- FillBSPMenu();\r
-\r
- /*\r
- ** other stuff\r
- */\r
- Z_Init ();\r
-}\r
-\r
-void WINAPI QE_ConvertDOSToUnixName( char *dst, const char *src )\r
-{\r
- while ( *src )\r
- {\r
- if ( *src == '\\' )\r
- *dst = '/';\r
- else\r
- *dst = *src;\r
- dst++; src++;\r
- }\r
- *dst = 0;\r
-}\r
-\r
-int g_numbrushes, g_numentities;\r
-\r
-void QE_CountBrushesAndUpdateStatusBar( void )\r
-{\r
- static int s_lastbrushcount, s_lastentitycount;\r
- static qboolean s_didonce;\r
- \r
- //entity_t *e;\r
- brush_t *b, *next;\r
-\r
- g_numbrushes = 0;\r
- g_numentities = 0;\r
- \r
- if ( active_brushes.next != NULL )\r
- {\r
- for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; b=next)\r
- {\r
- next = b->next;\r
- if (b->brush_faces )\r
- {\r
- if ( !b->owner->eclass->fixedsize)\r
- g_numbrushes++;\r
- else\r
- g_numentities++;\r
- }\r
- }\r
- }\r
-/*\r
- if ( entities.next != NULL )\r
- {\r
- for ( e = entities.next ; e != &entities && g_numentities != MAX_MAP_ENTITIES ; e = e->next)\r
- {\r
- g_numentities++;\r
- }\r
- }\r
-*/\r
- if ( ( ( g_numbrushes != s_lastbrushcount ) || ( g_numentities != s_lastentitycount ) ) || ( !s_didonce ) )\r
- {\r
- Sys_UpdateStatusBar();\r
-\r
- s_lastbrushcount = g_numbrushes;\r
- s_lastentitycount = g_numentities;\r
- s_didonce = true;\r
- }\r
-}\r
-\r
-char com_token[1024];\r
-qboolean com_eof;\r
-\r
-/*\r
-================\r
-I_FloatTime\r
-================\r
-*/\r
-double I_FloatTime (void)\r
-{\r
- time_t t;\r
- \r
- time (&t);\r
- \r
- return t;\r
-#if 0\r
-// more precise, less portable\r
- struct timeval tp;\r
- struct timezone tzp;\r
- static int secbase;\r
-\r
- gettimeofday(&tp, &tzp);\r
- \r
- if (!secbase)\r
- {\r
- secbase = tp.tv_sec;\r
- return tp.tv_usec/1000000.0;\r
- }\r
- \r
- return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0;\r
-#endif\r
-}\r
-\r
-\r
-/*\r
-==============\r
-COM_Parse\r
-\r
-Parse a token out of a string\r
-==============\r
-*/\r
-char *COM_Parse (char *data)\r
-{\r
- int c;\r
- int len;\r
-\r
- len = 0;\r
- com_token[0] = 0;\r
- \r
- if (!data)\r
- return NULL;\r
- \r
-// skip whitespace\r
-skipwhite:\r
- while ( (c = *data) <= ' ')\r
- {\r
- if (c == 0)\r
- {\r
- com_eof = true;\r
- return NULL; // end of file;\r
- }\r
- data++;\r
- }\r
- \r
-// skip // comments\r
- if (c=='/' && data[1] == '/')\r
- {\r
- while (*data && *data != '\n')\r
- data++;\r
- goto skipwhite;\r
- }\r
- \r
-\r
-// handle quoted strings specially\r
- if (c == '\"')\r
- {\r
- data++;\r
- do\r
- {\r
- c = *data++;\r
- if (c=='\"')\r
- {\r
- com_token[len] = 0;\r
- return data;\r
- }\r
- com_token[len] = c;\r
- len++;\r
- } while (1);\r
- }\r
-\r
-// parse single characters\r
- if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')\r
- {\r
- com_token[len] = c;\r
- len++;\r
- com_token[len] = 0;\r
- return data+1;\r
- }\r
-\r
-// parse a regular word\r
- do\r
- {\r
- com_token[len] = c;\r
- data++;\r
- len++;\r
- c = *data;\r
- if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')\r
- break;\r
- } while (c>32);\r
- \r
- com_token[len] = 0;\r
- return data;\r
-}\r
-\r
-char* Get_COM_Token()\r
-{\r
- return com_token;\r
-}\r
-\r
-/*\r
-=============================================================================\r
-\r
- MISC FUNCTIONS\r
-\r
-=============================================================================\r
-*/\r
-\r
-\r
-int argc;\r
-char *argv[MAX_NUM_ARGVS];\r
-\r
-/*\r
-============\r
-ParseCommandLine\r
-============\r
-*/\r
-void ParseCommandLine (char *lpCmdLine)\r
-{\r
- argc = 1;\r
- argv[0] = "programname";\r
-\r
- while (*lpCmdLine && (argc < MAX_NUM_ARGVS))\r
- {\r
- while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))\r
- lpCmdLine++;\r
-\r
- if (*lpCmdLine)\r
- {\r
- argv[argc] = lpCmdLine;\r
- argc++;\r
-\r
- while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))\r
- lpCmdLine++;\r
-\r
- if (*lpCmdLine)\r
- {\r
- *lpCmdLine = 0;\r
- lpCmdLine++;\r
- }\r
- \r
- }\r
- }\r
-}\r
-\r
-\r
-\r
-/*\r
-=================\r
-CheckParm\r
-\r
-Checks for the given parameter in the program's command line arguments\r
-Returns the argument number (1 to argc-1) or 0 if not present\r
-=================\r
-*/\r
-int CheckParm (char *check)\r
-{\r
- int i;\r
-\r
- for (i = 1;i<argc;i++)\r
- {\r
- if ( stricmp(check, argv[i]) )\r
- return i;\r
- }\r
-\r
- return 0;\r
-}\r
-\r
-\r
-\r
-\r
-/*\r
-==============\r
-ParseNum / ParseHex\r
-==============\r
-*/\r
-int ParseHex (char *hex)\r
-{\r
- char *str;\r
- int num;\r
-\r
- num = 0;\r
- str = hex;\r
-\r
- while (*str)\r
- {\r
- num <<= 4;\r
- if (*str >= '0' && *str <= '9')\r
- num += *str-'0';\r
- else if (*str >= 'a' && *str <= 'f')\r
- num += 10 + *str-'a';\r
- else if (*str >= 'A' && *str <= 'F')\r
- num += 10 + *str-'A';\r
- else\r
- Error ("Bad hex number: %s",hex);\r
- str++;\r
- }\r
-\r
- return num;\r
-}\r
-\r
-\r
-int ParseNum (char *str)\r
-{\r
- if (str[0] == '$')\r
- return ParseHex (str+1);\r
- if (str[0] == '0' && str[1] == 'x')\r
- return ParseHex (str+2);\r
- return atol (str);\r
-}\r
-\r
-// BSP frontend plugin\r
-// global flag for BSP frontend plugin is g_qeglobals.bBSPFrontendPlugin\r
-_QERPlugBSPFrontendTable g_BSPFrontendTable;\r
-\r
-// =============================================================================\r
-// Sys_ functions\r
-\r
-bool Sys_AltDown ()\r
-{\r
-#ifdef _WIN32\r
- return (GetKeyState(VK_MENU) & 0x8000) != 0;\r
-#endif\r
-\r
-#if defined (__linux__) || defined (__APPLE__)\r
- char keys[32];\r
- int x;\r
-\r
- XQueryKeymap(GDK_DISPLAY(), keys);\r
-\r
- x = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L);\r
- if (keys[x/8] & (1 << (x % 8)))\r
- return true;\r
-\r
- x = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_R);\r
- if (keys[x/8] & (1 << (x % 8)))\r
- return true;\r
-\r
- return false;\r
-#endif\r
-}\r
-\r
-bool Sys_ShiftDown ()\r
-{\r
-#ifdef _WIN32\r
- return (GetKeyState(VK_SHIFT) & 0x8000) != 0;\r
-#endif\r
-\r
-#if defined (__linux__) || defined (__APPLE__)\r
- char keys[32];\r
- int x;\r
-\r
- XQueryKeymap(GDK_DISPLAY(), keys);\r
-\r
- x = XKeysymToKeycode (GDK_DISPLAY(), XK_Shift_L);\r
- if (keys[x/8] & (1 << (x % 8)))\r
- return true;\r
-\r
- x = XKeysymToKeycode (GDK_DISPLAY(), XK_Shift_R);\r
- if (keys[x/8] & (1 << (x % 8)))\r
- return true;\r
-\r
- return false;\r
-#endif\r
-}\r
-\r
-void Sys_MarkMapModified (void)\r
-{\r
- char title[PATH_MAX];\r
-\r
- if (modified != 1)\r
- {\r
- modified = true; // mark the map as changed\r
- sprintf (title, "%s *", currentmap);\r
-\r
- QE_ConvertDOSToUnixName( title, title );\r
- Sys_SetTitle (title);\r
- }\r
-}\r
-\r
-void Sys_SetTitle (const char *text)\r
-{\r
- gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_main_window), text);\r
-}\r
-\r
-bool g_bWaitCursor = false;\r
-\r
-void WINAPI Sys_BeginWait (void)\r
-{\r
- GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);\r
- gdk_window_set_cursor (g_pParentWnd->m_pWidget->window, cursor);\r
- gdk_cursor_unref (cursor);\r
- g_bWaitCursor = true;\r
-}\r
-\r
-void WINAPI Sys_EndWait (void)\r
-{\r
- GdkCursor *cursor = gdk_cursor_new (GDK_LEFT_PTR);\r
- gdk_window_set_cursor (g_pParentWnd->m_pWidget->window, cursor);\r
- gdk_cursor_unref (cursor);\r
- g_bWaitCursor = false;\r
-}\r
-\r
-void Sys_GetCursorPos (int *x, int *y)\r
-{\r
- // FIXME: not multihead safe\r
- gdk_window_get_pointer (NULL, x, y, NULL);\r
-}\r
-\r
-void Sys_SetCursorPos (int x, int y)\r
-{\r
- // NOTE: coordinates are in GDK space, not OS space\r
-#ifdef _WIN32\r
- int sys_x = x - g_pParentWnd->GetGDKOffsetX();\r
- int sys_y = y - g_pParentWnd->GetGDKOffsetY();\r
-\r
- SetCursorPos (sys_x, sys_y);\r
-#endif\r
-\r
-#if defined (__linux__) || defined (__APPLE__)\r
- XWarpPointer (GDK_DISPLAY(), None, GDK_ROOT_WINDOW(), 0, 0, 0, 0, x, y);\r
-#endif\r
-}\r
-\r
-void Sys_Beep (void)\r
-{\r
-#if defined (__linux__) || defined (__APPLE__)\r
- gdk_beep ();\r
-#else\r
- MessageBeep (MB_ICONASTERISK); \r
-#endif\r
-}\r
-\r
-double Sys_DoubleTime (void)\r
-{\r
- return clock()/ 1000.0;\r
-}\r
-\r
-/*\r
-===============================================================\r
-\r
- STATUS WINDOW\r
-\r
-===============================================================\r
-*/\r
-\r
-void Sys_UpdateStatusBar( void )\r
-{\r
- extern int g_numbrushes, g_numentities;\r
-\r
- char numbrushbuffer[100]="";\r
-\r
- sprintf( numbrushbuffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities );\r
- g_pParentWnd->SetStatusText(2, numbrushbuffer);\r
- //Sys_Status( numbrushbuffer, 2 );\r
-}\r
-\r
-void Sys_Status(const char *psz, int part )\r
-{\r
- g_pParentWnd->SetStatusText (part, psz);\r
-}\r
-\r
-// =============================================================================\r
-// MRU\r
-\r
-#define MRU_MAX 4\r
-static GtkWidget *MRU_items[MRU_MAX];\r
-static int MRU_used;\r
-typedef char MRU_filename_t[PATH_MAX];\r
-MRU_filename_t MRU_filenames[MRU_MAX];\r
-\r
-static char* MRU_GetText (int index)\r
-{\r
- return MRU_filenames[index];\r
-}\r
-\r
-void buffer_write_escaped_mnemonic(char* buffer, const char* string)\r
-{\r
- while(*string != '\0')\r
- {\r
- if(*string == '_')\r
- {\r
- *buffer++ = '_';\r
- }\r
-\r
- *buffer++ = *string++;\r
- }\r
- *buffer = '\0';\r
-}\r
-\r
-static void MRU_SetText (int index, const char *filename)\r
-{\r
- strcpy(MRU_filenames[index], filename);\r
-\r
- char mnemonic[PATH_MAX * 2 + 4];\r
- mnemonic[0] = '_';\r
- sprintf(mnemonic+1, "%d", index+1);\r
- mnemonic[2] = '-';\r
- mnemonic[3] = ' ';\r
- buffer_write_escaped_mnemonic(mnemonic+4, filename);\r
- gtk_label_set_text_with_mnemonic(GTK_LABEL (GTK_BIN (MRU_items[index])->child), mnemonic);\r
-}\r
-\r
-void MRU_Load ()\r
-{\r
- int i = g_PrefsDlg.m_nMRUCount;\r
-\r
- if(i > 4)\r
- i = 4; //FIXME: make this a define\r
-\r
- for (; i > 0; i--)\r
- MRU_AddFile (g_PrefsDlg.m_strMRUFiles[i-1].GetBuffer());\r
-}\r
-\r
-void MRU_Save ()\r
-{\r
- g_PrefsDlg.m_nMRUCount = MRU_used;\r
-\r
- for (int i = 0; i < MRU_used; i++)\r
- g_PrefsDlg.m_strMRUFiles[i] = MRU_GetText (i);\r
-}\r
-\r
-void MRU_AddWidget (GtkWidget *widget, int pos)\r
-{\r
- if (pos < MRU_MAX)\r
- MRU_items[pos] = widget;\r
-}\r
-\r
-void MRU_AddFile (const char *str)\r
-{\r
- int i;\r
- char* text;\r
-\r
- // check if file is already in our list\r
- for (i = 0; i < MRU_used; i++)\r
- {\r
- text = MRU_GetText (i);\r
-\r
- if (strcmp (text, str) == 0)\r
- {\r
- // reorder menu\r
- for (; i > 0; i--)\r
- MRU_SetText (i, MRU_GetText (i-1));\r
-\r
- MRU_SetText (0, str);\r
-\r
- return;\r
- }\r
- }\r
-\r
- if (MRU_used < MRU_MAX)\r
- MRU_used++;\r
-\r
- // move items down\r
- for (i = MRU_used-1; i > 0; i--)\r
- MRU_SetText (i, MRU_GetText (i-1));\r
-\r
- MRU_SetText (0, str);\r
- gtk_widget_set_sensitive (MRU_items[0], TRUE);\r
- gtk_widget_show (MRU_items[MRU_used-1]);\r
-}\r
-\r
-void MRU_Activate (int index)\r
-{\r
- char *text = MRU_GetText (index);\r
-\r
- if (access (text, R_OK) == 0)\r
- {\r
- text = strdup (text);\r
- MRU_AddFile (text);\r
- Map_LoadFile (text);\r
- free (text);\r
- }\r
- else\r
- {\r
- MRU_used--;\r
-\r
- for (int i = index; i < MRU_used; i++)\r
- MRU_SetText (i, MRU_GetText (i+1));\r
-\r
- if (MRU_used == 0)\r
- {\r
- gtk_label_set_text (GTK_LABEL (GTK_BIN (MRU_items[0])->child), "Recent Files");\r
- gtk_widget_set_sensitive (MRU_items[0], FALSE);\r
- }\r
- else\r
- {\r
- gtk_widget_hide (MRU_items[MRU_used]);\r
- }\r
- }\r
-}\r
-\r
-/*\r
-======================================================================\r
-\r
-FILE DIALOGS\r
-\r
-======================================================================\r
-*/\r
- \r
-qboolean ConfirmModified ()\r
-{\r
- if (!modified)\r
- return true;\r
-\r
- if (gtk_MessageBox (g_pParentWnd->m_pWidget, "This will lose changes to the map", "warning", MB_OKCANCEL) == IDCANCEL)\r
- return false;\r
- return true;\r
-}\r
-\r
-void ProjectDialog ()\r
-{\r
- const char *filename;\r
- char buffer[NAME_MAX];\r
-\r
- /* \r
- * Obtain the system directory name and \r
- * store it in buffer. \r
- */ \r
-\r
- strcpy(buffer, g_qeglobals.m_strHomeGame.GetBuffer());\r
- strcat(buffer, g_pGameDescription->mBaseGame.GetBuffer());\r
- strcat (buffer, "/scripts/");\r
-\r
- // Display the Open dialog box\r
- filename = file_dialog (NULL, TRUE, "Open File", buffer, "project");\r
-\r
- if (filename == NULL)\r
- return; // canceled\r
-\r
- // Open the file.\r
- // NOTE: QE_LoadProject takes care of saving prefs with new path to the project file\r
- if (!QE_LoadProject(filename))\r
- Sys_Printf ("Failed to load project from file: %s\n", filename);\r
- else\r
- // FIXME TTimo QE_Init is probably broken if you don't call it during startup right now .. \r
- QE_Init();\r
-}\r
-\r
-/*\r
-=======================================================\r
-\r
-Menu modifications\r
-\r
-=======================================================\r
-*/\r
-\r
-/*\r
-==================\r
-FillBSPMenu\r
-\r
-==================\r
-*/\r
-char *bsp_commands[256];\r
-\r
-void FillBSPMenu ()\r
-{\r
- GtkWidget *item, *menu; // menu points to a GtkMenu (not an item)\r
- epair_t *ep;\r
- GList *lst;\r
- int i;\r
-\r
- menu = GTK_WIDGET (g_object_get_data (G_OBJECT (g_qeglobals_gui.d_main_window), "menu_bsp"));\r
-\r
- while ((lst = gtk_container_children (GTK_CONTAINER (menu))) != NULL)\r
- gtk_container_remove (GTK_CONTAINER (menu), GTK_WIDGET (lst->data));\r
-\r
- if (g_PrefsDlg.m_bDetachableMenus) {\r
- item = gtk_tearoff_menu_item_new ();\r
- gtk_menu_append (GTK_MENU (menu), item);\r
- gtk_widget_set_sensitive (item, TRUE);\r
- gtk_widget_show (item);\r
- }\r
-\r
- if (g_qeglobals.bBSPFrontendPlugin)\r
- {\r
- CString str = g_BSPFrontendTable.m_pfnGetBSPMenu();\r
- char cTemp[1024];\r
- strcpy(cTemp, str);\r
- char* token = strtok(cTemp, ",;");\r
- if (token && *token == ' ')\r
- {\r
- while (*token == ' ')\r
- token++;\r
- }\r
- i = 0;\r
-\r
- // first token is menu name\r
- item = gtk_menu_get_attach_widget (GTK_MENU (menu));\r
- gtk_label_set_text (GTK_LABEL (GTK_BIN (item)->child), token);\r
-\r
- token = strtok(NULL, ",;");\r
- while (token != NULL)\r
- {\r
- g_BSPFrontendCommands = g_slist_append (g_BSPFrontendCommands, g_strdup (token));\r
- item = gtk_menu_item_new_with_label (token);\r
- gtk_widget_show (item);\r
- gtk_container_add (GTK_CONTAINER (menu), item);\r
- gtk_signal_connect (GTK_OBJECT (item), "activate",\r
- GTK_SIGNAL_FUNC (HandleCommand), GINT_TO_POINTER (CMD_BSPCOMMAND+i));\r
- token = strtok(NULL, ",;");\r
- i++;\r
- }\r
- }\r
- else\r
- {\r
- i = 0;\r
- for (ep = g_qeglobals.d_project_entity->epairs; ep; ep = ep->next)\r
- {\r
- if (strncmp(ep->key, "bsp_", 4)==0)\r
- {\r
- bsp_commands[i] = ep->key; \r
- item = gtk_menu_item_new_with_label (ep->key+4);\r
- gtk_widget_show (item);\r
- gtk_container_add (GTK_CONTAINER (menu), item);\r
- gtk_signal_connect (GTK_OBJECT (item), "activate",\r
- GTK_SIGNAL_FUNC (HandleCommand), GINT_TO_POINTER (CMD_BSPCOMMAND+i));\r
- i++;\r
- }\r
- }\r
- }\r
-}\r
-\r
-//==============================================\r
-\r
-void AddSlash(CString& strPath)\r
-{\r
- if (strPath.GetLength() > 0)\r
- {\r
- if ((strPath.GetAt(strPath.GetLength()-1) != '/') &&\r
- (strPath.GetAt(strPath.GetLength()-1) != '\\'))\r
- strPath += '/';\r
- }\r
-}\r
-\r
-bool ExtractPath_and_Filename(const char* pPath, CString& strPath, CString& strFilename)\r
-{\r
- CString strPathName;\r
- strPathName = pPath;\r
- int nSlash = strPathName.ReverseFind('\\');\r
- if (nSlash == -1)\r
- // TTimo: try forward slash, some are using forward\r
- nSlash = strPathName.ReverseFind('/');\r
- if (nSlash >= 0)\r
- {\r
- strPath = strPathName.Left(nSlash+1);\r
- strFilename = strPathName.Right(strPathName.GetLength() - nSlash - 1);\r
- }\r
- // TTimo: try forward slash, some are using forward\r
- else\r
- strFilename = pPath;\r
- return true;\r
-}\r
-\r
-//===========================================\r
-\r
-//++timo FIXME: no longer used .. remove!\r
-char *TranslateString (char *buf)\r
-{\r
- static char buf2[32768];\r
- int i, l;\r
- char *out;\r
-\r
- l = strlen(buf);\r
- out = buf2;\r
- for (i=0 ; i<l ; i++)\r
- {\r
- if (buf[i] == '\n')\r
- {\r
- *out++ = '\r';\r
- *out++ = '\n';\r
- }\r
- else\r
- *out++ = buf[i];\r
- }\r
- *out++ = 0;\r
-\r
- return buf2;\r
-}\r
-\r
-// called whenever we need to open/close/check the console log file\r
-void Sys_LogFile (void)\r
-{\r
- if (g_PrefsDlg.mGamesDialog.m_bLogConsole && !g_qeglobals.hLogFile)\r
- {\r
- // settings say we should be logging and we don't have a log file .. so create it\r
- // open a file to log the console (if user prefs say so)\r
- // the file handle is g_qeglobals.hLogFile\r
- // the log file is erased\r
- Str name;\r
- name = g_strTempPath; \r
- name += "radiant.log";\r
-#if defined (__linux__) || defined (__APPLE__)\r
- g_qeglobals.hLogFile = open( name.GetBuffer(), O_TRUNC | O_CREAT | O_WRONLY, S_IREAD | S_IWRITE );\r
-#endif\r
-#ifdef _WIN32\r
- g_qeglobals.hLogFile = _open( name.GetBuffer(), _O_TRUNC | _O_CREAT | _O_WRONLY, _S_IREAD | _S_IWRITE );\r
-#endif\r
- if (g_qeglobals.hLogFile)\r
- {\r
- Sys_Printf("Started logging to %s\n", name.GetBuffer());\r
- time_t localtime;\r
- time(&localtime);\r
- Sys_Printf("Today is: %s", ctime(&localtime));\r
- Sys_Printf("This is GtkRadiant '" RADIANT_VERSION "' compiled " __DATE__ "\n");\r
- Sys_Printf(RADIANT_ABOUTMSG "\n");\r
- }\r
- else\r
- gtk_MessageBox (NULL, "Failed to create log file, check write permissions in Radiant directory.\n",\r
- "Console logging", MB_OK );\r
- }\r
- else if (!g_PrefsDlg.mGamesDialog.m_bLogConsole && g_qeglobals.hLogFile)\r
- {\r
- // settings say we should not be logging but still we have an active logfile .. close it\r
- time_t localtime;\r
- time(&localtime);\r
- Sys_Printf("Closing log file at %s\n", ctime(&localtime));\r
- #ifdef _WIN32\r
- _close( g_qeglobals.hLogFile );\r
- #endif\r
- #if defined (__linux__) || defined (__APPLE__)\r
- close( g_qeglobals.hLogFile );\r
- #endif\r
- g_qeglobals.hLogFile = 0;\r
- }\r
-}\r
-\r
-void Sys_ClearPrintf (void)\r
-{\r
- GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit));\r
- gtk_text_buffer_set_text(buffer, "", -1);\r
-}\r
-\r
-// used to be around 32000, that should be way enough already\r
-#define BUFFER_SIZE 4096\r
-\r
-extern "C" void Sys_FPrintf_VA (int level, const char *text, va_list args)\r
-{\r
- char buf[BUFFER_SIZE];\r
- \r
- buf[0] = 0;\r
- vsnprintf(buf, BUFFER_SIZE, text, args);\r
- buf[BUFFER_SIZE-1] = 0;\r
- const unsigned int length = strlen(buf);\r
-\r
- if (g_qeglobals.hLogFile)\r
- {\r
-#ifdef _WIN32\r
- _write(g_qeglobals.hLogFile, buf,length);\r
- _commit(g_qeglobals.hLogFile);\r
-#endif\r
-#if defined (__linux__) || defined (__APPLE__)\r
- write(g_qeglobals.hLogFile, buf, length);\r
-#endif\r
- }\r
-\r
- if (level != SYS_NOCON)\r
- {\r
- // TTimo: FIXME: killed the console to avoid GDI leak fuckup\r
- if (g_qeglobals_gui.d_edit != NULL)\r
- {\r
- GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit));\r
- \r
- GtkTextIter iter;\r
- gtk_text_buffer_get_end_iter(buffer, &iter);\r
-\r
- static GtkTextMark* end = gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE);\r
-\r
- const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 };\r
- const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 };\r
- const GdkColor black = { 0, 0x0000, 0x0000, 0x0000 };\r
-\r
- static GtkTextTag* error_tag = gtk_text_buffer_create_tag (buffer, "red_foreground", "foreground-gdk", &red, NULL);\r
- static GtkTextTag* warning_tag = gtk_text_buffer_create_tag (buffer, "yellow_foreground", "foreground-gdk", &yellow, NULL);\r
- static GtkTextTag* standard_tag = gtk_text_buffer_create_tag (buffer, "black_foreground", "foreground-gdk", &black, NULL);\r
- GtkTextTag* tag;\r
- switch (level)\r
- {\r
- case SYS_WRN:\r
- tag = warning_tag;\r
- break;\r
- case SYS_ERR:\r
- tag = error_tag;\r
- break;\r
- case SYS_STD:\r
- case SYS_VRB:\r
- default:\r
- tag = standard_tag;\r
- break;\r
- }\r
- gtk_text_buffer_insert_with_tags(buffer, &iter, buf, length, tag, NULL);\r
-\r
- gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit), end);\r
-\r
- // update console widget immediatly if we're doing something time-consuming\r
- if( !g_bScreenUpdates && GTK_WIDGET_REALIZED( g_qeglobals_gui.d_edit ) )\r
- {\r
- gtk_grab_add(g_qeglobals_gui.d_edit);\r
-\r
- while(gtk_events_pending())\r
- gtk_main_iteration();\r
-\r
- gtk_grab_remove(g_qeglobals_gui.d_edit);\r
- }\r
- }\r
- } \r
-}\r
-\r
-// NOTE: this is the handler sent to synapse\r
-// must match PFN_SYN_PRINTF_VA\r
-extern "C" void Sys_Printf_VA (const char *text, va_list args)\r
-{\r
- Sys_FPrintf_VA (SYS_STD, text, args);\r
-}\r
-\r
-extern "C" void Sys_Printf (const char *text, ...)\r
-{\r
- va_list args;\r
-\r
- va_start (args, text);\r
- Sys_FPrintf_VA (SYS_STD, text, args);\r
- va_end (args);\r
-}\r
-\r
-extern "C" void Sys_FPrintf (int level, const char *text, ...)\r
-{\r
- va_list args;\r
-\r
- va_start (args, text);\r
- Sys_FPrintf_VA (level, text, args);\r
- va_end (args);\r
-}\r
+/*
+ Copyright (C) 1999-2006 Id Software, Inc. and contributors.
+ For a list of contributors, see the accompanying CONTRIBUTORS file.
+
+ This file is part of GtkRadiant.
+
+ GtkRadiant is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ GtkRadiant is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with GtkRadiant; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ The following source code is licensed by Id Software and subject to the terms of
+ its LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with
+ GtkRadiant. If you did not receive a LIMITED USE SOFTWARE LICENSE AGREEMENT,
+ please contact Id Software immediately at info@idsoftware.com.
+ */
+
+//
+// Linux stuff
+//
+// Leonardo Zide (leo@lokigames.com)
+//
+
+#include "qe3.h"
+
+#include <gtk/gtk.h>
+
+#include "debugging/debugging.h"
+
+#include "ifilesystem.h"
+//#include "imap.h"
+
+#include <map>
+
+#include <uilib/uilib.h>
+
+#include "stream/textfilestream.h"
+#include "cmdlib.h"
+#include "stream/stringstream.h"
+#include "os/path.h"
+#include "scenelib.h"
+
+#include "gtkutil/messagebox.h"
+#include "error.h"
+#include "map.h"
+#include "build.h"
+#include "points.h"
+#include "camwindow.h"
+#include "mainframe.h"
+#include "preferences.h"
+#include "watchbsp.h"
+#include "autosave.h"
+#include "convert.h"
+
+QEGlobals_t g_qeglobals;
+
+
+#if defined( WIN32 )
+#define PATH_MAX 260
+#endif
+
+
+void QE_InitVFS(){
+ // VFS initialization -----------------------
+ // we will call GlobalFileSystem().initDirectory, giving the directories to look in (for files in pk3's and for standalone files)
+ // we need to call in order, the mod ones first, then the base ones .. they will be searched in this order
+ // *nix systems have a dual filesystem in ~/.q3a, which is searched first .. so we need to add that too
+
+ const char* gamename = gamename_get();
+ const char* basegame = basegame_get();
+ const char* userRoot = g_qeglobals.m_userEnginePath.c_str();
+ const char* globalRoot = EnginePath_get();
+
+ // if we have a mod dir
+ if ( !string_equal( gamename, basegame ) ) {
+ // ~/.<gameprefix>/<fs_game>
+ if ( userRoot ) {
+ StringOutputStream userGamePath( 256 );
+ userGamePath << userRoot << gamename << '/';
+ GlobalFileSystem().initDirectory( userGamePath.c_str() );
+ }
+
+ // <fs_basepath>/<fs_game>
+ {
+ StringOutputStream globalGamePath( 256 );
+ globalGamePath << globalRoot << gamename << '/';
+ GlobalFileSystem().initDirectory( globalGamePath.c_str() );
+ }
+ }
+
+ // ~/.<gameprefix>/<fs_main>
+ if ( userRoot ) {
+ StringOutputStream userBasePath( 256 );
+ userBasePath << userRoot << basegame << '/';
+ GlobalFileSystem().initDirectory( userBasePath.c_str() );
+ }
+
+ // <fs_basepath>/<fs_main>
+ {
+ StringOutputStream globalBasePath( 256 );
+ globalBasePath << globalRoot << basegame << '/';
+ GlobalFileSystem().initDirectory( globalBasePath.c_str() );
+ }
+}
+
+int g_numbrushes = 0;
+int g_numentities = 0;
+
+void QE_UpdateStatusBar(){
+ char buffer[128];
+ sprintf( buffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities );
+ g_pParentWnd->SetStatusText( g_pParentWnd->m_brushcount_status, buffer );
+}
+
+SimpleCounter g_brushCount;
+
+void QE_brushCountChanged(){
+ g_numbrushes = int(g_brushCount.get() );
+ QE_UpdateStatusBar();
+}
+
+SimpleCounter g_entityCount;
+
+void QE_entityCountChanged(){
+ g_numentities = int(g_entityCount.get() );
+ QE_UpdateStatusBar();
+}
+
+bool ConfirmModified( const char* title ){
+ if ( !Map_Modified( g_map ) ) {
+ return true;
+ }
+
+ auto result = MainFrame_getWindow().alert( "The current map has changed since it was last saved.\nDo you want to save the current map before continuing?", title, ui::alert_type::YESNOCANCEL, ui::alert_icon::Question );
+ if ( result == ui::alert_response::CANCEL ) {
+ return false;
+ }
+ if ( result == ui::alert_response::YES ) {
+ if ( Map_Unnamed( g_map ) ) {
+ return Map_SaveAs();
+ }
+ else
+ {
+ return Map_Save();
+ }
+ }
+ return true;
+}
+
+void bsp_init(){
+ build_set_variable( "RadiantPath", AppPath_get() );
+ build_set_variable( "ExecutableType", RADIANT_EXECUTABLE );
+ build_set_variable( "EnginePath", EnginePath_get() );
+ build_set_variable( "UserEnginePath", g_qeglobals.m_userEnginePath.c_str() );
+ build_set_variable( "MonitorAddress", ( g_WatchBSP_Enabled ) ? "127.0.0.1:39000" : "" );
+ build_set_variable( "GameName", gamename_get() );
+
+ const char* mapname = Map_Name( g_map );
+ StringOutputStream name( 256 );
+ name << StringRange( mapname, path_get_filename_base_end( mapname ) ) << ".bsp";
+
+ build_set_variable( "MapFile", mapname );
+ build_set_variable( "BspFile", name.c_str() );
+}
+
+void bsp_shutdown(){
+ build_clear_variables();
+}
+
+class ArrayCommandListener : public CommandListener
+{
+GPtrArray* m_array;
+public:
+ArrayCommandListener(){
+ m_array = g_ptr_array_new();
+}
+~ArrayCommandListener(){
+ g_ptr_array_free( m_array, TRUE );
+}
+
+void execute( const char* command ){
+ g_ptr_array_add( m_array, g_strdup( command ) );
+}
+
+GPtrArray* array() const {
+ return m_array;
+}
+};
+
+class BatchCommandListener : public CommandListener
+{
+TextOutputStream& m_file;
+std::size_t m_commandCount;
+const char* m_outputRedirect;
+public:
+BatchCommandListener( TextOutputStream& file, const char* outputRedirect ) : m_file( file ), m_commandCount( 0 ), m_outputRedirect( outputRedirect ){
+}
+
+void execute( const char* command ){
+ m_file << command;
+ if ( m_commandCount == 0 ) {
+ m_file << " > ";
+ }
+ else
+ {
+ m_file << " >> ";
+ }
+ m_file << "\"" << m_outputRedirect << "\"";
+ m_file << "\n";
+ ++m_commandCount;
+}
+};
+
+bool Region_cameraValid(){
+ Vector3 vOrig( vector3_snapped( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) ) );
+
+ for ( int i = 0 ; i < 3 ; i++ )
+ {
+ if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+void RunBSP( const char* name ){
+ // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=503
+ // make sure we don't attempt to region compile a map with the camera outside the region
+ if ( region_active && !Region_cameraValid() ) {
+ globalErrorStream() << "The camera must be in the region to start a region compile.\n";
+ return;
+ }
+
+ SaveMap();
+
+ if ( Map_Unnamed( g_map ) ) {
+ globalOutputStream() << "build cancelled\n";
+ return;
+ }
+
+ if ( g_SnapShots_Enabled && !Map_Unnamed( g_map ) && Map_Modified( g_map ) ) {
+ Map_Snapshot();
+ }
+
+ if ( region_active ) {
+ const char* mapname = Map_Name( g_map );
+ StringOutputStream name( 256 );
+ name << StringRange( mapname, path_get_filename_base_end( mapname ) ) << ".reg";
+ Map_SaveRegion( name.c_str() );
+ }
+
+ Pointfile_Delete();
+
+ bsp_init();
+
+ if ( g_WatchBSP_Enabled ) {
+ ArrayCommandListener listener;
+ build_run( name, listener );
+ // grab the file name for engine running
+ const char* fullname = Map_Name( g_map );
+ StringOutputStream bspname( 64 );
+ bspname << StringRange( path_get_filename_start( fullname ), path_get_filename_base_end( fullname ) );
+ BuildMonitor_Run( listener.array(), bspname.c_str() );
+ }
+ else
+ {
+ char junkpath[PATH_MAX];
+ strcpy( junkpath, SettingsPath_get() );
+ strcat( junkpath, "junk.txt" );
+
+ char batpath[PATH_MAX];
+#if defined( POSIX )
+ strcpy( batpath, SettingsPath_get() );
+ strcat( batpath, "qe3bsp.sh" );
+#elif defined( WIN32 )
+ strcpy( batpath, SettingsPath_get() );
+ strcat( batpath, "qe3bsp.bat" );
+#else
+#error "unsupported platform"
+#endif
+ bool written = false;
+ {
+ TextFileOutputStream batchFile( batpath );
+ if ( !batchFile.failed() ) {
+#if defined ( POSIX )
+ batchFile << "#!/bin/sh \n\n";
+#endif
+ BatchCommandListener listener( batchFile, junkpath );
+ build_run( name, listener );
+ written = true;
+ }
+ }
+ if ( written ) {
+#if defined ( POSIX )
+ chmod( batpath, 0744 );
+#endif
+ globalOutputStream() << "Writing the compile script to '" << batpath << "'\n";
+ globalOutputStream() << "The build output will be saved in '" << junkpath << "'\n";
+ Q_Exec( batpath, NULL, NULL, true, false );
+ }
+ }
+
+ bsp_shutdown();
+}
+
+// =============================================================================
+// Sys_ functions
+
+void Sys_SetTitle( const char *text, bool modified ){
+ StringOutputStream title;
+ title << text;
+
+ if ( modified ) {
+ title << " *";
+ }
+
+ gtk_window_set_title(MainFrame_getWindow(), title.c_str() );
+}
+
+bool g_bWaitCursor = false;
+
+void Sys_BeginWait( void ){
+ ScreenUpdates_Disable( "Processing...", "Please Wait" );
+ GdkCursor *cursor = gdk_cursor_new( GDK_WATCH );
+ gdk_window_set_cursor( gtk_widget_get_window(MainFrame_getWindow()), cursor );
+ gdk_cursor_unref( cursor );
+ g_bWaitCursor = true;
+}
+
+void Sys_EndWait( void ){
+ ScreenUpdates_Enable();
+ gdk_window_set_cursor(gtk_widget_get_window(MainFrame_getWindow()), 0 );
+ g_bWaitCursor = false;
+}
+
+void Sys_Beep( void ){
+ gdk_beep();
+}