2 Copyright (C) 1999-2006 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
22 /*! \mainpage GtkRadiant Documentation Index
24 \section intro_sec Introduction
26 This documentation is generated from comments in the source code.
28 \section links_sec Useful Links
30 \link include/itextstream.h include/itextstream.h \endlink - Global output and error message streams, similar to std::cout and std::cerr. \n
32 FileInputStream - similar to std::ifstream (binary mode) \n
33 FileOutputStream - similar to std::ofstream (binary mode) \n
34 TextFileInputStream - similar to std::ifstream (text mode) \n
35 TextFileOutputStream - similar to std::ofstream (text mode) \n
36 StringOutputStream - similar to std::stringstream \n
38 \link string/string.h string/string.h \endlink - C-style string comparison and memory management. \n
39 \link os/path.h os/path.h \endlink - Path manipulation for radiant's standard path format \n
40 \link os/file.h os/file.h \endlink - OS file-system access. \n
42 ::std::string - automatic string memory management \n
43 Array - automatic array memory management \n
44 HashTable - generic hashtable, similar to std::hash_map \n
46 \link math/vector.h math/vector.h \endlink - Vectors \n
47 \link math/matrix.h math/matrix.h \endlink - Matrices \n
48 \link math/quaternion.h math/quaternion.h \endlink - Quaternions \n
49 \link math/plane.h math/plane.h \endlink - Planes \n
50 \link math/aabb.h math/aabb.h \endlink - AABBs \n
52 Callback MemberCaller FunctionCaller - callbacks similar to using boost::function with boost::bind \n
53 SmartPointer SmartReference - smart-pointer and smart-reference similar to Loki's SmartPtr \n
55 \link generic/bitfield.h generic/bitfield.h \endlink - Type-safe bitfield \n
56 \link generic/enumeration.h generic/enumeration.h \endlink - Type-safe enumeration \n
58 DefaultAllocator - Memory allocation using new/delete, compliant with std::allocator interface \n
60 \link debugging/debugging.h debugging/debugging.h \endlink - Debugging macros \n
68 #include "debugging/debugging.h"
72 #include <gtk/gtkmain.h>
77 #include "stream/stringstream.h"
78 #include "stream/textfilestream.h"
80 #include "gtkutil/messagebox.h"
81 #include "gtkutil/image.h"
83 #include "texwindow.h"
85 #include "mainframe.h"
87 #include "preferences.h"
88 #include "environment.h"
89 #include "referencecache.h"
90 #include "stacktrace.h"
99 void error_redirect( const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data ){
100 gboolean in_recursion;
104 in_recursion = ( log_level & G_LOG_FLAG_RECURSION ) != 0;
105 is_fatal = ( log_level & G_LOG_FLAG_FATAL ) != 0;
106 log_level = (GLogLevelFlags) ( log_level & G_LOG_LEVEL_MASK );
109 message = "(0) message";
113 strcpy( buf, domain );
122 case G_LOG_LEVEL_ERROR:
123 if ( in_recursion ) {
124 strcat( buf, "ERROR (recursed) **: " );
127 strcat( buf, "ERROR **: " );
130 case G_LOG_LEVEL_CRITICAL:
131 if ( in_recursion ) {
132 strcat( buf, "CRITICAL (recursed) **: " );
135 strcat( buf, "CRITICAL **: " );
138 case G_LOG_LEVEL_WARNING:
139 if ( in_recursion ) {
140 strcat( buf, "WARNING (recursed) **: " );
143 strcat( buf, "WARNING **: " );
146 case G_LOG_LEVEL_MESSAGE:
147 if ( in_recursion ) {
148 strcat( buf, "Message (recursed): " );
151 strcat( buf, "Message: " );
154 case G_LOG_LEVEL_INFO:
155 if ( in_recursion ) {
156 strcat( buf, "INFO (recursed): " );
159 strcat( buf, "INFO: " );
162 case G_LOG_LEVEL_DEBUG:
163 if ( in_recursion ) {
164 strcat( buf, "DEBUG (recursed): " );
167 strcat( buf, "DEBUG: " );
171 /* we are used for a log level that is not defined by GLib itself,
172 * try to make the best out of it.
174 if ( in_recursion ) {
175 strcat( buf, "LOG (recursed:" );
178 strcat( buf, "LOG (" );
181 gchar string[] = "0x00): ";
182 gchar *p = string + 2;
185 i = g_bit_nth_msf( log_level, -1 );
188 *p = '0' + ( i & 0xf );
193 strcat( buf, string );
196 strcat( buf, "): " );
200 strcat( buf, message );
202 strcat( buf, "\naborting...\n" );
209 globalErrorStream() << buf << "\n";
211 bool worth_showing = strcmp(domain,"Gdk") != 0;
213 // FIXME why are warnings is_fatal?
215 worth_showing = worth_showing && is_fatal;
219 ERROR_MESSAGE( "GTK+ error: " << buf );
222 #if defined ( _DEBUG ) && defined ( WIN32 ) && defined ( _MSC_VER )
227 #if defined ( _DEBUG ) && defined ( WIN32 ) && defined ( _MSC_VER )
228 _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
236 Lock() : m_locked( false ){
244 bool locked() const {
253 ScopedLock( Lock& lock ) : m_lock( lock ){
261 class LineLimitedTextOutputStream : public TextOutputStream
263 TextOutputStream& outputStream;
266 LineLimitedTextOutputStream( TextOutputStream& outputStream, std::size_t count )
267 : outputStream( outputStream ), count( count ){
269 std::size_t write( const char* buffer, std::size_t length ){
271 const char* p = buffer;
272 const char* end = buffer + length;
275 p = std::find( p, end, '\n' );
280 if ( --count == 0 ) {
285 outputStream.write( buffer, length );
291 class PopupDebugMessageHandler : public DebugMessageHandler
293 StringOutputStream m_buffer;
296 TextOutputStream& getOutputStream(){
297 if ( !m_lock.locked() ) {
300 return globalErrorStream();
302 bool handleMessage(){
303 getOutputStream() << "----------------\n";
304 LineLimitedTextOutputStream outputStream( getOutputStream(), 24 );
305 write_stack_trace( outputStream );
306 getOutputStream() << "----------------\n";
307 globalErrorStream() << m_buffer.c_str();
308 if ( !m_lock.locked() ) {
309 ScopedLock lock( m_lock );
311 m_buffer << "Break into the debugger?\n";
312 bool handled = gtk_MessageBox( 0, m_buffer.c_str(), "Radiant - Runtime Error", eMB_YESNO, eMB_ICONERROR ) == eIDNO;
316 m_buffer << "Please report this error to the developers\n";
317 gtk_MessageBox( 0, m_buffer.c_str(), "Radiant - Runtime Error", eMB_OK, eMB_ICONERROR );
325 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
328 GlobalErrorStream::instance().setOutputStream( getSysPrintErrorStream() );
329 GlobalOutputStream::instance().setOutputStream( getSysPrintOutputStream() );
333 const char* home = environment_get_home_path();
337 StringOutputStream path( 256 );
338 path << home << "1." << RADIANT_MAJOR_VERSION "." << RADIANT_MINOR_VERSION << '/';
339 g_strSettingsPath = path.c_str();
342 Q_mkdir( g_strSettingsPath.c_str() );
344 g_strAppPath = environment_get_app_path();
346 // radiant is installed in the parent dir of "tools/"
347 // NOTE: this is not very easy for debugging
348 // maybe add options to lookup in several places?
349 // (for now I had to create symlinks)
351 StringOutputStream path( 256 );
352 path << g_strAppPath.c_str() << "bitmaps/";
353 BitmapsPath_set( path.c_str() );
356 // we will set this right after the game selection is done
357 g_strGameToolsPath = g_strAppPath;
360 bool check_version_file( const char* filename, const char* version ){
361 TextFileInputStream file( filename );
362 if ( !file.failed() ) {
364 buf[file.read( buf, 9 )] = '\0';
366 // chomp it (the hard way)
368 while ( buf[chomp] >= '0' && buf[chomp] <= '9' )
372 return string_equal( buf, version );
377 bool check_version(){
378 // a safe check to avoid people running broken installations
379 // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
380 // make something idiot proof and someone will make better idiots, this may be overkill
381 // let's leave it disabled in debug mode in any case
382 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
384 #define CHECK_VERSION
387 // locate and open RADIANT_MAJOR and RADIANT_MINOR
388 bool bVerIsGood = true;
390 StringOutputStream ver_file_name( 256 );
391 ver_file_name << AppPath_get() << "RADIANT_MAJOR";
392 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MAJOR_VERSION );
395 StringOutputStream ver_file_name( 256 );
396 ver_file_name << AppPath_get() << "RADIANT_MINOR";
397 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MINOR_VERSION );
401 StringOutputStream msg( 256 );
402 msg << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n"
403 "Make sure you run the right/latest editor binary you installed\n"
405 gtk_MessageBox( 0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONDEFAULT );
413 void create_global_pid(){
415 the global prefs loading / game selection dialog might fail for any reason we don't know about
416 we need to catch when it happens, to cleanup the stateful prefs which might be killing it
417 and to turn on console logging for lookup of the problem
418 this is the first part of the two step .pid system
419 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
421 StringOutputStream g_pidFile( 256 ); ///< the global .pid file (only for global part of the startup)
423 g_pidFile << SettingsPath_get() << "radiant.pid";
426 pid = fopen( g_pidFile.c_str(), "r" );
430 if ( remove( g_pidFile.c_str() ) == -1 ) {
431 StringOutputStream msg( 256 );
432 msg << "WARNING: Could not delete " << g_pidFile.c_str();
433 gtk_MessageBox( 0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
436 // in debug, never prompt to clean registry, turn console logging auto after a failed start
437 #if !defined( _DEBUG )
438 StringOutputStream msg( 256 );
439 msg << "Radiant failed to start properly the last time it was run.\n"
440 "The failure may be related to current global preferences.\n"
441 "Do you want to reset global preferences to defaults?";
443 if ( gtk_MessageBox( 0, msg.c_str(), "Radiant - Startup Failure", eMB_YESNO, eMB_ICONQUESTION ) == eIDYES ) {
444 g_GamesDialog.Reset();
448 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
450 gtk_MessageBox( 0, msg.c_str(), "Radiant - Console Log", eMB_OK );
453 // set without saving, the class is not in a coherent state yet
454 // just do the value change and call to start logging, CGamesDialog will pickup when relevant
455 g_GamesDialog.m_bForceLogConsole = true;
459 // create a primary .pid for global init run
460 pid = fopen( g_pidFile.c_str(), "w" );
466 void remove_global_pid(){
467 StringOutputStream g_pidFile( 256 );
468 g_pidFile << SettingsPath_get() << "radiant.pid";
471 if ( remove( g_pidFile.c_str() ) == -1 ) {
472 StringOutputStream msg( 256 );
473 msg << "WARNING: Could not delete " << g_pidFile.c_str();
474 gtk_MessageBox( 0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
479 now the secondary game dependant .pid file
480 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
482 void create_local_pid(){
483 StringOutputStream g_pidGameFile( 256 ); ///< the game-specific .pid file
484 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
486 FILE *pid = fopen( g_pidGameFile.c_str(), "r" );
489 if ( remove( g_pidGameFile.c_str() ) == -1 ) {
490 StringOutputStream msg;
491 msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
492 gtk_MessageBox( 0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONERROR );
495 // in debug, never prompt to clean registry, turn console logging auto after a failed start
496 #if !defined( _DEBUG )
497 StringOutputStream msg;
498 msg << "Radiant failed to start properly the last time it was run.\n"
499 "The failure may be caused by current preferences.\n"
500 "Do you want to reset all preferences to defaults?";
502 if ( gtk_MessageBox( 0, msg.c_str(), "Radiant - Startup Failure", eMB_YESNO, eMB_ICONQUESTION ) == eIDYES ) {
507 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
509 gtk_MessageBox( 0, msg.c_str(), "Radiant - Console Log", eMB_OK );
512 // force console logging on! (will go in prefs too)
513 g_GamesDialog.m_bForceLogConsole = true;
518 // create one, will remove right after entering message loop
519 pid = fopen( g_pidGameFile.c_str(), "w" );
528 now the secondary game dependant .pid file
529 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
531 void remove_local_pid(){
532 StringOutputStream g_pidGameFile( 256 );
533 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
534 remove( g_pidGameFile.c_str() );
537 void user_shortcuts_init(){
538 StringOutputStream path( 256 );
539 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
540 LoadCommandMap( path.c_str() );
541 SaveCommandMap( path.c_str() );
544 void user_shortcuts_save(){
545 StringOutputStream path( 256 );
546 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
547 SaveCommandMap( path.c_str() );
550 int main( int argc, char* argv[] ){
557 lib = LoadLibrary( "dwmapi.dll" );
559 void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" );
560 if ( qDwmEnableComposition ) {
561 qDwmEnableComposition( FALSE );
567 gtk_disable_setlocale();
568 gtk_init( &argc, &argv );
570 // redirect Gtk warnings to the console
571 g_log_set_handler( "Gdk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
572 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
573 g_log_set_handler( "Gtk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
574 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
575 g_log_set_handler( "GtkGLExt", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
576 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
577 g_log_set_handler( "GLib", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
578 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
579 g_log_set_handler( 0, (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
580 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
582 GlobalDebugMessageHandler::instance().setHandler( GlobalPopupDebugMessageHandler::instance() );
584 environment_init( argc, argv );
588 if ( !check_version() ) {
596 GlobalPreferences_Init();
598 g_GamesDialog.Init();
600 g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
604 g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
608 // in a very particular post-.pid startup
609 // we may have the console turned on and want to keep it that way
610 // so we use a latching system
611 if ( g_GamesDialog.m_bForceLogConsole ) {
613 g_Console_enableLogging = true;
614 g_GamesDialog.m_bForceLogConsole = false;
618 Radiant_Initialise();
622 user_shortcuts_init();
625 g_pParentWnd = new MainFrame();
629 if ( g_bLoadLastMap && !g_strLastMap.empty() ) {
630 Map_LoadFile( g_strLastMap.c_str() );
637 // load up shaders now that we have the map loaded
639 TextureBrowser_ShowStartupShaders( GlobalTextureBrowser() );
646 // avoid saving prefs when the app is minimized
647 if ( g_pParentWnd->IsSleeping() ) {
648 globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
649 g_preferences_globals.disable_ini = true;
654 if ( !Map_Unnamed( g_map ) ) {
655 g_strLastMap = Map_Name( g_map );
660 user_shortcuts_save();
662 global_accel_destroy();
666 // close the log file if any
667 Sys_LogFile( false );