2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 // Leonardo Zide (leo@lokigames.com)
30 #include <glib/gi18n.h>
33 #if defined (__linux__) || defined (__APPLE__)
35 #include <X11/keysym.h>
37 #include <gdk/gdkprivate.h>
39 // for the logging part
41 #include <sys/types.h>
43 QEGlobals_t g_qeglobals;
44 QEGlobals_GUI_t g_qeglobals_gui;
46 // leo: Track memory allocations for debugging
47 // NOTE TTimo this was never used and probably not relevant
48 // there are tools to do that
51 static GList *memblocks;
53 void* debug_malloc (size_t size, const char* file, int line)
55 void *buf = g_malloc (size + 8);
57 *((const char**)buf) = file;
62 memblocks = g_list_append (memblocks, buf);
67 void debug_free (void *buf, const char* file, int line)
72 if (g_list_find (memblocks, buf))
74 memblocks = g_list_remove (memblocks, buf);
79 f = *((const char**)buf);
81 Sys_FPrintf (SYS_DBG, "free: %s %d", file, line);
82 Sys_FPrintf (SYS_DBG, " allocated: %s %d\n", f, l);
87 // free (buf); // from qmalloc, will leak unless we add this same hack to cmdlib
92 vec_t Rad_rint (vec_t in)
94 if (g_PrefsDlg.m_bNoClamp)
97 return (float)floor (in + 0.5);
100 void WINAPI QE_CheckOpenGLForErrors(void)
103 int i = qglGetError();
104 if (i != GL_NO_ERROR)
106 if (i == GL_OUT_OF_MEMORY)
108 sprintf(strMsg, "OpenGL out of memory error %s\nDo you wish to save before exiting?", qgluErrorString((GLenum)i));
109 if (gtk_MessageBox(g_pParentWnd->m_pWidget, strMsg, "Radiant Error", MB_YESNO) == IDYES)
111 Map_SaveFile(NULL, false);
117 Sys_Printf ("Warning: OpenGL Error %s\n", qgluErrorString((GLenum)i));
122 // NOTE: don't this function, use VFS instead
123 char *ExpandReletivePath (char *p)
125 static char temp[1024];
130 if (p[0] == '/' || p[0] == '\\')
133 base = ValueForKey(g_qeglobals.d_project_entity, "basepath");
134 sprintf (temp, "%s/%s", base, p);
138 char *copystring (char *s)
141 b = (char*)malloc(strlen(s)+1);
147 bool DoesFileExist(const char* pBuff, long& lSize)
150 if (file.Open(pBuff, "r"))
152 lSize += file.GetLength();
164 // I hope the modified flag is kept correctly up to date
168 // we need to do the following
169 // 1. make sure the snapshot directory exists (create it if it doesn't)
170 // 2. find out what the lastest save is based on number
171 // 3. inc that and save the map
172 CString strOrgPath, strOrgFile;
173 ExtractPath_and_Filename(currentmap, strOrgPath, strOrgFile);
174 AddSlash(strOrgPath);
175 strOrgPath += "snapshots";
178 if (stat(strOrgPath, &Stat) == -1)
181 bGo = (_mkdir(strOrgPath) != -1);
184 #if defined (__linux__) || defined (__APPLE__)
185 bGo = (mkdir(strOrgPath,0755) != -1);
188 AddSlash(strOrgPath);
194 strNewPath = strOrgPath;
195 strNewPath += strOrgFile;
200 sprintf( buf, "%s.%i", strNewPath.GetBuffer(), nCount );
202 bGo = DoesFileExist(strFile, lSize);
205 // strFile has the next available slot
206 Map_SaveFile(strFile, false);
207 // it is still a modified map (we enter this only if this is a modified map)
208 Sys_SetTitle (currentmap);
209 Sys_MarkMapModified();
210 if (lSize > 12 * 1024 * 1024) // total size of saves > 4 mb
212 Sys_Printf("The snapshot files in %s total more than 4 megabytes. You might consider cleaning up.", strOrgPath.GetBuffer());
217 strMsg.Format("Snapshot save failed.. unabled to create directory\n%s", strOrgPath.GetBuffer());
218 gtk_MessageBox(g_pParentWnd->m_pWidget, strMsg);
227 If five minutes have passed since making a change
228 and the map hasn't been saved, save it out.
233 void QE_CheckAutoSave( void )
235 static time_t s_start;
239 if (modified != 1 || !s_start)
245 if ((now - s_start) > (60 * g_PrefsDlg.m_nAutoSave))
247 if (g_PrefsDlg.m_bAutoSave)
250 strMsg = g_PrefsDlg.m_bSnapShots ? "Autosaving snapshot..." : "Autosaving...";
253 Sys_Status (strMsg,0);
255 // only snapshot if not working on a default map
256 if (g_PrefsDlg.m_bSnapShots && stricmp(currentmap, "unnamed.map") != 0)
262 Map_SaveFile (ValueForKey(g_qeglobals.d_project_entity, "autosave"), false);
265 Sys_Status ("Autosaving...Saved.", 0 );
270 Sys_Printf ("Autosave skipped...\n");
271 Sys_Status ("Autosave skipped...", 0 );
278 // NOTE TTimo we don't like that BuildShortPathName too much
279 // the VFS provides a vfsCleanFileName which should perform the cleanup tasks
280 // in the long run I'd like to completely get rid of this
282 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=144
283 // used to be disabled, but caused problems
285 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=291
286 // can't work with long win32 names until the BSP commands are not working differently
288 int BuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen)
291 int nResult = GetFullPathName(pPath, nBufferLen, pBuffer, &pFile);
292 nResult = GetShortPathName(pPath, pBuffer, nBufferLen);
294 strcpy(pBuffer, pPath); // Use long filename
299 #if defined (__linux__) || defined (__APPLE__)
300 int BuildShortPathName(const char* pPath, char* pBuffer, int nBufferLen)
302 // remove /../ from directories
303 const char *scr = pPath; char *dst = pBuffer;
304 for (int i = 0; (i < nBufferLen) && (*scr != 0); i++)
306 if (*scr == '/' && *(scr+1) == '.' && *(scr+2) == '.')
309 while (dst != pBuffer && *(--dst) != '/')
321 return strlen (pBuffer);
326 const char *g_pPathFixups[]=
332 const int g_nPathFixupCount = sizeof(g_pPathFixups) / sizeof(const char*);
334 void QE_CheckProjectEntity()
337 char pBuff[PATH_MAX];
338 char pNewPath[PATH_MAX];
339 for (int i = 0; i < g_nPathFixupCount; i++)
341 char *pPath = ValueForKey (g_qeglobals.d_project_entity, g_pPathFixups[i]);
343 strcpy (pNewPath, pPath);
344 if (pPath[0] != '\\' && pPath[0] != '/')
345 if (GetFullPathName(pPath, PATH_MAX, pBuff, &pFile))
346 strcpy (pNewPath, pBuff);
348 BuildShortPathName (pNewPath, pBuff, PATH_MAX);
350 // check it's not ending with a filename seperator
351 if (pBuff[strlen(pBuff)-1] == '/' || pBuff[strlen(pBuff)-1] == '\\')
353 Sys_FPrintf(SYS_WRN, "WARNING: \"%s\" path in the project file has an ending file seperator, fixing.\n", g_pPathFixups[i]);
354 pBuff[strlen(pBuff)-1]=0;
357 SetKeyValue(g_qeglobals.d_project_entity, g_pPathFixups[i], pBuff);
362 void HandleXMLError( void* ctxt, const char* text, ... )
365 static char buf[32768];
367 va_start (argptr,text);
368 vsprintf (buf, text, argptr);
369 Sys_FPrintf (SYS_ERR, "XML %s\n", buf);
373 #define DTD_BUFFER_LENGTH 1024
374 xmlDocPtr ParseXMLStream(IDataStream *stream, bool validate = false)
376 xmlDocPtr doc = NULL;
377 bool wellFormed = false, valid = false;
378 int res, size = 1024;
380 xmlParserCtxtPtr ctxt;
382 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=433
384 // xmlDoValidityCheckingDefaultValue = 1;
386 xmlDoValidityCheckingDefaultValue = 0;
387 xmlSetGenericErrorFunc(NULL, HandleXMLError);
390 // HACK: use AppPath to resolve DTD location
391 // do a buffer-safe string copy and concatenate
395 char buf[DTD_BUFFER_LENGTH];
400 //assert(g_strAppPath.GetBuffer() != NULL);
401 for(r = g_strAppPath.GetBuffer(); i<DTD_BUFFER_LENGTH && *r != '\0'; i++, r++) w[i] = *r;
403 for(r = "dtds/"; i<DTD_BUFFER_LENGTH && *r != '\0'; i++, r++) w[i] = *r;
407 if(i == DTD_BUFFER_LENGTH)
409 HandleXMLError(NULL, "ERROR: buffer overflow: DTD path length too large\n");
413 res = stream->Read(chars, 4);
416 ctxt = xmlCreatePushParserCtxt(NULL, NULL, chars, res, buf);
418 while ((res = stream->Read(chars, size)) > 0)
420 xmlParseChunk(ctxt, chars, res, 0);
422 xmlParseChunk(ctxt, chars, 0, 1);
425 wellFormed = (ctxt->wellFormed == 1);
426 valid = (ctxt->valid == 1);
428 xmlFreeParserCtxt(ctxt);
431 if(wellFormed && (!validate || (validate && valid)))
440 xmlDocPtr ParseXMLFile(const char* filename, bool validate = false)
443 if (stream.Open(filename, "r"))
444 return ParseXMLStream(&stream, validate);
446 Sys_FPrintf(SYS_ERR, "Failed to open file: %s\n",filename);
450 // copy a string r to a buffer w
451 // replace $string as appropriate
452 void ReplaceTemplates(char* w, const char* r)
455 const char *__ENGINEPATH = "TEMPLATEenginepath";
456 const char *__USERHOMEPATH = "TEMPLATEuserhomepath";
457 const char *__TOOLSPATH = "TEMPLATEtoolspath";
458 const char *__BASEDIR = "TEMPLATEbasedir";
459 const char *__APPPATH = "TEMPLATEapppath";
461 // iterate through string r
464 // check for special character
467 if(strncmp(r+1, __ENGINEPATH, strlen(__ENGINEPATH)) == 0)
469 r+=strlen(__ENGINEPATH)+1;
470 p = g_pGameDescription->mEnginePath.GetBuffer();
472 else if(strncmp(r+1, __USERHOMEPATH, strlen(__USERHOMEPATH)) == 0)
474 r+=strlen(__USERHOMEPATH)+1;
475 p = g_qeglobals.m_strHomeGame.GetBuffer();
477 else if(strncmp(r+1, __BASEDIR, strlen(__BASEDIR)) == 0)
479 r+=strlen(__BASEDIR)+1;
480 p = g_pGameDescription->mBaseGame;
482 else if(strncmp(r+1, __TOOLSPATH, strlen(__TOOLSPATH)) == 0)
484 r+=strlen(__TOOLSPATH)+1;
485 p = g_strGameToolsPath.GetBuffer();
487 else if(strncmp(r+1, __APPPATH, strlen(__APPPATH)) == 0)
489 r+=strlen(__APPPATH)+1;
490 p = g_strAppPath.GetBuffer();
498 while(*p!='\0') *w++ = *p++;
508 TODO TODO TODO (don't think this got fully merged in)
509 TTimo: added project file "version", version 2 adds '#' chars to the BSP command strings
510 version 3 was .. I don't remember .. version 4 adds q3map2 commands
511 TTimo: when QE_LoadProject is called, the prefs are updated with path to the latest project and saved on disk
514 /*\todo decide on a sensible location/name for project files.*/
515 bool QE_LoadProject (const char *projectfile)
519 xmlNodePtr node, project;
521 Sys_Printf("Loading project file: \"%s\"\n", projectfile);
522 doc = ParseXMLFile(projectfile, true);
524 if(doc == NULL) return false;
527 while(node != NULL && node->type != XML_DTD_NODE) node=node->next;
528 if(node == NULL || strcmp((char*)node->name, "project") != 0)
530 Sys_FPrintf(SYS_ERR, "ERROR: invalid file type\n");
534 while(node->type != XML_ELEMENT_NODE) node=node->next;
538 if(g_qeglobals.d_project_entity != NULL) Entity_Free(g_qeglobals.d_project_entity);
539 g_qeglobals.d_project_entity = Entity_Alloc();
541 for(node = project->children; node != NULL; node=node->next)
543 if(node->type != XML_ELEMENT_NODE) continue;
546 ReplaceTemplates(buf, (char*)node->properties->next->children->content);
548 SetKeyValue(g_qeglobals.d_project_entity, (char*)node->properties->children->content, buf);
553 // project file version checking
554 // add a version checking to avoid people loading later versions of the project file and bitching
555 int ver = IntForKey( g_qeglobals.d_project_entity, "version" );
556 if (ver > PROJECT_VERSION)
559 sprintf (strMsg, "This is a version %d project file. This build only supports <=%d project files.\n"
560 "Please choose another project file or upgrade your version of Radiant.", ver, PROJECT_VERSION);
561 gtk_MessageBox (g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK);
562 // set the project file to nothing so we are sure we'll ask next time?
563 g_PrefsDlg.m_strLastProject = "";
564 g_PrefsDlg.SavePrefs();
568 // set here some default project settings you need
569 if ( strlen( ValueForKey( g_qeglobals.d_project_entity, "brush_primit" ) ) == 0 )
571 SetKeyValue( g_qeglobals.d_project_entity, "brush_primit", "0" );
574 g_qeglobals.m_bBrushPrimitMode = IntForKey( g_qeglobals.d_project_entity, "brush_primit" );
576 g_qeglobals.m_strHomeMaps = g_qeglobals.m_strHomeGame;
577 const char* str = ValueForKey(g_qeglobals.d_project_entity, "gamename");
578 if(str[0] == '\0') str = g_pGameDescription->mBaseGame.GetBuffer();
579 g_qeglobals.m_strHomeMaps += str;
580 g_qeglobals.m_strHomeMaps += '/';
582 // don't forget to create the dirs
583 Q_mkdir(g_qeglobals.m_strHomeGame.GetBuffer(), 0775);
584 Q_mkdir(g_qeglobals.m_strHomeMaps.GetBuffer(), 0775);
586 // usefull for the log file and debuggin fucked up configurations from users:
587 // output the basic information of the .qe4 project file
589 // all these paths should be unix format, with a trailing slash at the end
590 // if not.. to debug, check that the project file paths are set up correctly
591 Sys_Printf("basepath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "basepath") );
592 Sys_Printf("entitypath : %s\n", ValueForKey( g_qeglobals.d_project_entity, "entitypath" ) );
595 // check whether user_project key exists..
596 // if not, save the current project under a new name
597 if (ValueForKey(g_qeglobals.d_project_entity, "user_project")[0] == '\0')
599 Sys_Printf("Loaded a template project file\n");
601 // create the user_project key
602 SetKeyValue( g_qeglobals.d_project_entity, "user_project", "1" );
604 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=672
605 if (IntForKey( g_qeglobals.d_project_entity, "version" ) != PROJECT_VERSION)
609 "The template project '%s' has version %d. The editor binary is configured for version %d.\n"
610 "This indicates a problem in your setup. See http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=672\n"
611 "I will keep going with this project till you fix this",
612 projectfile, IntForKey( g_qeglobals.d_project_entity, "version" ), PROJECT_VERSION);
613 gtk_MessageBox (g_pParentWnd->m_pWidget, strMsg, "Can't load project file", MB_ICONERROR | MB_OK);
616 // create the writable project file path
617 strcpy(buf, g_qeglobals.m_strHomeGame.GetBuffer());
618 strcat(buf, g_pGameDescription->mBaseGame.GetBuffer());
619 strcat(buf, "/scripts/");
620 // while the filename is already in use, increment the number we add to the end
622 char pUser[PATH_MAX];
625 sprintf( pUser, "%suser%d." PROJECT_FILETYPE, buf, counter );
627 if (access( pUser, R_OK) != 0)
630 strcpy( buf, pUser );
634 // saving project will cause a save prefs
635 g_PrefsDlg.m_strLastProject = buf;
636 g_PrefsDlg.m_nLastProjectVer = IntForKey( g_qeglobals.d_project_entity, "version" );
641 // update preferences::LastProject with path of this successfully-loaded project
643 Sys_Printf("Setting current project in prefs to \"%s\"\n", g_PrefsDlg.m_strLastProject.GetBuffer() );
644 g_PrefsDlg.m_strLastProject = projectfile;
645 g_PrefsDlg.SavePrefs();
654 TTimo: whenever QE_SaveProject is called, prefs are updated and saved with the path to the project
657 qboolean QE_SaveProject (const char* filename)
659 Sys_Printf("Save project file '%s'\n", filename);
662 xmlDocPtr doc = xmlNewDoc((xmlChar *)"1.0");
664 xmlCreateIntSubset(doc, (xmlChar *)"project", NULL, (xmlChar *)"project.dtd");
665 // create project node
666 doc->children->next = xmlNewDocNode(doc, NULL, (xmlChar *)"project", NULL);
668 for(epair_t* epair = g_qeglobals.d_project_entity->epairs; epair != NULL; epair = epair->next)
670 node = xmlNewChild(doc->children->next, NULL, (xmlChar *)"key", NULL);
671 xmlSetProp(node, (xmlChar*)"name", (xmlChar*)epair->key);
672 xmlSetProp(node, (xmlChar*)"value", (xmlChar*)epair->value);
675 CreateDirectoryPath(filename);
676 if (xmlSaveFormatFile(filename, doc, 1) != -1)
679 Sys_Printf("Setting current project in prefs to \"%s\"\n", filename );
680 g_PrefsDlg.m_strLastProject = filename;
681 g_PrefsDlg.SavePrefs();
687 Sys_FPrintf(SYS_ERR, "failed to save project file: \"%s\"\n", filename);
699 #define SPEED_MOVE 32
700 #define SPEED_TURN 22.5
707 Sets target / targetname on the two entities selected
708 from the first selected to the secon
711 void ConnectEntities (void)
715 char *newtarg = NULL;
717 if (g_qeglobals.d_select_count != 2)
719 Sys_Status ("Must have two brushes selected", 0);
724 e1 = g_qeglobals.d_select_order[0]->owner;
725 e2 = g_qeglobals.d_select_order[1]->owner;
727 if (e1 == world_entity || e2 == world_entity)
729 Sys_Status ("Can't connect to the world", 0);
736 Sys_Status ("Brushes are from same entity", 0);
741 target = ValueForKey (e1, "target");
742 if (target && target[0])
743 newtarg = g_strdup(target);
746 target = ValueForKey(e2, "targetname");
747 if(target && target[0])
748 newtarg = g_strdup(target);
750 Entity_Connect(e1, e2);
755 SetKeyValue(e1, "target", newtarg);
756 SetKeyValue(e2, "targetname", newtarg);
760 Sys_UpdateWindows (W_XY | W_CAMERA);
763 Select_Brush (g_qeglobals.d_select_order[1]);
766 qboolean QE_SingleBrush (bool bQuiet)
768 if ( (selected_brushes.next == &selected_brushes)
769 || (selected_brushes.next->next != &selected_brushes) )
773 Sys_Printf ("Error: you must have a single brush selected\n");
777 if (selected_brushes.next->owner->eclass->fixedsize)
781 Sys_Printf ("Error: you cannot manipulate fixed size entities\n");
789 void QE_InitVFS (void)
791 // VFS initialization -----------------------
792 // we will call vfsInitDirectory, giving the directories to look in (for files in pk3's and for standalone files)
793 // we need to call in order, the mod ones first, then the base ones .. they will be searched in this order
794 // *nix systems have a dual filesystem in ~/.q3a, which is searched first .. so we need to add that too
795 Str directory,prefabs;
797 // TTimo: let's leave this to HL mode for now
798 if (g_pGameDescription->mGameFile == "hl.game")
800 // Hydra: we search the "gametools" path first so that we can provide editor
801 // specific pk3's wads and misc files for use by the editor.
802 // the relevant map compiler tools will NOT use this directory, so this helps
803 // to ensure that editor files are not used/required in release versions of maps
804 // it also helps keep your editor files all in once place, with the editor modules,
805 // plugins, scripts and config files.
806 // it also helps when testing maps, as you'll know your files won't/can't be used
807 // by the game engine itself.
810 directory = g_pGameDescription->mGameToolsPath;
811 vfsInitDirectory(directory.GetBuffer());
814 // NOTE TTimo about the mymkdir calls .. this is a bit dirty, but a safe thing on *nix
816 // if we have a mod dir
817 if (*ValueForKey(g_qeglobals.d_project_entity, "gamename") != '\0')
820 #if defined (__linux__) || defined (__APPLE__)
821 // ~/.<gameprefix>/<fs_game>
822 directory = g_qeglobals.m_strHomeGame.GetBuffer();
823 Q_mkdir (directory.GetBuffer (), 0775);
824 directory += ValueForKey(g_qeglobals.d_project_entity, "gamename");
825 Q_mkdir (directory.GetBuffer (), 0775);
826 vfsInitDirectory(directory.GetBuffer());
827 AddSlash (directory);
829 // also create the maps dir, it will be used as prompt for load/save
830 directory += "/maps";
831 Q_mkdir (directory, 0775);
832 // and the prefabs dir
833 prefabs += "/prefabs";
834 Q_mkdir (prefabs, 0775);
838 // <fs_basepath>/<fs_game>
839 directory = g_pGameDescription->mEnginePath;
840 directory += ValueForKey(g_qeglobals.d_project_entity, "gamename");
841 Q_mkdir (directory.GetBuffer (), 0775);
842 vfsInitDirectory(directory.GetBuffer());
845 // also create the maps dir, it will be used as prompt for load/save
846 directory += "/maps";
847 Q_mkdir (directory.GetBuffer (), 0775);
848 // and the prefabs dir
849 prefabs += "/prefabs";
850 Q_mkdir (prefabs, 0775);
853 #if defined (__linux__) || defined (__APPLE__)
854 // ~/.<gameprefix>/<fs_main>
855 directory = g_qeglobals.m_strHomeGame.GetBuffer();
856 directory += g_pGameDescription->mBaseGame;
857 vfsInitDirectory (directory.GetBuffer ());
860 // <fs_basepath>/<fs_main>
861 directory = g_pGameDescription->mEnginePath;
862 directory += g_pGameDescription->mBaseGame;
863 vfsInitDirectory(directory.GetBuffer());
869 ** initialize variables
871 g_qeglobals.d_gridsize = 8;
872 g_qeglobals.d_showgrid = true;
877 FillClassList(); // list in entity window
889 void WINAPI QE_ConvertDOSToUnixName( char *dst, const char *src )
902 int g_numbrushes, g_numentities;
904 void QE_CountBrushesAndUpdateStatusBar( void )
906 static int s_lastbrushcount, s_lastentitycount;
907 static qboolean s_didonce;
915 if ( active_brushes.next != NULL )
917 for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; b=next)
922 if ( !b->owner->eclass->fixedsize)
930 if ( entities.next != NULL )
932 for ( e = entities.next ; e != &entities && g_numentities != MAX_MAP_ENTITIES ; e = e->next)
938 if ( ( ( g_numbrushes != s_lastbrushcount ) || ( g_numentities != s_lastentitycount ) ) || ( !s_didonce ) )
940 Sys_UpdateStatusBar();
942 s_lastbrushcount = g_numbrushes;
943 s_lastentitycount = g_numentities;
948 char com_token[1024];
956 double I_FloatTime (void)
964 // more precise, less portable
969 gettimeofday(&tp, &tzp);
974 return tp.tv_usec/1000000.0;
977 return (tp.tv_sec - secbase) + tp.tv_usec/1000000.0;
986 Parse a token out of a string
989 char *COM_Parse (char *data)
1002 while ( (c = *data) <= ' ')
1007 return NULL; // end of file;
1013 if (c=='/' && data[1] == '/')
1015 while (*data && *data != '\n')
1021 // handle quoted strings specially
1038 // parse single characters
1039 if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
1047 // parse a regular word
1054 if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c==':')
1062 char* Get_COM_Token()
1068 =============================================================================
1072 =============================================================================
1077 char *argv[MAX_NUM_ARGVS];
1084 void ParseCommandLine (char *lpCmdLine)
1087 argv[0] = "programname";
1089 while (*lpCmdLine && (argc < MAX_NUM_ARGVS))
1091 while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126)))
1096 argv[argc] = lpCmdLine;
1099 while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126)))
1118 Checks for the given parameter in the program's command line arguments
1119 Returns the argument number (1 to argc-1) or 0 if not present
1122 int CheckParm (const char *check)
1126 for (i = 1;i<argc;i++)
1128 if ( stricmp(check, argv[i]) )
1143 int ParseHex (const char *hex)
1154 if (*str >= '0' && *str <= '9')
1156 else if (*str >= 'a' && *str <= 'f')
1157 num += 10 + *str-'a';
1158 else if (*str >= 'A' && *str <= 'F')
1159 num += 10 + *str-'A';
1161 Error ("Bad hex number: %s",hex);
1169 int ParseNum (const char *str)
1172 return ParseHex (str+1);
1173 if (str[0] == '0' && str[1] == 'x')
1174 return ParseHex (str+2);
1178 // BSP frontend plugin
1179 // global flag for BSP frontend plugin is g_qeglobals.bBSPFrontendPlugin
1180 _QERPlugBSPFrontendTable g_BSPFrontendTable;
1182 // =============================================================================
1188 return (GetKeyState(VK_MENU) & 0x8000) != 0;
1191 #if defined (__linux__) || defined (__APPLE__)
1195 XQueryKeymap(GDK_DISPLAY(), keys);
1197 x = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_L);
1198 if (keys[x/8] & (1 << (x % 8)))
1201 x = XKeysymToKeycode (GDK_DISPLAY(), XK_Alt_R);
1202 if (keys[x/8] & (1 << (x % 8)))
1209 bool Sys_ShiftDown ()
1212 return (GetKeyState(VK_SHIFT) & 0x8000) != 0;
1215 #if defined (__linux__) || defined (__APPLE__)
1219 XQueryKeymap(GDK_DISPLAY(), keys);
1221 x = XKeysymToKeycode (GDK_DISPLAY(), XK_Shift_L);
1222 if (keys[x/8] & (1 << (x % 8)))
1225 x = XKeysymToKeycode (GDK_DISPLAY(), XK_Shift_R);
1226 if (keys[x/8] & (1 << (x % 8)))
1233 void Sys_MarkMapModified (void)
1235 char title[PATH_MAX];
1239 modified = true; // mark the map as changed
1240 sprintf (title, "%s *", currentmap);
1242 QE_ConvertDOSToUnixName( title, title );
1243 Sys_SetTitle (title);
1247 void Sys_SetTitle (const char *text)
1249 gtk_window_set_title (GTK_WINDOW (g_qeglobals_gui.d_main_window), text);
1252 bool g_bWaitCursor = false;
1254 void WINAPI Sys_BeginWait (void)
1256 GdkCursor *cursor = gdk_cursor_new (GDK_WATCH);
1257 gdk_window_set_cursor (g_pParentWnd->m_pWidget->window, cursor);
1258 gdk_cursor_unref (cursor);
1259 g_bWaitCursor = true;
1262 void WINAPI Sys_EndWait (void)
1264 GdkCursor *cursor = gdk_cursor_new (GDK_LEFT_PTR);
1265 gdk_window_set_cursor (g_pParentWnd->m_pWidget->window, cursor);
1266 gdk_cursor_unref (cursor);
1267 g_bWaitCursor = false;
1270 void Sys_GetCursorPos (int *x, int *y)
1272 // FIXME: not multihead safe
1273 gdk_window_get_pointer (NULL, x, y, NULL);
1276 void Sys_SetCursorPos (int x, int y)
1278 // NOTE: coordinates are in GDK space, not OS space
1280 int sys_x = x - g_pParentWnd->GetGDKOffsetX();
1281 int sys_y = y - g_pParentWnd->GetGDKOffsetY();
1283 SetCursorPos (sys_x, sys_y);
1286 #if defined (__linux__) || defined (__APPLE__)
1287 XWarpPointer (GDK_DISPLAY(), None, GDK_ROOT_WINDOW(), 0, 0, 0, 0, x, y);
1291 void Sys_Beep (void)
1293 #if defined (__linux__) || defined (__APPLE__)
1296 MessageBeep (MB_ICONASTERISK);
1300 double Sys_DoubleTime (void)
1302 return clock()/ 1000.0;
1306 ===============================================================
1310 ===============================================================
1313 void Sys_UpdateStatusBar( void )
1315 extern int g_numbrushes, g_numentities;
1317 char numbrushbuffer[100]="";
1319 sprintf( numbrushbuffer, "Brushes: %d Entities: %d", g_numbrushes, g_numentities );
1320 g_pParentWnd->SetStatusText(2, numbrushbuffer);
1321 //Sys_Status( numbrushbuffer, 2 );
1324 void Sys_Status(const char *psz, int part )
1326 g_pParentWnd->SetStatusText (part, psz);
1329 // =============================================================================
1333 static GtkWidget *MRU_items[MRU_MAX];
1334 static int MRU_used;
1335 typedef char MRU_filename_t[PATH_MAX];
1336 MRU_filename_t MRU_filenames[MRU_MAX];
1338 static char* MRU_GetText (int index)
1340 return MRU_filenames[index];
1343 void buffer_write_escaped_mnemonic(char* buffer, const char* string)
1345 while(*string != '\0')
1352 *buffer++ = *string++;
1357 static void MRU_SetText (int index, const char *filename)
1359 strcpy(MRU_filenames[index], filename);
1361 char mnemonic[PATH_MAX * 2 + 4];
1363 sprintf(mnemonic+1, "%d", index+1);
1366 buffer_write_escaped_mnemonic(mnemonic+4, filename);
1367 gtk_label_set_text_with_mnemonic(GTK_LABEL (GTK_BIN (MRU_items[index])->child), mnemonic);
1372 int i = g_PrefsDlg.m_nMRUCount;
1375 i = 4; //FIXME: make this a define
1378 MRU_AddFile (g_PrefsDlg.m_strMRUFiles[i-1].GetBuffer());
1383 g_PrefsDlg.m_nMRUCount = MRU_used;
1385 for (int i = 0; i < MRU_used; i++)
1386 g_PrefsDlg.m_strMRUFiles[i] = MRU_GetText (i);
1389 void MRU_AddWidget (GtkWidget *widget, int pos)
1392 MRU_items[pos] = widget;
1395 void MRU_AddFile (const char *str)
1400 // check if file is already in our list
1401 for (i = 0; i < MRU_used; i++)
1403 text = MRU_GetText (i);
1405 if (strcmp (text, str) == 0)
1409 MRU_SetText (i, MRU_GetText (i-1));
1411 MRU_SetText (0, str);
1417 if (MRU_used < MRU_MAX)
1421 for (i = MRU_used-1; i > 0; i--)
1422 MRU_SetText (i, MRU_GetText (i-1));
1424 MRU_SetText (0, str);
1425 gtk_widget_set_sensitive (MRU_items[0], TRUE);
1426 gtk_widget_show (MRU_items[MRU_used-1]);
1429 void MRU_Activate (int index)
1431 char *text = MRU_GetText (index);
1433 if (access (text, R_OK) == 0)
1435 text = strdup (text);
1437 Map_LoadFile (text);
1444 for (int i = index; i < MRU_used; i++)
1445 MRU_SetText (i, MRU_GetText (i+1));
1449 gtk_label_set_text (GTK_LABEL (GTK_BIN (MRU_items[0])->child), "Recent Files");
1450 gtk_widget_set_sensitive (MRU_items[0], FALSE);
1454 gtk_widget_hide (MRU_items[MRU_used]);
1460 ======================================================================
1464 ======================================================================
1467 qboolean ConfirmModified ()
1472 if (gtk_MessageBox (g_pParentWnd->m_pWidget, "This will lose changes to the map", "warning", MB_OKCANCEL) == IDCANCEL)
1477 void ProjectDialog ()
1479 const char *filename;
1480 char buffer[NAME_MAX];
1483 * Obtain the system directory name and
1484 * store it in buffer.
1487 strcpy(buffer, g_qeglobals.m_strHomeGame.GetBuffer());
1488 strcat(buffer, g_pGameDescription->mBaseGame.GetBuffer());
1489 strcat (buffer, "/scripts/");
1491 // Display the Open dialog box
1492 filename = file_dialog (NULL, TRUE, "Open File", buffer, "project");
1494 if (filename == NULL)
1498 // NOTE: QE_LoadProject takes care of saving prefs with new path to the project file
1499 if (!QE_LoadProject(filename))
1500 Sys_Printf ("Failed to load project from file: %s\n", filename);
1502 // FIXME TTimo QE_Init is probably broken if you don't call it during startup right now ..
1507 =======================================================
1511 =======================================================
1520 char *bsp_commands[256];
1524 GtkWidget *item, *menu; // menu points to a GtkMenu (not an item)
1529 menu = GTK_WIDGET (g_object_get_data (G_OBJECT (g_qeglobals_gui.d_main_window), "menu_bsp"));
1531 while ((lst = gtk_container_children (GTK_CONTAINER (menu))) != NULL)
1532 gtk_container_remove (GTK_CONTAINER (menu), GTK_WIDGET (lst->data));
1534 if (g_PrefsDlg.m_bDetachableMenus) {
1535 item = gtk_tearoff_menu_item_new ();
1536 gtk_menu_append (GTK_MENU (menu), item);
1537 gtk_widget_set_sensitive (item, TRUE);
1538 gtk_widget_show (item);
1541 if (g_qeglobals.bBSPFrontendPlugin)
1543 CString str = g_BSPFrontendTable.m_pfnGetBSPMenu();
1546 char* token = strtok(cTemp, ",;");
1547 if (token && *token == ' ')
1549 while (*token == ' ')
1554 // first token is menu name
1555 item = gtk_menu_get_attach_widget (GTK_MENU (menu));
1556 gtk_label_set_text (GTK_LABEL (GTK_BIN (item)->child), token);
1558 token = strtok(NULL, ",;");
1559 while (token != NULL)
1561 g_BSPFrontendCommands = g_slist_append (g_BSPFrontendCommands, g_strdup (token));
1562 item = gtk_menu_item_new_with_label (token);
1563 gtk_widget_show (item);
1564 gtk_container_add (GTK_CONTAINER (menu), item);
1565 gtk_signal_connect (GTK_OBJECT (item), "activate",
1566 GTK_SIGNAL_FUNC (HandleCommand), GINT_TO_POINTER (CMD_BSPCOMMAND+i));
1567 token = strtok(NULL, ",;");
1574 for (ep = g_qeglobals.d_project_entity->epairs; ep; ep = ep->next)
1576 if (strncmp(ep->key, "bsp_", 4)==0)
1578 bsp_commands[i] = ep->key;
1579 item = gtk_menu_item_new_with_label (ep->key+4);
1580 gtk_widget_show (item);
1581 gtk_container_add (GTK_CONTAINER (menu), item);
1582 gtk_signal_connect (GTK_OBJECT (item), "activate",
1583 GTK_SIGNAL_FUNC (HandleCommand), GINT_TO_POINTER (CMD_BSPCOMMAND+i));
1590 //==============================================
1592 void AddSlash(CString& strPath)
1594 if (strPath.GetLength() > 0)
1596 if ((strPath.GetAt(strPath.GetLength()-1) != '/') &&
1597 (strPath.GetAt(strPath.GetLength()-1) != '\\'))
1602 bool ExtractPath_and_Filename(const char* pPath, CString& strPath, CString& strFilename)
1604 CString strPathName;
1605 strPathName = pPath;
1606 int nSlash = strPathName.ReverseFind('\\');
1608 // TTimo: try forward slash, some are using forward
1609 nSlash = strPathName.ReverseFind('/');
1612 strPath = strPathName.Left(nSlash+1);
1613 strFilename = strPathName.Right(strPathName.GetLength() - nSlash - 1);
1615 // TTimo: try forward slash, some are using forward
1617 strFilename = pPath;
1621 //===========================================
1623 //++timo FIXME: no longer used .. remove!
1624 char *TranslateString (char *buf)
1626 static char buf2[32768];
1632 for (i=0 ; i<l ; i++)
1647 // called whenever we need to open/close/check the console log file
1648 void Sys_LogFile (void)
1650 if (g_PrefsDlg.mGamesDialog.m_bLogConsole && !g_qeglobals.hLogFile)
1652 // settings say we should be logging and we don't have a log file .. so create it
1653 // open a file to log the console (if user prefs say so)
1654 // the file handle is g_qeglobals.hLogFile
1655 // the log file is erased
1657 name = g_strTempPath;
1658 name += "radiant.log";
1659 #if defined (__linux__) || defined (__APPLE__)
1660 g_qeglobals.hLogFile = open( name.GetBuffer(), O_TRUNC | O_CREAT | O_WRONLY, S_IREAD | S_IWRITE );
1663 g_qeglobals.hLogFile = _open( name.GetBuffer(), _O_TRUNC | _O_CREAT | _O_WRONLY, _S_IREAD | _S_IWRITE );
1665 if (g_qeglobals.hLogFile)
1667 Sys_Printf("Started logging to %s\n", name.GetBuffer());
1670 Sys_Printf( "Today is: %s", ctime(&localtime));
1671 Sys_Printf( "This is radiant '" RADIANT_VERSION "' compiled " __DATE__ "\n");
1672 Sys_Printf( RADIANT_ABOUTMSG "\n");
1675 gtk_MessageBox (NULL, "Failed to create log file, check write permissions in Radiant directory.\n",
1676 "Console logging", MB_OK );
1678 else if (!g_PrefsDlg.mGamesDialog.m_bLogConsole && g_qeglobals.hLogFile)
1680 // settings say we should not be logging but still we have an active logfile .. close it
1683 Sys_Printf("Closing log file at %s\n", ctime(&localtime));
1685 _close( g_qeglobals.hLogFile );
1687 #if defined (__linux__) || defined (__APPLE__)
1688 close( g_qeglobals.hLogFile );
1690 g_qeglobals.hLogFile = 0;
1694 void Sys_ClearPrintf (void)
1696 GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit));
1697 gtk_text_buffer_set_text(buffer, "", -1);
1700 // used to be around 32000, that should be way enough already
1701 #define BUFFER_SIZE 4096
1703 extern "C" void Sys_FPrintf_VA( int level, const char *text, va_list args ) {
1704 char buf[BUFFER_SIZE];
1707 vsnprintf( buf, BUFFER_SIZE, text, args );
1708 buf[BUFFER_SIZE-1] = 0;
1709 const unsigned int length = strlen(buf);
1711 if ( g_qeglobals.hLogFile ) {
1713 _write( g_qeglobals.hLogFile, buf, length );
1714 _commit( g_qeglobals.hLogFile );
1716 #if defined (__linux__) || defined (__APPLE__)
1717 write( g_qeglobals.hLogFile, buf, length );
1721 if ( level != SYS_NOCON ) {
1722 // TTimo: FIXME: killed the console to avoid GDI leak fuckup
1723 if ( g_qeglobals_gui.d_edit != NULL ) {
1724 GtkTextBuffer* buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit));
1727 gtk_text_buffer_get_end_iter(buffer, &iter);
1729 static GtkTextMark* end = gtk_text_buffer_create_mark(buffer, "end", &iter, FALSE);
1731 const GdkColor yellow = { 0, 0xb0ff, 0xb0ff, 0x0000 };
1732 const GdkColor red = { 0, 0xffff, 0x0000, 0x0000 };
1733 const GdkColor black = { 0, 0x0000, 0x0000, 0x0000 };
1735 static GtkTextTag* error_tag = gtk_text_buffer_create_tag (buffer, "red_foreground", "foreground-gdk", &red, NULL);
1736 static GtkTextTag* warning_tag = gtk_text_buffer_create_tag (buffer, "yellow_foreground", "foreground-gdk", &yellow, NULL);
1737 static GtkTextTag* standard_tag = gtk_text_buffer_create_tag (buffer, "black_foreground", "foreground-gdk", &black, NULL);
1753 gtk_text_buffer_insert_with_tags(buffer, &iter, buf, length, tag, NULL);
1755 gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(g_qeglobals_gui.d_edit), end);
1757 // update console widget immediatly if we're doing something time-consuming
1758 if( !g_bScreenUpdates && GTK_WIDGET_REALIZED( g_qeglobals_gui.d_edit ) )
1760 gtk_grab_add(g_qeglobals_gui.d_edit);
1762 while(gtk_events_pending())
1763 gtk_main_iteration();
1765 gtk_grab_remove(g_qeglobals_gui.d_edit);
1771 // NOTE: this is the handler sent to synapse
1772 // must match PFN_SYN_PRINTF_VA
1773 extern "C" void Sys_Printf_VA (const char *text, va_list args)
1775 Sys_FPrintf_VA (SYS_STD, text, args);
1778 extern "C" void Sys_Printf( const char *text, ... ) {
1781 va_start( args, text );
1782 Sys_FPrintf_VA( SYS_STD, text, args );
1786 extern "C" void Sys_FPrintf (int level, const char *text, ...)
1790 va_start (args, text);
1791 Sys_FPrintf_VA (level, text, args);