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"
92 #include <util/buffer.h>
101 void error_redirect( const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data ){
102 gboolean in_recursion;
104 auto buf = u::buffer<256>();
106 in_recursion = ( log_level & G_LOG_FLAG_RECURSION ) != 0;
107 is_fatal = ( log_level & G_LOG_FLAG_FATAL ) != 0;
108 log_level = (GLogLevelFlags) ( log_level & G_LOG_LEVEL_MASK );
111 message = "(0) message";
120 strcat( buf.mut(), "-" );
124 case G_LOG_LEVEL_ERROR:
125 if ( in_recursion ) {
126 strcat( buf.mut(), "ERROR (recursed) **: " );
129 strcat( buf.mut(), "ERROR **: " );
132 case G_LOG_LEVEL_CRITICAL:
133 if ( in_recursion ) {
134 strcat( buf.mut(), "CRITICAL (recursed) **: " );
137 strcat( buf.mut(), "CRITICAL **: " );
140 case G_LOG_LEVEL_WARNING:
141 if ( in_recursion ) {
142 strcat( buf.mut(), "WARNING (recursed) **: " );
145 strcat( buf.mut(), "WARNING **: " );
148 case G_LOG_LEVEL_MESSAGE:
149 if ( in_recursion ) {
150 strcat( buf.mut(), "Message (recursed): " );
153 strcat( buf.mut(), "Message: " );
156 case G_LOG_LEVEL_INFO:
157 if ( in_recursion ) {
158 strcat( buf.mut(), "INFO (recursed): " );
161 strcat( buf.mut(), "INFO: " );
164 case G_LOG_LEVEL_DEBUG:
165 if ( in_recursion ) {
166 strcat( buf.mut(), "DEBUG (recursed): " );
169 strcat( buf.mut(), "DEBUG: " );
173 /* we are used for a log level that is not defined by GLib itself,
174 * try to make the best out of it.
176 if ( in_recursion ) {
177 strcat( buf.mut(), "LOG (recursed:" );
180 strcat( buf.mut(), "LOG (" );
183 gchar string[] = "0x00): ";
184 gchar *p = string + 2;
187 i = g_bit_nth_msf( log_level, -1 );
190 *p = '0' + ( i & 0xf );
195 strcat( buf.mut(), string );
198 strcat( buf.mut(), "): " );
202 strcat( buf.mut(), message );
204 strcat( buf.mut(), "\naborting...\n" );
207 strcat( buf.mut(), "\n" );
211 globalErrorStream() << buf << "\n";
214 ERROR_MESSAGE( "GTK+ error: " << buf );
218 #if defined ( _DEBUG ) && defined ( WIN32 ) && defined ( _MSC_VER )
223 #if defined ( _DEBUG ) && defined ( WIN32 ) && defined ( _MSC_VER )
224 _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
232 Lock() : m_locked( false ){
240 bool locked() const {
249 ScopedLock( Lock& lock ) : m_lock( lock ){
257 class LineLimitedTextOutputStream : public TextOutputStream
259 TextOutputStream& outputStream;
262 LineLimitedTextOutputStream( TextOutputStream& outputStream, std::size_t count )
263 : outputStream( outputStream ), count( count ){
265 std::size_t write( const char* buffer, std::size_t length ){
267 const char* p = buffer;
268 const char* end = buffer + length;
271 p = std::find( p, end, '\n' );
276 if ( --count == 0 ) {
281 outputStream.write( buffer, length );
287 class PopupDebugMessageHandler : public DebugMessageHandler
289 StringOutputStream m_buffer;
292 TextOutputStream& getOutputStream(){
293 if ( !m_lock.locked() ) {
296 return globalErrorStream();
298 bool handleMessage(){
299 getOutputStream() << "----------------\n";
300 LineLimitedTextOutputStream outputStream( getOutputStream(), 24 );
301 write_stack_trace( outputStream );
302 getOutputStream() << "----------------\n";
303 globalErrorStream() << m_buffer.c_str();
304 if ( !m_lock.locked() ) {
305 ScopedLock lock( m_lock );
307 m_buffer << "Break into the debugger?\n";
308 bool handled = ui::root.alert( m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::YESNO, ui::alert_icon::Error ) == ui::alert_response::NO;
312 m_buffer << "Please report this error to the developers\n";
313 ui::root.alert( m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::OK, ui::alert_icon::Error );
321 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
324 GlobalErrorStream::instance().setOutputStream( getSysPrintErrorStream() );
325 GlobalOutputStream::instance().setOutputStream( getSysPrintOutputStream() );
329 g_strSettingsPath = environment_get_home_path();
331 Q_mkdir( g_strSettingsPath.c_str() );
333 g_strAppPath = environment_get_app_path();
335 // radiant is installed in the parent dir of "tools/"
336 // NOTE: this is not very easy for debugging
337 // maybe add options to lookup in several places?
338 // (for now I had to create symlinks)
340 StringOutputStream path( 256 );
341 path << g_strAppPath.c_str() << "bitmaps/";
342 BitmapsPath_set( path.c_str() );
345 // we will set this right after the game selection is done
346 g_strGameToolsPath = g_strAppPath;
349 bool check_version_file( const char* filename, const char* version ){
350 TextFileInputStream file( filename );
351 if ( !file.failed() ) {
352 auto buf = u::buffer<10>();
353 auto mut_buf = buf.mut();
354 mut_buf[file.read(mut_buf, 9 )] = '\0';
356 // chomp it (the hard way)
358 while (mut_buf[chomp] >= '0' && mut_buf[chomp] <= '9' )
360 mut_buf[chomp] = '\0';
362 return string_equal(mut_buf, version );
367 bool check_version(){
368 // a safe check to avoid people running broken installations
369 // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
370 // make something idiot proof and someone will make better idiots, this may be overkill
371 // let's leave it disabled in debug mode in any case
372 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
374 #define CHECK_VERSION
377 // locate and open RADIANT_MAJOR and RADIANT_MINOR
378 bool bVerIsGood = true;
380 StringOutputStream ver_file_name( 256 );
381 ver_file_name << AppPath_get() << "RADIANT_MAJOR";
382 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MAJOR_VERSION );
385 StringOutputStream ver_file_name( 256 );
386 ver_file_name << AppPath_get() << "RADIANT_MINOR";
387 bVerIsGood = check_version_file( ver_file_name.c_str(), RADIANT_MINOR_VERSION );
391 StringOutputStream msg( 256 );
392 msg << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n"
393 "Make sure you run the right/latest editor binary you installed\n"
395 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Default);
403 void create_global_pid(){
405 the global prefs loading / game selection dialog might fail for any reason we don't know about
406 we need to catch when it happens, to cleanup the stateful prefs which might be killing it
407 and to turn on console logging for lookup of the problem
408 this is the first part of the two step .pid system
409 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
411 StringOutputStream g_pidFile( 256 ); ///< the global .pid file (only for global part of the startup)
413 g_pidFile << SettingsPath_get() << "radiant.pid";
416 pid = fopen( g_pidFile.c_str(), "r" );
420 if ( remove( g_pidFile.c_str() ) == -1 ) {
421 StringOutputStream msg( 256 );
422 msg << "WARNING: Could not delete " << g_pidFile.c_str();
423 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
426 // in debug, never prompt to clean registry, turn console logging auto after a failed start
427 #if !defined( _DEBUG )
428 StringOutputStream msg( 256 );
429 msg << "Radiant failed to start properly the last time it was run.\n"
430 "The failure may be related to current global preferences.\n"
431 "Do you want to reset global preferences to defaults?";
433 if ( ui::root.alert( msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::YES ) {
434 g_GamesDialog.Reset();
438 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
440 ui::root.alert( msg.c_str(), "Radiant - Console Log", ui::alert_type::OK );
443 // set without saving, the class is not in a coherent state yet
444 // just do the value change and call to start logging, CGamesDialog will pickup when relevant
445 g_GamesDialog.m_bForceLogConsole = true;
449 // create a primary .pid for global init run
450 pid = fopen( g_pidFile.c_str(), "w" );
456 void remove_global_pid(){
457 StringOutputStream g_pidFile( 256 );
458 g_pidFile << SettingsPath_get() << "radiant.pid";
461 if ( remove( g_pidFile.c_str() ) == -1 ) {
462 StringOutputStream msg( 256 );
463 msg << "WARNING: Could not delete " << g_pidFile.c_str();
464 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
469 now the secondary game dependant .pid file
470 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
472 void create_local_pid(){
473 StringOutputStream g_pidGameFile( 256 ); ///< the game-specific .pid file
474 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
476 FILE *pid = fopen( g_pidGameFile.c_str(), "r" );
479 if ( remove( g_pidGameFile.c_str() ) == -1 ) {
480 StringOutputStream msg;
481 msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
482 ui::root.alert( msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error );
485 // in debug, never prompt to clean registry, turn console logging auto after a failed start
486 #if !defined( _DEBUG )
487 StringOutputStream msg;
488 msg << "Radiant failed to start properly the last time it was run.\n"
489 "The failure may be caused by current preferences.\n"
490 "Do you want to reset all preferences to defaults?";
492 if ( ui::root.alert( msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO, ui::alert_icon::Question ) == ui::alert_response::YES ) {
497 msg << "Logging console output to " << SettingsPath_get() << "radiant.log\nRefer to the log if Radiant fails to start again.";
499 ui::root.alert( msg.c_str(), "Radiant - Console Log", ui::alert_type::OK );
502 // force console logging on! (will go in prefs too)
503 g_GamesDialog.m_bForceLogConsole = true;
508 // create one, will remove right after entering message loop
509 pid = fopen( g_pidGameFile.c_str(), "w" );
518 now the secondary game dependant .pid file
519 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
521 void remove_local_pid(){
522 StringOutputStream g_pidGameFile( 256 );
523 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
524 remove( g_pidGameFile.c_str() );
527 void user_shortcuts_init(){
528 StringOutputStream path( 256 );
529 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
530 LoadCommandMap( path.c_str() );
531 SaveCommandMap( path.c_str() );
534 void user_shortcuts_save(){
535 StringOutputStream path( 256 );
536 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
537 SaveCommandMap( path.c_str() );
540 int main( int argc, char* argv[] ){
547 lib = LoadLibrary( "dwmapi.dll" );
549 void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" );
550 if ( qDwmEnableComposition ) {
551 qDwmEnableComposition( FALSE );
557 const char* mapname = NULL;
558 const char *error = NULL;
559 if ( !ui::init( &argc, &argv, "<filename.map>", &error) ) {
560 g_print( "%s\n", error );
564 // Gtk already removed parsed `--options`
566 if ( strlen( argv[1] ) > 1 ) {
567 if ( g_str_has_suffix( argv[1], ".map" ) ) {
568 if ( g_path_is_absolute( argv[1] ) ) {
572 mapname = g_build_filename( g_get_current_dir(), argv[1], NULL );
576 g_print( "bad file name, will not load: %s\n", argv[1] );
581 g_print ( "%s\n", "too many arguments" );
585 // redirect Gtk warnings to the console
586 g_log_set_handler( "Gdk", (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( "Gtk", (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 );
590 g_log_set_handler( "GtkGLExt", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
591 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
592 g_log_set_handler( "GLib", (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
593 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
594 g_log_set_handler( 0, (GLogLevelFlags)( G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
595 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION ), error_redirect, 0 );
597 GlobalDebugMessageHandler::instance().setHandler( GlobalPopupDebugMessageHandler::instance() );
599 environment_init(argc, (const char **) argv);
603 if ( !check_version() ) {
611 GlobalPreferences_Init();
613 g_GamesDialog.Init();
615 g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
619 g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
623 // in a very particular post-.pid startup
624 // we may have the console turned on and want to keep it that way
625 // so we use a latching system
626 if ( g_GamesDialog.m_bForceLogConsole ) {
628 g_Console_enableLogging = true;
629 g_GamesDialog.m_bForceLogConsole = false;
633 Radiant_Initialise();
635 user_shortcuts_init();
638 g_pParentWnd = new MainFrame();
642 if ( mapname != NULL ) {
643 Map_LoadFile( mapname );
645 else if ( g_bLoadLastMap && !g_strLastMap.empty() ) {
646 Map_LoadFile( g_strLastMap.c_str() );
653 // load up shaders now that we have the map loaded
655 TextureBrowser_ShowStartupShaders( GlobalTextureBrowser() );
662 // avoid saving prefs when the app is minimized
663 if ( g_pParentWnd->IsSleeping() ) {
664 globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
665 g_preferences_globals.disable_ini = true;
670 if ( !Map_Unnamed( g_map ) ) {
671 g_strLastMap = Map_Name( g_map );
676 user_shortcuts_save();
680 // close the log file if any
681 Sys_LogFile( false );