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 ::CopiedString - 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"
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";
212 ERROR_MESSAGE( "GTK+ error: " << buf );
216 #if defined ( _DEBUG ) && defined ( WIN32 ) && defined ( _MSC_VER )
221 #if defined ( _DEBUG ) && defined ( WIN32 ) && defined ( _MSC_VER )
222 _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
230 Lock() : m_locked( false ){
238 bool locked() const {
247 ScopedLock( Lock& lock ) : m_lock( lock ){
255 class LineLimitedTextOutputStream : public TextOutputStream
257 TextOutputStream& outputStream;
260 LineLimitedTextOutputStream( TextOutputStream& outputStream, std::size_t count )
261 : outputStream( outputStream ), count( count ){
263 std::size_t write( const char* buffer, std::size_t length ){
265 const char* p = buffer;
266 const char* end = buffer + length;
269 p = std::find( p, end, '\n' );
274 if ( --count == 0 ) {
279 outputStream.write( buffer, length );
285 class PopupDebugMessageHandler : public DebugMessageHandler
287 StringOutputStream m_buffer;
290 TextOutputStream& getOutputStream(){
291 if ( !m_lock.locked() ) {
294 return globalErrorStream();
296 bool handleMessage(){
297 getOutputStream() << "----------------\n";
298 LineLimitedTextOutputStream outputStream( getOutputStream(), 24 );
299 write_stack_trace( outputStream );
300 getOutputStream() << "----------------\n";
301 globalErrorStream() << m_buffer.c_str();
302 if ( !m_lock.locked() ) {
303 ScopedLock lock( m_lock );
305 m_buffer << "Break into the debugger?\n";
306 bool handled = ui::root.alert( m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::YESNO, ui::alert_icon::Error ) == ui::alert_response::NO;
310 m_buffer << "Please report this error to the developers\n";
311 ui::root.alert( m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::OK, ui::alert_icon::Error );
319 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
322 GlobalErrorStream::instance().setOutputStream( getSysPrintErrorStream() );
323 GlobalOutputStream::instance().setOutputStream( getSysPrintOutputStream() );
327 const char* home = environment_get_home_path();
331 StringOutputStream path( 256 );
333 g_strSettingsPath = path.c_str();
336 Q_mkdir( g_strSettingsPath.c_str() );
338 g_strAppPath = environment_get_app_path();
340 // radiant is installed in the parent dir of "tools/"
341 // NOTE: this is not very easy for debugging
342 // maybe add options to lookup in several places?
343 // (for now I had to create symlinks)
345 StringOutputStream path( 256 );
346 path << g_strAppPath.c_str() << "bitmaps/";
347 BitmapsPath_set( path.c_str() );
350 // we will set this right after the game selection is done
351 g_strGameToolsPath = g_strAppPath;
354 bool check_version_file( const char* filename, const char* version ){
355 TextFileInputStream file( filename );
356 if ( !file.failed() ) {
358 buf[file.read( buf, 9 )] = '\0';
360 // chomp it (the hard way)
362 while ( buf[chomp] >= '0' && buf[chomp] <= '9' )
366 return string_equal( buf, version );
371 bool check_version(){
372 // a safe check to avoid people running broken installations
373 // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
374 // make something idiot proof and someone will make better idiots, this may be overkill
375 // let's leave it disabled in debug mode in any case
376 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
378 #define CHECK_VERSION
381 // locate and open RADIANT_MAJOR and RADIANT_MINOR
382 bool bVerIsGood = true;
384 StringOutputStream ver_file_name( 256 );
385 ver_file_name << AppPath_get() << "RADIANT_MAJOR";
386 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MAJOR_VERSION );
389 StringOutputStream ver_file_name( 256 );
390 ver_file_name << AppPath_get() << "RADIANT_MINOR";
391 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MINOR_VERSION );
395 StringOutputStream msg( 256 );
396 msg << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n"
397 "Make sure you run the right/latest editor binary you installed\n"
399 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Default);
407 void create_global_pid(){
409 the global prefs loading / game selection dialog might fail for any reason we don't know about
410 we need to catch when it happens, to cleanup the stateful prefs which might be killing it
411 and to turn on console logging for lookup of the problem
412 this is the first part of the two step .pid system
413 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
415 StringOutputStream g_pidFile( 256 ); ///< the global .pid file (only for global part of the startup)
417 g_pidFile << SettingsPath_get() << "radiant.pid";
420 pid = fopen( g_pidFile.c_str(), "r" );
424 if ( remove( g_pidFile.c_str() ) == -1 ) {
425 StringOutputStream msg( 256 );
426 msg << "WARNING: Could not delete " << g_pidFile.c_str();
427 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
430 // in debug, never prompt to clean registry, turn console logging auto after a failed start
431 #if !defined( _DEBUG )
432 StringOutputStream msg( 256 );
433 msg << "Radiant failed to start properly the last time it was run.\n"
434 "The failure may be related to current global preferences.\n"
435 "Do you want to reset global preferences to defaults?";
437 if ( ui::root.alert( msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::YES ) {
438 g_GamesDialog.Reset();
442 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
444 ui::root.alert( msg.c_str(), "Radiant - Console Log", ui::alert_type::OK );
447 // set without saving, the class is not in a coherent state yet
448 // just do the value change and call to start logging, CGamesDialog will pickup when relevant
449 g_GamesDialog.m_bForceLogConsole = true;
453 // create a primary .pid for global init run
454 pid = fopen( g_pidFile.c_str(), "w" );
460 void remove_global_pid(){
461 StringOutputStream g_pidFile( 256 );
462 g_pidFile << SettingsPath_get() << "radiant.pid";
465 if ( remove( g_pidFile.c_str() ) == -1 ) {
466 StringOutputStream msg( 256 );
467 msg << "WARNING: Could not delete " << g_pidFile.c_str();
468 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
473 now the secondary game dependant .pid file
474 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
476 void create_local_pid(){
477 StringOutputStream g_pidGameFile( 256 ); ///< the game-specific .pid file
478 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
480 FILE *pid = fopen( g_pidGameFile.c_str(), "r" );
483 if ( remove( g_pidGameFile.c_str() ) == -1 ) {
484 StringOutputStream msg;
485 msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
486 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
489 // in debug, never prompt to clean registry, turn console logging auto after a failed start
490 #if !defined( _DEBUG )
491 StringOutputStream msg;
492 msg << "Radiant failed to start properly the last time it was run.\n"
493 "The failure may be caused by current preferences.\n"
494 "Do you want to reset all preferences to defaults?";
496 if ( ui::root.alert( msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::YES ) {
501 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
503 ui::root.alert( msg.c_str(), "Radiant - Console Log", ui::alert_type::OK );
506 // force console logging on! (will go in prefs too)
507 g_GamesDialog.m_bForceLogConsole = true;
512 // create one, will remove right after entering message loop
513 pid = fopen( g_pidGameFile.c_str(), "w" );
522 now the secondary game dependant .pid file
523 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
525 void remove_local_pid(){
526 StringOutputStream g_pidGameFile( 256 );
527 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
528 remove( g_pidGameFile.c_str() );
531 void user_shortcuts_init(){
532 StringOutputStream path( 256 );
533 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
534 LoadCommandMap( path.c_str() );
535 SaveCommandMap( path.c_str() );
538 void user_shortcuts_save(){
539 StringOutputStream path( 256 );
540 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
541 SaveCommandMap( path.c_str() );
544 int main( int argc, char* argv[] ){
551 lib = LoadLibrary( "dwmapi.dll" );
553 void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" );
554 if ( qDwmEnableComposition ) {
555 qDwmEnableComposition( FALSE );
561 static GOptionEntry entries[] = {
564 GError *error = NULL;
565 const char* mapname = NULL;
567 gtk_disable_setlocale();
568 if ( !gtk_init_with_args( &argc, &argv, "<filename.map>", entries, NULL, &error) ) {
569 g_print( "%s\n", error->message );
573 // Gtk already removed parsed `--options`
575 if ( strlen( argv[1] ) > 1 ) {
576 if ( g_str_has_suffix( argv[1], ".map" ) ) {
577 if ( g_path_is_absolute( argv[1] ) ) {
581 mapname = g_build_filename( g_get_current_dir(), argv[1], NULL );
585 g_print( "bad file name, will not load: %s\n", argv[1] );
590 g_print ( "%s\n", "too many arguments" );
594 // redirect Gtk warnings to the console
595 g_log_set_handler( "Gdk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
596 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
597 g_log_set_handler( "Gtk", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
598 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
599 g_log_set_handler( "GtkGLExt", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
600 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
601 g_log_set_handler( "GLib", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
602 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
603 g_log_set_handler( 0, (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
604 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
606 GlobalDebugMessageHandler::instance().setHandler( GlobalPopupDebugMessageHandler::instance() );
608 environment_init( argc, argv );
612 if ( !check_version() ) {
620 GlobalPreferences_Init();
622 g_GamesDialog.Init();
624 g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
628 g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
632 // in a very particular post-.pid startup
633 // we may have the console turned on and want to keep it that way
634 // so we use a latching system
635 if ( g_GamesDialog.m_bForceLogConsole ) {
637 g_Console_enableLogging = true;
638 g_GamesDialog.m_bForceLogConsole = false;
642 Radiant_Initialise();
644 user_shortcuts_init();
647 g_pParentWnd = new MainFrame();
651 if ( mapname != NULL ) {
652 Map_LoadFile( mapname );
654 else if ( g_bLoadLastMap && !g_strLastMap.empty() ) {
655 Map_LoadFile( g_strLastMap.c_str() );
662 // load up shaders now that we have the map loaded
664 TextureBrowser_ShowStartupShaders( GlobalTextureBrowser() );
671 // avoid saving prefs when the app is minimized
672 if ( g_pParentWnd->IsSleeping() ) {
673 globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
674 g_preferences_globals.disable_ini = true;
679 if ( !Map_Unnamed( g_map ) ) {
680 g_strLastMap = Map_Name( g_map );
685 user_shortcuts_save();
689 // close the log file if any
690 Sys_LogFile( false );