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 "uilib/uilib.h"
78 #include "stream/stringstream.h"
79 #include "stream/textfilestream.h"
81 #include "gtkutil/messagebox.h"
82 #include "gtkutil/image.h"
84 #include "texwindow.h"
86 #include "mainframe.h"
88 #include "preferences.h"
89 #include "environment.h"
90 #include "referencecache.h"
91 #include "stacktrace.h"
100 void error_redirect( const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data ){
101 gboolean in_recursion;
105 in_recursion = ( log_level & G_LOG_FLAG_RECURSION ) != 0;
106 is_fatal = ( log_level & G_LOG_FLAG_FATAL ) != 0;
107 log_level = (GLogLevelFlags) ( log_level & G_LOG_LEVEL_MASK );
110 message = "(0) message";
114 strcpy( buf, domain );
123 case G_LOG_LEVEL_ERROR:
124 if ( in_recursion ) {
125 strcat( buf, "ERROR (recursed) **: " );
128 strcat( buf, "ERROR **: " );
131 case G_LOG_LEVEL_CRITICAL:
132 if ( in_recursion ) {
133 strcat( buf, "CRITICAL (recursed) **: " );
136 strcat( buf, "CRITICAL **: " );
139 case G_LOG_LEVEL_WARNING:
140 if ( in_recursion ) {
141 strcat( buf, "WARNING (recursed) **: " );
144 strcat( buf, "WARNING **: " );
147 case G_LOG_LEVEL_MESSAGE:
148 if ( in_recursion ) {
149 strcat( buf, "Message (recursed): " );
152 strcat( buf, "Message: " );
155 case G_LOG_LEVEL_INFO:
156 if ( in_recursion ) {
157 strcat( buf, "INFO (recursed): " );
160 strcat( buf, "INFO: " );
163 case G_LOG_LEVEL_DEBUG:
164 if ( in_recursion ) {
165 strcat( buf, "DEBUG (recursed): " );
168 strcat( buf, "DEBUG: " );
172 /* we are used for a log level that is not defined by GLib itself,
173 * try to make the best out of it.
175 if ( in_recursion ) {
176 strcat( buf, "LOG (recursed:" );
179 strcat( buf, "LOG (" );
182 gchar string[] = "0x00): ";
183 gchar *p = string + 2;
186 i = g_bit_nth_msf( log_level, -1 );
189 *p = '0' + ( i & 0xf );
194 strcat( buf, string );
197 strcat( buf, "): " );
201 strcat( buf, message );
203 strcat( buf, "\naborting...\n" );
210 globalErrorStream() << buf << "\n";
212 bool worth_showing = strcmp(domain,"Gdk") != 0;
214 // FIXME why are warnings is_fatal?
216 worth_showing = worth_showing && is_fatal;
220 ERROR_MESSAGE( "GTK+ error: " << buf );
223 #if defined ( _DEBUG ) && defined ( WIN32 ) && defined ( _MSC_VER )
228 #if defined ( _DEBUG ) && defined ( WIN32 ) && defined ( _MSC_VER )
229 _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
237 Lock() : m_locked( false ){
245 bool locked() const {
254 ScopedLock( Lock& lock ) : m_lock( lock ){
262 class LineLimitedTextOutputStream : public TextOutputStream
264 TextOutputStream& outputStream;
267 LineLimitedTextOutputStream( TextOutputStream& outputStream, std::size_t count )
268 : outputStream( outputStream ), count( count ){
270 std::size_t write( const char* buffer, std::size_t length ){
272 const char* p = buffer;
273 const char* end = buffer + length;
276 p = std::find( p, end, '\n' );
281 if ( --count == 0 ) {
286 outputStream.write( buffer, length );
292 class PopupDebugMessageHandler : public DebugMessageHandler
294 StringOutputStream m_buffer;
297 TextOutputStream& getOutputStream(){
298 if ( !m_lock.locked() ) {
301 return globalErrorStream();
303 bool handleMessage(){
304 getOutputStream() << "----------------\n";
305 LineLimitedTextOutputStream outputStream( getOutputStream(), 24 );
306 write_stack_trace( outputStream );
307 getOutputStream() << "----------------\n";
308 globalErrorStream() << m_buffer.c_str();
309 if ( !m_lock.locked() ) {
310 ScopedLock lock( m_lock );
312 m_buffer << "Break into the debugger?\n";
313 bool handled = ui::root.alert( m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::YESNO, ui::alert_icon::ERROR ) == ui::alert_response::NO;
317 m_buffer << "Please report this error to the developers\n";
318 ui::root.alert( m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::OK, ui::alert_icon::ERROR );
326 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
329 GlobalErrorStream::instance().setOutputStream( getSysPrintErrorStream() );
330 GlobalOutputStream::instance().setOutputStream( getSysPrintOutputStream() );
334 const char* home = environment_get_home_path();
338 StringOutputStream path( 256 );
339 path << home << "1." << radiant::version_major() << "." << radiant::version_minor() << '/';
340 g_strSettingsPath = path.c_str();
343 Q_mkdir( g_strSettingsPath.c_str() );
345 g_strAppPath = environment_get_app_path();
347 // radiant is installed in the parent dir of "tools/"
348 // NOTE: this is not very easy for debugging
349 // maybe add options to lookup in several places?
350 // (for now I had to create symlinks)
352 StringOutputStream path( 256 );
353 path << g_strAppPath.c_str() << "bitmaps/";
354 BitmapsPath_set( path.c_str() );
357 // we will set this right after the game selection is done
358 g_strGameToolsPath = g_strAppPath;
361 bool check_version_file( const char* filename, const char* version ){
362 TextFileInputStream file( filename );
363 if ( !file.failed() ) {
365 buf[file.read( buf, 9 )] = '\0';
367 // chomp it (the hard way)
369 while ( buf[chomp] >= '0' && buf[chomp] <= '9' )
373 return string_equal( buf, version );
378 bool check_version(){
379 // a safe check to avoid people running broken code
380 // (otherwise, they run it, don't crash it, wait 20 years and blame us for not maintaining this hard enough)
381 // make something idiot proof and someone will make better idiots, this may be overkill
382 // let's leave it disabled in any case
383 /// \todo actually remove this and check if the functions called from here
384 /// are used elsewhere; if not, remove them.
386 /* // a safe check to avoid people running broken installations
387 // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
388 // make something idiot proof and someone will make better idiots, this may be overkill
389 // let's leave it disabled in debug mode in any case
390 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
392 #define CHECK_VERSION
395 // locate and open RADIANT_MAJOR and RADIANT_MINOR
396 bool bVerIsGood = true;
398 StringOutputStream ver_file_name( 256 );
399 ver_file_name << AppPath_get() << "RADIANT_MAJOR";
400 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MAJOR_VERSION );
403 StringOutputStream ver_file_name( 256 );
404 ver_file_name << AppPath_get() << "RADIANT_MINOR";
405 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MINOR_VERSION );
409 StringOutputStream msg( 256 );
410 msg << "This editor binary ("
411 << radiant::version()
412 << ") doesn't match what the latest setup has configured in this directory\n"
413 "Make sure you run the right/latest editor binary you installed\n"
415 ui::alert( 0, msg.c_str(), "Radiant", eMB_OK, eMB_ICONDEFAULT );
423 void create_global_pid(){
425 the global prefs loading / game selection dialog might fail for any reason we don't know about
426 we need to catch when it happens, to cleanup the stateful prefs which might be killing it
427 and to turn on console logging for lookup of the problem
428 this is the first part of the two step .pid system
429 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
431 StringOutputStream g_pidFile( 256 ); ///< the global .pid file (only for global part of the startup)
433 g_pidFile << SettingsPath_get() << "radiant.pid";
436 pid = fopen( g_pidFile.c_str(), "r" );
440 if ( remove( g_pidFile.c_str() ) == -1 ) {
441 StringOutputStream msg( 256 );
442 msg << "WARNING: Could not delete " << g_pidFile.c_str();
443 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::ERROR );
446 // in debug, never prompt to clean registry, turn console logging auto after a failed start
447 #if !defined( _DEBUG )
448 StringOutputStream msg( 256 );
449 msg << "Radiant failed to start properly the last time it was run.\n"
450 "The failure may be related to current global preferences.\n"
451 "Do you want to reset global preferences to defaults?";
453 if ( ui::root.alert( msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::QUESTION ) == ui::alert_response::YES ) {
454 g_GamesDialog.Reset();
458 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
460 ui::root.alert( msg.c_str(), "Radiant - Console Log", ui::alert_type::OK );
463 // set without saving, the class is not in a coherent state yet
464 // just do the value change and call to start logging, CGamesDialog will pickup when relevant
465 g_GamesDialog.m_bForceLogConsole = true;
469 // create a primary .pid for global init run
470 pid = fopen( g_pidFile.c_str(), "w" );
476 void remove_global_pid(){
477 StringOutputStream g_pidFile( 256 );
478 g_pidFile << SettingsPath_get() << "radiant.pid";
481 if ( remove( g_pidFile.c_str() ) == -1 ) {
482 StringOutputStream msg( 256 );
483 msg << "WARNING: Could not delete " << g_pidFile.c_str();
484 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::ERROR );
489 now the secondary game dependant .pid file
490 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
492 void create_local_pid(){
493 StringOutputStream g_pidGameFile( 256 ); ///< the game-specific .pid file
494 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
496 FILE *pid = fopen( g_pidGameFile.c_str(), "r" );
499 if ( remove( g_pidGameFile.c_str() ) == -1 ) {
500 StringOutputStream msg;
501 msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
502 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::ERROR );
505 // in debug, never prompt to clean registry, turn console logging auto after a failed start
506 #if !defined( _DEBUG )
507 StringOutputStream msg;
508 msg << "Radiant failed to start properly the last time it was run.\n"
509 "The failure may be caused by current preferences.\n"
510 "Do you want to reset all preferences to defaults?";
512 if ( ui::root.alert( msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::QUESTION ) == ui::alert_response::YES ) {
517 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
519 ui::root.alert( msg.c_str(), "Radiant - Console Log", ui::alert_type::OK );
522 // force console logging on! (will go in prefs too)
523 g_GamesDialog.m_bForceLogConsole = true;
528 // create one, will remove right after entering message loop
529 pid = fopen( g_pidGameFile.c_str(), "w" );
538 now the secondary game dependant .pid file
539 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
541 void remove_local_pid(){
542 StringOutputStream g_pidGameFile( 256 );
543 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
544 remove( g_pidGameFile.c_str() );
547 void user_shortcuts_init(){
548 StringOutputStream path( 256 );
549 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
550 LoadCommandMap( path.c_str() );
551 SaveCommandMap( path.c_str() );
554 void user_shortcuts_save(){
555 StringOutputStream path( 256 );
556 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
557 SaveCommandMap( path.c_str() );
560 int main( int argc, char* argv[] ){
567 lib = LoadLibrary( "dwmapi.dll" );
569 void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" );
570 if ( qDwmEnableComposition ) {
571 qDwmEnableComposition( FALSE );
577 ui::init(argc, argv);
579 // redirect Gtk warnings to the console
580 g_log_set_handler( "Gdk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
581 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
582 g_log_set_handler( "Gtk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
583 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
584 g_log_set_handler( "GtkGLExt", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
585 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
586 g_log_set_handler( "GLib", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
587 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
588 g_log_set_handler( nullptr, (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
589 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
591 GlobalDebugMessageHandler::instance().setHandler( GlobalPopupDebugMessageHandler::instance() );
593 environment_init( argc, argv );
597 if ( !check_version() ) {
605 GlobalPreferences_Init();
607 g_GamesDialog.Init();
609 g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
613 g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
617 // in a very particular post-.pid startup
618 // we may have the console turned on and want to keep it that way
619 // so we use a latching system
620 if ( g_GamesDialog.m_bForceLogConsole ) {
622 g_Console_enableLogging = true;
623 g_GamesDialog.m_bForceLogConsole = false;
627 Radiant_Initialise();
631 user_shortcuts_init();
634 g_pParentWnd = new MainFrame();
638 if ( g_bLoadLastMap && !g_strLastMap.empty() ) {
639 Map_LoadFile( g_strLastMap.c_str() );
646 // load up shaders now that we have the map loaded
648 TextureBrowser_ShowStartupShaders( GlobalTextureBrowser() );
655 // avoid saving prefs when the app is minimized
656 if ( g_pParentWnd->IsSleeping() ) {
657 globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
658 g_preferences_globals.disable_ini = true;
663 if ( !Map_Unnamed( g_map ) ) {
664 g_strLastMap = Map_Name( g_map );
669 user_shortcuts_save();
671 global_accel_destroy();
675 // close the log file if any
676 Sys_LogFile( false );