/* Copyright (C) 1999-2007 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 */ // // Linux stuff // // Leonardo Zide (leo@lokigames.com) // #include "stdafx.h" #include #include #include "gtkmisc.h" #include #if defined (__linux__) || defined (__APPLE__) #include #include #include #include #endif // for the logging part #include #include QEGlobals_t g_qeglobals; QEGlobals_GUI_t g_qeglobals_gui; // leo: Track memory allocations for debugging // NOTE TTimo this was never used and probably not relevant // there are tools to do that #ifdef MEM_DEBUG static GList *memblocks; void* debug_malloc (size_t size, const char* file, int line) { void *buf = g_malloc (size + 8); *((const char**)buf) = file; buf = (char*)buf + 4; *((int*)buf) = line; buf = (char*)buf + 4; memblocks = g_list_append (memblocks, buf); return buf; } void debug_free (void *buf, const char* file, int line) { const char *f; int l; if (g_list_find (memblocks, buf)) { memblocks = g_list_remove (memblocks, buf); buf = (char*)buf - 4; l = *((int*)buf); buf = (char*)buf - 4; f = *((const char**)buf); Sys_FPrintf (SYS_DBG, "free: %s %d", file, line); Sys_FPrintf (SYS_DBG, " allocated: %s %d\n", f, l); g_free (buf); } // else // free (buf); // from qmalloc, will leak unless we add this same hack to cmdlib } #endif vec_t Rad_rint (vec_t in) { if (g_PrefsDlg.m_bNoClamp) return in; else return (float)floor (in + 0.5); } void WINAPI QE_CheckOpenGLForErrors(void) { char strMsg[1024]; int i = qglGetError(); if (i != GL_NO_ERROR) { if (i == GL_OUT_OF_MEMORY) { sprintf(strMsg, "OpenGL out of memory error %s\nDo you wish to save before exiting?", qgluErrorString((GLenum)i)); if (gtk_MessageBox(g_pParentWnd->m_pWidget, strMsg, "Radiant Error", MB_YESNO) == IDYES) { Map_SaveFile(NULL, false); } _exit(1); } else { Sys_Printf ("Warning: OpenGL Error %s\n", qgluErrorString((GLenum)i)); } } } // NOTE: don't this function, use VFS instead char *ExpandReletivePath (char *p) { static char temp[1024]; const char *base; if (!p || !p[0]) return NULL; if (p[0] == '/' || p[0] == '\\') return p; base = ValueForKey(g_qeglobals.d_project_entity, "basepath"); sprintf (temp, "%s/%s", base, p); return temp; } char *copystring (char *s) { char *b; b = (char*)malloc(strlen(s)+1); strcpy (b,s); return b; } bool DoesFileExist(const char* pBuff, long& lSize) { FileStream file; if (file.Open(pBuff, "r")) { lSize += file.GetLength(); file.Close(); return true; } return false; } void Map_Snapshot() { CString strMsg; // I hope the modified flag is kept correctly up to date if (!modified) return; // we need to do the following // 1. make sure the snapshot directory exists (create it if it doesn't) // 2. find out what the lastest save is based on number // 3. inc that and save the map CString strOrgPath, strOrgFile; ExtractPath_and_Filename(currentmap, strOrgPath, strOrgFile); AddSlash(strOrgPath); strOrgPath += "snapshots"; bool bGo = true; struct stat Stat; if (stat(strOrgPath, &Stat) == -1) { #ifdef _WIN32 bGo = (_mkdir(strOrgPath) != -1); #endif #if defined (__linux__) || defined (__APPLE__) bGo = (mkdir(strOrgPath,0755) != -1); #endif } AddSlash(strOrgPath); if (bGo) { int nCount = 0; long lSize = 0; CString strNewPath; strNewPath = strOrgPath; strNewPath += strOrgFile; CString strFile; while (bGo) { char buf[PATH_MAX]; sprintf( buf, "%s.%i", strNewPath.GetBuffer(), nCount ); strFile = buf; bGo = DoesFileExist(strFile, lSize); nCount++; } // strFile has the next available slot Map_SaveFile(strFile, false); // it is still a modified map (we enter this only if this is a modified map) Sys_SetTitle (currentmap); Sys_MarkMapModified(); if (lSize > 12 * 1024 * 1024) // total size of saves > 4 mb { Sys_Printf("The snapshot files in %s total more than 4 megabytes. You might consider cleaning up.", strOrgPath.GetBuffer()); } } else { strMsg.Format("Snapshot save failed.. unabled to create directory\n%s", strOrgPath.GetBuffer()); gtk_MessageBox(g_pParentWnd->m_pWidget, strMsg); } strOrgPath = ""; strOrgFile = ""; } /* =============== QE_CheckAutoSave If five minutes have passed since making a change and the map hasn't been saved, save it out. =============== */ void QE_CheckAutoSave( void ) { static time_t s_start; time_t now; time (&now); if (modified != 1 || !s_start) { s_start = now; return; } if ((now - s_start) > (60 * g_PrefsDlg.m_nAutoSave)) { if (g_PrefsDlg.m_bAutoSave) { CString strMsg; strMsg = g_PrefsDlg.m_bSnapShots ? "Autosaving snapshot..." : "Autosaving..."; Sys_Printf(strMsg); Sys_Printf("\n"); Sys_Status (strMsg,0); // only snapshot if not working on a default map if (g_PrefsDlg.m_bSnapShots && stricmp(currentmap, "unnamed.map") != 0) { Map_Snapshot(); } else { Map_SaveFile (ValueForKey(g_qeglobals.d_project_entity, "autosave"), false); } Sys_Status ("Autosaving...Saved.", 0 ); modified = 2; } else { Sys_Printf ("Autosave skipped...\n"); Sys_Status ("Autosave skipped...", 0 ); } s_start = now; } } // NOTE TTimo we don't like that BuildShortPathName too much // the VFS provides a vfsCleanFileName which should perform the cleanup tasks // in the long run I'd like to completely get rid of this // used to be disabled, but caused problems // can't work with long win32 names until the BSP commands are not working differently #ifdef _WIN32 int BuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen) { char *pFile = NULL; int nResult = GetFullPathName(pPath, nBufferLen, pBuffer, &pFile); nResult = GetShortPathName(pPath, pBuffer, nBufferLen); if (nResult == 0) strcpy(pBuffer, pPath); // Use long filename return nResult; } #endif #if defined (__linux__) || defined (__APPLE__) int BuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen) { // remove /../ from directories const char *scr = pPath; char *dst = pBuffer; for (int i = 0; (i < nBufferLen) && (*scr != 0); i++) { if (*scr == '/' && *(scr+1) == '.' && *(scr+2) == '.') { scr += 3; while (dst != pBuffer && *(--dst) != '/') { i--; } } *dst = *scr; scr++; dst++; } *dst = 0; return strlen (pBuffer); } #endif /* const char *g_pPathFixups[]= { "basepath", "autosave", }; const int g_nPathFixupCount = sizeof(g_pPathFixups) / sizeof(const char*); void QE_CheckProjectEntity() { char *pFile; char pBuff[PATH_MAX]; char pNewPath[PATH_MAX]; for (int i = 0; i < g_nPathFixupCount; i++) { char *pPath = ValueForKey (g_qeglobals.d_project_entity, g_pPathFixups[i]); strcpy (pNewPath, pPath); if (pPath[0] != '\\' && pPath[0] != '/') if (GetFullPathName(pPath, PATH_MAX, pBuff, &pFile)) strcpy (pNewPath, pBuff); BuildShortPathName (pNewPath, pBuff, PATH_MAX); // check it's not ending with a filename seperator if (pBuff[strlen(pBuff)-1] == '/' || pBuff[strlen(pBuff)-1] == '\\') { Sys_FPrintf(SYS_WRN, "WARNING: \"%s\" path in the project file has an ending file seperator, fixing.\n", g_pPathFixups[i]); pBuff[strlen(pBuff)-1]=0; } SetKeyValue(g_qeglobals.d_project_entity, g_pPathFixups[i], pBuff); } } */ void HandleXMLError( void* ctxt, const char* text, ... ) { va_list argptr; static char buf[32768]; va_start (argptr,text); vsprintf (buf, text, argptr); Sys_FPrintf (SYS_ERR, "XML %s\n", buf); va_end (argptr); } #define DTD_BUFFER_LENGTH 1024 xmlDocPtr ParseXMLStream(IDataStream *stream, bool validate = false) { xmlDocPtr doc = NULL; bool wellFormed = false, valid = false; int res, size = 1024; char chars[1024]; xmlParserCtxtPtr ctxt; //if(validate) // xmlDoValidityCheckingDefaultValue = 1; //else xmlDoValidityCheckingDefaultValue = 0; xmlSetGenericErrorFunc(NULL, HandleXMLError); // SPoG // HACK: use AppPath to resolve DTD location // do a buffer-safe string copy and concatenate int i; char* w; const char* r; char buf[DTD_BUFFER_LENGTH]; w = buf; i = 0; // copy //assert(g_strAppPath.GetBuffer() != NULL); for(r = g_strAppPath.GetBuffer(); iRead(chars, 4); if (res > 0) { ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, buf); while ((res = stream->Read(chars, size)) > 0) { xmlParseChunk(ctxt, chars, res, 0); } xmlParseChunk(ctxt, chars, 0, 1); doc = ctxt->myDoc; wellFormed = (ctxt->wellFormed == 1); valid = (ctxt->valid == 1); xmlFreeParserCtxt(ctxt); } if(wellFormed && (!validate || (validate && valid))) return doc; if(doc != NULL) xmlFreeDoc(doc); return NULL; } xmlDocPtr ParseXMLFile(const char* filename, bool validate = false) { FileStream stream; if (stream.Open(filename, "r")) return ParseXMLStream(&stream, validate); Sys_FPrintf(SYS_ERR, "Failed to open file: %s\n",filename); return NULL; } // copy a string r to a buffer w // replace $string as appropriate void ReplaceTemplates(char* w, const char* r) { const char *p; const char *__ENGINEPATH = "TEMPLATEenginepath"; const char *__USERHOMEPATH = "TEMPLATEuserhomepath"; const char *__TOOLSPATH = "TEMPLATEtoolspath"; const char *__BASEDIR = "TEMPLATEbasedir"; const char *__APPPATH = "TEMPLATEapppath"; // iterate through string r while(*r!='\0') { // check for special character if(*r=='$') { if(strncmp(r+1, __ENGINEPATH, strlen(__ENGINEPATH)) == 0) { r+=strlen(__ENGINEPATH)+1; p = g_pGameDescription->mEnginePath.GetBuffer(); } else if(strncmp(r+1, __USERHOMEPATH, strlen(__USERHOMEPATH)) == 0) { r+=strlen(__USERHOMEPATH)+1; p = g_qeglobals.m_strHomeGame.GetBuffer(); } else if(strncmp(r+1, __BASEDIR, strlen(__BASEDIR)) == 0) { r+=strlen(__BASEDIR)+1; p = g_pGameDescription->mBaseGame; } else if(strncmp(r+1, __TOOLSPATH, strlen(__TOOLSPATH)) == 0) { r+=strlen(__TOOLSPATH)+1; p = g_strGameToolsPath.GetBuffer(); } else if(strncmp(r+1, __APPPATH, strlen(__APPPATH)) == 0) { r+=strlen(__APPPATH)+1; p = g_strAppPath.GetBuffer(); } else { r++; p = "$"; } while(*p!='\0') *w++ = *p++; } else *w++ = *r++; } *w = '\0'; } /* =========== QE_LoadProject TODO TODO TODO (don't think this got fully merged in) TTimo: added project file "version", version 2 adds '#' chars to the BSP command strings version 3 was .. I don't remember .. version 4 adds q3map2 commands TTimo: when QE_LoadProject is called, the prefs are updated with path to the latest project and saved on disk =========== */ /*\todo decide on a sensible location/name for project files.*/ bool QE_LoadProject (const char *projectfile) { char buf[1024]; xmlDocPtr doc; xmlNodePtr node, project; Sys_Printf("Loading project file: \"%s\"\n", projectfile); doc = ParseXMLFile(projectfile, true); if(doc == NULL) return false; node=doc->children; while(node != NULL && node->type != XML_DTD_NODE) node=node->next; if(node == NULL || strcmp((char*)node->name, "project") != 0) { Sys_FPrintf(SYS_ERR, "ERROR: invalid file type\n"); return false; } while(node->type != XML_ELEMENT_NODE) node=node->next; // project = node; if(g_qeglobals.d_project_entity != NULL) Entity_Free(g_qeglobals.d_project_entity); g_qeglobals.d_project_entity = Entity_Alloc(); for(node = project->children; node != NULL; node=node->next) { if(node->type != XML_ELEMENT_NODE) continue; // ReplaceTemplates(buf, (char*)node->properties->next->children->content); SetKeyValue(g_qeglobals.d_project_entity, (char*)node->properties->children->content, buf); } xmlFreeDoc(doc); // project file version checking // add a version checking to avoid people loading later versions of the project file and bitching int ver = IntForKey( g_qeglobals.d_project_entity, "version" ); if (ver > PROJECT_VERSION) { char strMsg[1024]; sprintf (strMsg, "This is a version %d project file. This build only supports <=%d project files.\n" "Please choose another project file or upgrade your version of Radiant.", ver, PROJECT_VERSION); gtk_MessageBox (g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK); // set the project file to nothing so we are sure we'll ask next time? g_PrefsDlg.m_strLastProject = ""; g_PrefsDlg.SavePrefs(); return false; } // set here some default project settings you need if ( strlen( ValueForKey( g_qeglobals.d_project_entity, "brush_primit" ) ) == 0 ) { SetKeyValue( g_qeglobals.d_project_entity, "brush_primit", "0" ); } g_qeglobals.m_bBrushPrimitMode = IntForKey( g_qeglobals.d_project_entity, "brush_primit" ); g_qeglobals.m_strHomeMaps = g_qeglobals.m_strHomeGame; const char* str = ValueForKey(g_qeglobals.d_project_entity, "gamename"); if(str[0] == '\0') str = g_pGameDescription->mBaseGame.GetBuffer(); g_qeglobals.m_strHomeMaps += str; g_qeglobals.m_strHomeMaps += '/'; // don't forget to create the dirs Q_mkdir(g_qeglobals.m_strHomeGame.GetBuffer(), 0775); Q_mkdir(g_qeglobals.m_strHomeMaps.GetBuffer(), 0775); // usefull for the log file and debuggin fucked up configurations from users: // output the basic information of the .qe4 project file // SPoG // all these paths should be unix format, with a trailing slash at the end // if not.. to debug, check that the project file paths are set up correctly Sys_Printf("basepath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "basepath") ); Sys_Printf("entitypath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "entitypath" ) ); // check whether user_project key exists.. // if not, save the current project under a new name if (ValueForKey(g_qeglobals.d_project_entity, "user_project")[0] == '\0') { Sys_Printf("Loaded a template project file\n"); // create the user_project key SetKeyValue( g_qeglobals.d_project_entity, "user_project", "1" ); if (IntForKey( g_qeglobals.d_project_entity, "version" ) != PROJECT_VERSION) { char strMsg[2048]; sprintf(strMsg, "The template project '%s' has version %d. The editor binary is configured for version %d.\n" "This indicates a problem in your setup.\n" "I will keep going with this project till you fix this", projectfile, IntForKey( g_qeglobals.d_project_entity, "version" ), PROJECT_VERSION); gtk_MessageBox (g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK); } // create the writable project file path strcpy(buf, g_qeglobals.m_strHomeGame.GetBuffer()); strcat(buf, g_pGameDescription->mBaseGame.GetBuffer()); strcat(buf, "/scripts/"); // while the filename is already in use, increment the number we add to the end int counter = 0; char pUser[PATH_MAX]; while (1) { sprintf( pUser, "%suser%d." PROJECT_FILETYPE, buf, counter ); counter++; if (access( pUser, R_OK) != 0) { // this is the one strcpy( buf, pUser ); break; } } // saving project will cause a save prefs g_PrefsDlg.m_strLastProject = buf; g_PrefsDlg.m_nLastProjectVer = IntForKey( g_qeglobals.d_project_entity, "version" ); QE_SaveProject(buf); } else { // update preferences::LastProject with path of this successfully-loaded project // save preferences Sys_Printf("Setting current project in prefs to \"%s\"\n", g_PrefsDlg.m_strLastProject.GetBuffer() ); g_PrefsDlg.m_strLastProject = projectfile; g_PrefsDlg.SavePrefs(); } return true; } /* =========== QE_SaveProject TTimo: whenever QE_SaveProject is called, prefs are updated and saved with the path to the project =========== */ qboolean QE_SaveProject (const char* filename) { Sys_Printf("Save project file '%s'\n", filename); xmlNodePtr node; xmlDocPtr doc = xmlNewDoc((xmlChar *)"1.0"); // create DTD node xmlCreateIntSubset(doc, (xmlChar *)"project", NULL, (xmlChar *)"project.dtd"); // create project node doc->children->next = xmlNewDocNode(doc, NULL, (xmlChar *)"project", NULL); for(epair_t* epair = g_qeglobals.d_project_entity->epairs; epair != NULL; epair = epair->next) { node = xmlNewChild(doc->children->next, NULL, (xmlChar *)"key", NULL); xmlSetProp(node, (xmlChar*)"name", (xmlChar*)epair->key); xmlSetProp(node, (xmlChar*)"value", (xmlChar*)epair->value); } CreateDirectoryPath(filename); if (xmlSaveFormatFile(filename, doc, 1) != -1) { xmlFreeDoc(doc); Sys_Printf("Setting current project in prefs to \"%s\"\n", filename ); g_PrefsDlg.m_strLastProject = filename; g_PrefsDlg.SavePrefs(); return TRUE; } else { xmlFreeDoc(doc); Sys_FPrintf(SYS_ERR, "failed to save project file: \"%s\"\n", filename); return FALSE; } } /* =========== QE_KeyDown =========== */ #define SPEED_MOVE 32 #define SPEED_TURN 22.5 /* =============== ConnectEntities Sets target / targetname on the two entities selected from the first selected to the secon =============== */ void ConnectEntities (void) { entity_t *e1, *e2; const char *target; char *newtarg = NULL; if (g_qeglobals.d_select_count != 2) { Sys_Status ("Must have two brushes selected", 0); Sys_Beep (); return; } e1 = g_qeglobals.d_select_order[0]->owner; e2 = g_qeglobals.d_select_order[1]->owner; if (e1 == world_entity || e2 == world_entity) { Sys_Status ("Can't connect to the world", 0); Sys_Beep (); return; } if (e1 == e2) { Sys_Status ("Brushes are from same entity", 0); Sys_Beep (); return; } target = ValueForKey (e1, "target"); if (target && target[0]) newtarg = g_strdup(target); else { target = ValueForKey(e2, "targetname"); if(target && target[0]) newtarg = g_strdup(target); else Entity_Connect(e1, e2); } if(newtarg != NULL) { SetKeyValue(e1, "target", newtarg); SetKeyValue(e2, "targetname", newtarg); g_free(newtarg); } Sys_UpdateWindows (W_XY | W_CAMERA); Select_Deselect(); Select_Brush (g_qeglobals.d_select_order[1]); } qboolean QE_SingleBrush (bool bQuiet) { if ( (selected_brushes.next == &selected_brushes) || (selected_brushes.next->next != &selected_brushes) ) { if (!bQuiet) { Sys_Printf ("Error: you must have a single brush selected\n"); } return false; } if (selected_brushes.next->owner->eclass->fixedsize) { if (!bQuiet) { Sys_Printf ("Error: you cannot manipulate fixed size entities\n"); } return false; } return true; } void QE_InitVFS (void) { // VFS initialization ----------------------- // we will call vfsInitDirectory, 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 Str directory,prefabs; // TTimo: let's leave this to HL mode for now if (g_pGameDescription->mGameFile == "hl.game") { // Hydra: we search the "gametools" path first so that we can provide editor // specific pk3's wads and misc files for use by the editor. // the relevant map compiler tools will NOT use this directory, so this helps // to ensure that editor files are not used/required in release versions of maps // it also helps keep your editor files all in once place, with the editor modules, // plugins, scripts and config files. // it also helps when testing maps, as you'll know your files won't/can't be used // by the game engine itself. // directory = g_pGameDescription->mGameToolsPath; vfsInitDirectory(directory.GetBuffer()); } // NOTE TTimo about the mymkdir calls .. this is a bit dirty, but a safe thing on *nix // if we have a mod dir if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0') { #if defined (__linux__) || defined (__APPLE__) // ~/./ directory = g_qeglobals.m_strHomeGame.GetBuffer(); Q_mkdir (directory.GetBuffer (), 0775); directory += ValueForKey(g_qeglobals.d_project_entity, "gamename"); Q_mkdir (directory.GetBuffer (), 0775); vfsInitDirectory(directory.GetBuffer()); AddSlash (directory); prefabs = directory; // also create the maps dir, it will be used as prompt for load/save directory += "/maps"; Q_mkdir (directory, 0775); // and the prefabs dir prefabs += "/prefabs"; Q_mkdir (prefabs, 0775); #endif // / directory = g_pGameDescription->mEnginePath; directory += ValueForKey(g_qeglobals.d_project_entity, "gamename"); Q_mkdir (directory.GetBuffer (), 0775); vfsInitDirectory(directory.GetBuffer()); AddSlash(directory); prefabs = directory; // also create the maps dir, it will be used as prompt for load/save directory += "/maps"; Q_mkdir (directory.GetBuffer (), 0775); // and the prefabs dir prefabs += "/prefabs"; Q_mkdir (prefabs, 0775); } #if defined (__linux__) || defined (__APPLE__) // ~/./ directory = g_qeglobals.m_strHomeGame.GetBuffer(); directory += g_pGameDescription->mBaseGame; vfsInitDirectory (directory.GetBuffer ()); #endif // / directory = g_pGameDescription->mEnginePath; directory += g_pGameDescription->mBaseGame; vfsInitDirectory(directory.GetBuffer()); } void QE_Init (void) { /* ** initialize variables */ g_qeglobals.d_gridsize = 8; g_qeglobals.d_showgrid = true; QE_InitVFS(); Eclass_Init(); FillClassList(); // list in entity window Map_Init(); FillTextureMenu(); FillBSPMenu(); /* ** other stuff */ Z_Init (); } void WINAPI QE_ConvertDOSToUnixName( char *dst, const char *src ) { while ( *src ) { if ( *src == '\\' ) *dst = '/'; else *dst = *src; dst++; src++; } *dst = 0; } int g_numbrushes, g_numentities; void QE_CountBrushesAndUpdateStatusBar( void ) { static int s_lastbrushcount, s_lastentitycount; static qboolean s_didonce; //entity_t *e; brush_t *b, *next; g_numbrushes = 0; g_numentities = 0; if ( active_brushes.next != NULL ) { for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; b=next) { next = b->next; if (b->brush_faces ) { if ( !b->owner->eclass->fixedsize) g_numbrushes++; else g_numentities++; } } } /* if ( entities.next != NULL ) { for ( e = entities.next ; e != &entities && g_numentities != MAX_MAP_ENTITIES ; e = e->next) { g_numentities++; } } */ if ( ( ( g_numbrushes != s_lastbrushcount ) || ( g_numentities != s_lastentitycount ) ) || ( !s_didonce ) ) { Sys_UpdateStatusBar(); s_lastbrushcount = g_numbrushes; s_lastentitycount = g_numentities; s_didonce = true; } } char com_token[1024]; qboolean com_eof; /* ================ I_FloatTime ================ */ double I_FloatTime (void) { time_t t; time (&t); return t; #if 0 // more precise, less portable struct timeval tp; struct timezone tzp; static int secbase; gettimeofday(&tp, &tzp); if (!secbase) { secbase = tp.tv_sec; return tp.tv_usec/1000000.0; } return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0; #endif } /* ============== COM_Parse Parse a token out of a string ============== */ char *COM_Parse (char *data) { int c; int len; len = 0; com_token[0] = 0; if (!data) return NULL; // skip whitespace skipwhite: while ( (c = *data) <= ' ') { if (c == 0) { com_eof = true; return NULL; // end of file; } data++; } // skip // comments if (c=='/' && data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } // handle quoted strings specially if (c == '\"') { data++; do { c = *data++; if (c=='\"') { com_token[len] = 0; return data; } com_token[len] = c; len++; } while (1); } // parse single characters if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') { com_token[len] = c; len++; com_token[len] = 0; return data+1; } // parse a regular word do { com_token[len] = c; data++; len++; c = *data; if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':') break; } while (c>32); com_token[len] = 0; return data; } char* Get_COM_Token() { return com_token; } /* ============================================================================= MISC FUNCTIONS ============================================================================= */ int argc; char *argv[MAX_NUM_ARGVS]; /* ============ ParseCommandLine ============ */ void ParseCommandLine (char *lpCmdLine) { argc = 1; argv[0] = "programname"; while (*lpCmdLine && (argc < MAX_NUM_ARGVS)) { while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126))) lpCmdLine++; if (*lpCmdLine) { argv[argc] = lpCmdLine; argc++; while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126))) lpCmdLine++; if (*lpCmdLine) { *lpCmdLine = 0; lpCmdLine++; } } } } /* ================= CheckParm Checks for the given parameter in the program's command line arguments Returns the argument number (1 to argc-1) or 0 if not present ================= */ int CheckParm (const char *check) { int i; for (i = 1;i= '0' && *str <= '9') num += *str-'0'; else if (*str >= 'a' && *str <= 'f') num += 10 + *str-'a'; else if (*str >= 'A' && *str <= 'F') num += 10 + *str-'A'; else Error ("Bad hex number: %s",hex); str++; } return num; } int ParseNum (const char *str) { if (str[0] == '$') return ParseHex (str+1); if (str[0] == '0' && str[1] == 'x') return ParseHex (str+2); return atol (str); } // BSP frontend plugin // global flag for BSP frontend plugin is g_qeglobals.bBSPFrontendPlugin _QERPlugBSPFrontendTable g_BSPFrontendTable; // ============================================================================= // Sys_ functions bool Sys_AltDown () { #ifdef _WIN32 return (GetKeyState(VK_MENU) & 0x8000) != 0; #endif #if defined (__linux__) || defined (__APPLE__) char keys[32]; int x; XQueryKeymap(GDK_DISPLAY(), keys); x = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L); if (keys[x/8] & (1 << (x % 8))) return true; x = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_R); if (keys[x/8] & (1 << (x % 8))) return true; return false; #endif } bool Sys_ShiftDown () { #ifdef _WIN32 return (GetKeyState(VK_SHIFT) & 0x8000) != 0; #endif #if defined (__linux__) || defined (__APPLE__) char keys[32]; int x; XQueryKeymap(GDK_DISPLAY(), keys); x = XKeysymToKeycode (GDK_DISPLAY(), XK_Shift_L); if (keys[x/8] & (1 << (x % 8))) return true; x = XKeysymToKeycode (GDK_DISPLAY(), XK_Shift_R); if (keys[x/8] & (1 << (x % 8))) return true; return false; #endif } void Sys_MarkMapModified (void) { char title[PATH_MAX]; if (modified != 1) { modified = true; // mark the map as changed sprintf (title, "%s *", currentmap); QE_ConvertDOSToUnixName( title, title ); Sys_SetTitle (title); } } void Sys_SetTitle (const char *text) { gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_main_window), text); } bool g_bWaitCursor = false; void WINAPI Sys_BeginWait (void) { GdkCursor *cursor = gdk_cursor_new (GDK_WATCH); gdk_window_set_cursor (g_pParentWnd->m_pWidget->window, cursor); gdk_cursor_unref (cursor); g_bWaitCursor = true; } void WINAPI Sys_EndWait (void) { GdkCursor *cursor = gdk_cursor_new (GDK_LEFT_PTR); gdk_window_set_cursor (g_pParentWnd->m_pWidget->window, cursor); gdk_cursor_unref (cursor); g_bWaitCursor = false; } void Sys_GetCursorPos (int *x, int *y) { // FIXME: not multihead safe gdk_window_get_pointer (NULL, x, y, NULL); } void Sys_SetCursorPos (int x, int y) { // NOTE: coordinates are in GDK space, not OS space #ifdef _WIN32 int sys_x = x - g_pParentWnd->GetGDKOffsetX(); int sys_y = y - g_pParentWnd->GetGDKOffsetY(); SetCursorPos (sys_x, sys_y); #endif #if defined (__linux__) || defined (__APPLE__) XWarpPointer (GDK_DISPLAY(), None, GDK_ROOT_WINDOW(), 0, 0, 0, 0, x, y); #endif } void Sys_Beep (void) { #if defined (__linux__) || defined (__APPLE__) gdk_beep (); #else MessageBeep (MB_ICONASTERISK); #endif } double Sys_DoubleTime (void) { return clock()/ 1000.0; } /* =============================================================== STATUS WINDOW =============================================================== */ void Sys_UpdateStatusBar( void ) { extern int g_numbrushes, g_numentities; char numbrushbuffer[100]=""; sprintf( numbrushbuffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities ); g_pParentWnd->SetStatusText(2, numbrushbuffer); //Sys_Status( numbrushbuffer, 2 ); } void Sys_Status(const char *psz, int part ) { g_pParentWnd->SetStatusText (part, psz); } // ============================================================================= // MRU #define MRU_MAX 4 static GtkWidget *MRU_items[MRU_MAX]; static int MRU_used; typedef char MRU_filename_t[PATH_MAX]; MRU_filename_t MRU_filenames[MRU_MAX]; static char* MRU_GetText (int index) { return MRU_filenames[index]; } void buffer_write_escaped_mnemonic(char* buffer, const char* string) { while(*string != '\0') { if(*string == '_') { *buffer++ = '_'; } *buffer++ = *string++; } *buffer = '\0'; } static void MRU_SetText (int index, const char *filename) { strcpy(MRU_filenames[index], filename); char mnemonic[PATH_MAX * 2 + 4]; mnemonic[0] = '_'; sprintf(mnemonic+1, "%d", index+1); mnemonic[2] = '-'; mnemonic[3] = ' '; buffer_write_escaped_mnemonic(mnemonic+4, filename); gtk_label_set_text_with_mnemonic(GTK_LABEL (GTK_BIN (MRU_items[index])->child), mnemonic); } void MRU_Load () { int i = g_PrefsDlg.m_nMRUCount; if(i > 4) i = 4; //FIXME: make this a define for (; i > 0; i--) MRU_AddFile (g_PrefsDlg.m_strMRUFiles[i-1].GetBuffer()); } void MRU_Save () { g_PrefsDlg.m_nMRUCount = MRU_used; for (int i = 0; i < MRU_used; i++) g_PrefsDlg.m_strMRUFiles[i] = MRU_GetText (i); } void MRU_AddWidget (GtkWidget *widget, int pos) { if (pos < MRU_MAX) MRU_items[pos] = widget; } void MRU_AddFile (const char *str) { int i; char* text; // check if file is already in our list for (i = 0; i < MRU_used; i++) { text = MRU_GetText (i); if (strcmp (text, str) == 0) { // reorder menu for (; i > 0; i--) MRU_SetText (i, MRU_GetText (i-1)); MRU_SetText (0, str); return; } } if (MRU_used < MRU_MAX) MRU_used++; // move items down for (i = MRU_used-1; i > 0; i--) MRU_SetText (i, MRU_GetText (i-1)); MRU_SetText (0, str); gtk_widget_set_sensitive (MRU_items[0], TRUE); gtk_widget_show (MRU_items[MRU_used-1]); } void MRU_Activate (int index) { char *text = MRU_GetText (index); if (access (text, R_OK) == 0) { text = strdup (text); MRU_AddFile (text); Map_LoadFile (text); free (text); } else { MRU_used--; for (int i = index; i < MRU_used; i++) MRU_SetText (i, MRU_GetText (i+1)); if (MRU_used == 0) { gtk_label_set_text (GTK_LABEL (GTK_BIN (MRU_items[0])->child), "Recent Files"); gtk_widget_set_sensitive (MRU_items[0], FALSE); } else { gtk_widget_hide (MRU_items[MRU_used]); } } } /* ====================================================================== FILE DIALOGS ====================================================================== */ qboolean ConfirmModified () { if (!modified) return true; if (gtk_MessageBox (g_pParentWnd->m_pWidget, "This will lose changes to the map", "warning", MB_OKCANCEL) == IDCANCEL) return false; return true; } void ProjectDialog () { const char *filename; char buffer[NAME_MAX]; /* * Obtain the system directory name and * store it in buffer. */ strcpy(buffer, g_qeglobals.m_strHomeGame.GetBuffer()); strcat(buffer, g_pGameDescription->mBaseGame.GetBuffer()); strcat (buffer, "/scripts/"); // Display the Open dialog box filename = file_dialog (NULL, TRUE, _("Open File"), buffer, "project"); if (filename == NULL) return; // canceled // Open the file. // NOTE: QE_LoadProject takes care of saving prefs with new path to the project file if (!QE_LoadProject(filename)) Sys_Printf ("Failed to load project from file: %s\n", filename); else // FIXME TTimo QE_Init is probably broken if you don't call it during startup right now .. QE_Init(); } /* ======================================================= Menu modifications ======================================================= */ /* ================== FillBSPMenu ================== */ char *bsp_commands[256]; void FillBSPMenu () { GtkWidget *item, *menu; // menu points to a GtkMenu (not an item) epair_t *ep; GList *lst; int i; menu = GTK_WIDGET (g_object_get_data (G_OBJECT (g_qeglobals_gui.d_main_window), "menu_bsp")); while ((lst = gtk_container_children (GTK_CONTAINER (menu))) != NULL) gtk_container_remove (GTK_CONTAINER (menu), GTK_WIDGET (lst->data)); if (g_PrefsDlg.m_bDetachableMenus) { item = gtk_tearoff_menu_item_new (); gtk_menu_append (GTK_MENU (menu), item); gtk_widget_set_sensitive (item, TRUE); gtk_widget_show (item); } if (g_qeglobals.bBSPFrontendPlugin) { CString str = g_BSPFrontendTable.m_pfnGetBSPMenu(); char cTemp[1024]; strcpy(cTemp, str); char* token = strtok(cTemp, ",;"); if (token && *token == ' ') { while (*token == ' ') token++; } i = 0; // first token is menu name item = gtk_menu_get_attach_widget (GTK_MENU (menu)); gtk_label_set_text (GTK_LABEL (GTK_BIN (item)->child), token); token = strtok(NULL, ",;"); while (token != NULL) { g_BSPFrontendCommands = g_slist_append (g_BSPFrontendCommands, g_strdup (token)); item = gtk_menu_item_new_with_label (token); gtk_widget_show (item); gtk_container_add (GTK_CONTAINER (menu), item); gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (HandleCommand), GINT_TO_POINTER (CMD_BSPCOMMAND+i)); token = strtok(NULL, ",;"); i++; } } else { i = 0; for (ep = g_qeglobals.d_project_entity->epairs; ep; ep = ep->next) { if (strncmp(ep->key, "bsp_", 4)==0) { bsp_commands[i] = ep->key; item = gtk_menu_item_new_with_label (ep->key+4); gtk_widget_show (item); gtk_container_add (GTK_CONTAINER (menu), item); gtk_signal_connect (GTK_OBJECT (item), "activate", GTK_SIGNAL_FUNC (HandleCommand), GINT_TO_POINTER (CMD_BSPCOMMAND+i)); i++; } } } } //============================================== void AddSlash(CString& strPath) { if (strPath.GetLength() > 0) { if ((strPath.GetAt(strPath.GetLength()-1) != '/') && (strPath.GetAt(strPath.GetLength()-1) != '\\')) strPath += '/'; } } bool ExtractPath_and_Filename(const char* pPath, CString& strPath, CString& strFilename) { CString strPathName; strPathName = pPath; int nSlash = strPathName.ReverseFind('\\'); if (nSlash == -1) // TTimo: try forward slash, some are using forward nSlash = strPathName.ReverseFind('/'); if (nSlash >= 0) { strPath = strPathName.Left(nSlash+1); strFilename = strPathName.Right(strPathName.GetLength() - nSlash - 1); } // TTimo: try forward slash, some are using forward else strFilename = pPath; return true; } //=========================================== //++timo FIXME: no longer used .. remove! char *TranslateString (char *buf) { static char buf2[32768]; int i, l; char *out; l = strlen(buf); out = buf2; for (i=0 ; i