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 MemberCaller0 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
66 #include "globaldefs.h"
70 #include "debugging/debugging.h"
74 #include "uilib/uilib.h"
79 #include "stream/stringstream.h"
80 #include "stream/textfilestream.h"
82 #include "gtkutil/messagebox.h"
83 #include "gtkutil/image.h"
85 #include "texwindow.h"
87 #include "mainframe.h"
89 #include "preferences.h"
90 #include "environment.h"
91 #include "referencecache.h"
92 #include "stacktrace.h"
102 void error_redirect(const gchar *domain, GLogLevelFlags log_level, const gchar *message, gpointer user_data)
104 gboolean in_recursion;
108 in_recursion = (log_level & G_LOG_FLAG_RECURSION) != 0;
109 is_fatal = (log_level & G_LOG_FLAG_FATAL) != 0;
110 log_level = (GLogLevelFlags) (log_level & G_LOG_LEVEL_MASK);
113 message = "(0) message";
124 case G_LOG_LEVEL_ERROR:
126 strcat(buf, "ERROR (recursed) **: ");
128 strcat(buf, "ERROR **: ");
131 case G_LOG_LEVEL_CRITICAL:
133 strcat(buf, "CRITICAL (recursed) **: ");
135 strcat(buf, "CRITICAL **: ");
138 case G_LOG_LEVEL_WARNING:
140 strcat(buf, "WARNING (recursed) **: ");
142 strcat(buf, "WARNING **: ");
145 case G_LOG_LEVEL_MESSAGE:
147 strcat(buf, "Message (recursed): ");
149 strcat(buf, "Message: ");
152 case G_LOG_LEVEL_INFO:
154 strcat(buf, "INFO (recursed): ");
156 strcat(buf, "INFO: ");
159 case G_LOG_LEVEL_DEBUG:
161 strcat(buf, "DEBUG (recursed): ");
163 strcat(buf, "DEBUG: ");
167 /* we are used for a log level that is not defined by GLib itself,
168 * try to make the best out of it.
171 strcat(buf, "LOG (recursed:");
173 strcat(buf, "LOG (");
176 gchar string[] = "0x00): ";
177 gchar *p = string + 2;
180 i = g_bit_nth_msf(log_level, -1);
183 *p = '0' + (i & 0xf);
194 strcat(buf, message);
196 strcat(buf, "\naborting...\n");
202 globalErrorStream() << buf << "\n";
205 ERROR_MESSAGE("GTK+ error: " << buf);
209 #if GDEF_COMPILER_MSVC && GDEF_DEBUG
215 #if GDEF_COMPILER_MSVC && GDEF_DEBUG
216 _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
223 Lock() : m_locked(false)
246 ScopedLock(Lock &lock) : m_lock(lock)
257 class LineLimitedTextOutputStream : public TextOutputStream {
258 TextOutputStream &outputStream;
261 LineLimitedTextOutputStream(TextOutputStream &outputStream, std::size_t count)
262 : outputStream(outputStream), count(count)
266 std::size_t write(const char *buffer, std::size_t length)
269 const char *p = buffer;
270 const char *end = buffer + length;
272 p = std::find(p, end, '\n');
282 outputStream.write(buffer, length);
288 class PopupDebugMessageHandler : public DebugMessageHandler {
289 StringOutputStream m_buffer;
292 TextOutputStream &getOutputStream()
294 if (!m_lock.locked()) {
297 return globalErrorStream();
302 getOutputStream() << "----------------\n";
303 LineLimitedTextOutputStream outputStream(getOutputStream(), 24);
304 write_stack_trace(outputStream);
305 getOutputStream() << "----------------\n";
306 globalErrorStream() << m_buffer.c_str();
307 if (!m_lock.locked()) {
308 ScopedLock lock(m_lock);
310 m_buffer << "Break into the debugger?\n";
311 bool handled = ui::alert(ui::root, m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::YESNO,
312 ui::alert_icon::Error) == ui::alert_response::NO;
316 m_buffer << "Please report this error to the developers\n";
317 ui::alert(ui::root, m_buffer.c_str(), "Radiant - Runtime Error", ui::alert_type::OK,
318 ui::alert_icon::Error);
326 typedef Static<PopupDebugMessageHandler> GlobalPopupDebugMessageHandler;
330 GlobalErrorStream::instance().setOutputStream(getSysPrintErrorStream());
331 GlobalOutputStream::instance().setOutputStream(getSysPrintOutputStream());
336 g_strSettingsPath = environment_get_home_path();
338 Q_mkdir(g_strSettingsPath.c_str());
340 g_strAppPath = environment_get_app_path();
342 // radiant is installed in the parent dir of "tools/"
343 // NOTE: this is not very easy for debugging
344 // maybe add options to lookup in several places?
345 // (for now I had to create symlinks)
347 StringOutputStream path(256);
348 path << g_strAppPath.c_str() << "bitmaps/";
349 BitmapsPath_set(path.c_str());
352 // we will set this right after the game selection is done
353 g_strGameToolsPath = g_strAppPath;
356 bool check_version_file(const char *filename, const char *version)
358 TextFileInputStream file(filename);
359 if (!file.failed()) {
361 buf[file.read(buf, 9)] = '\0';
363 // chomp it (the hard way)
365 while (buf[chomp] >= '0' && buf[chomp] <= '9') {
370 return string_equal(buf, version);
377 // a safe check to avoid people running broken installations
378 // (otherwise, they run it, crash it, and blame us for not forcing them hard enough to pay attention while installing)
379 // make something idiot proof and someone will make better idiots, this may be overkill
380 // let's leave it disabled in debug mode in any case
381 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=431
385 // locate and open RADIANT_MAJOR and RADIANT_MINOR
386 bool bVerIsGood = true;
388 StringOutputStream ver_file_name(256);
389 ver_file_name << AppPath_get() << "RADIANT_MAJOR";
390 bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MAJOR_VERSION);
393 StringOutputStream ver_file_name(256);
394 ver_file_name << AppPath_get() << "RADIANT_MINOR";
395 bVerIsGood = check_version_file(ver_file_name.c_str(), RADIANT_MINOR_VERSION);
399 StringOutputStream msg(256);
401 << "This editor binary (" RADIANT_VERSION ") doesn't match what the latest setup has configured in this directory\n"
402 "Make sure you run the right/latest editor binary you installed\n"
404 ui::alert(ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Default);
409 void create_global_pid()
412 the global prefs loading / game selection dialog might fail for any reason we don't know about
413 we need to catch when it happens, to cleanup the stateful prefs which might be killing it
414 and to turn on console logging for lookup of the problem
415 this is the first part of the two step .pid system
416 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
418 StringOutputStream g_pidFile(256); ///< the global .pid file (only for global part of the startup)
420 g_pidFile << SettingsPath_get() << "radiant.pid";
423 pid = fopen(g_pidFile.c_str(), "r");
427 if (remove(g_pidFile.c_str()) == -1) {
428 StringOutputStream msg(256);
429 msg << "WARNING: Could not delete " << g_pidFile.c_str();
430 ui::alert(ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error);
433 // in debug, never prompt to clean registry, turn console logging auto after a failed start
435 StringOutputStream msg(256);
436 msg << "Radiant failed to start properly the last time it was run.\n"
437 "The failure may be related to current global preferences.\n"
438 "Do you want to reset global preferences to defaults?";
440 if (ui::alert(ui::root, msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO,
441 ui::alert_icon::Question) == ui::alert_response::YES) {
442 g_GamesDialog.Reset();
446 msg << "Logging console output to " << SettingsPath_get()
447 << "radiant.log\nRefer to the log if Radiant fails to start again.";
449 ui::alert(ui::root, msg.c_str(), "Radiant - Console Log", ui::alert_type::OK);
452 // set without saving, the class is not in a coherent state yet
453 // just do the value change and call to start logging, CGamesDialog will pickup when relevant
454 g_GamesDialog.m_bForceLogConsole = true;
458 // create a primary .pid for global init run
459 pid = fopen(g_pidFile.c_str(), "w");
465 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 ui::alert(ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error);
479 now the secondary game dependant .pid file
480 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
482 void create_local_pid()
484 StringOutputStream g_pidGameFile(256); ///< the game-specific .pid file
485 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
487 FILE *pid = fopen(g_pidGameFile.c_str(), "r");
490 if (remove(g_pidGameFile.c_str()) == -1) {
491 StringOutputStream msg;
492 msg << "WARNING: Could not delete " << g_pidGameFile.c_str();
493 ui::alert(ui::root, msg.c_str(), "Radiant", ui::alert_type::OK, ui::alert_icon::Error);
496 // in debug, never prompt to clean registry, turn console logging auto after a failed start
498 StringOutputStream msg;
499 msg << "Radiant failed to start properly the last time it was run.\n"
500 "The failure may be caused by current preferences.\n"
501 "Do you want to reset all preferences to defaults?";
503 if (ui::alert(ui::root, msg.c_str(), "Radiant - Startup Failure", ui::alert_type::YESNO,
504 ui::alert_icon::Question) == ui::alert_response::YES) {
509 msg << "Logging console output to " << SettingsPath_get()
510 << "radiant.log\nRefer to the log if Radiant fails to start again.";
512 ui::alert(ui::root, msg.c_str(), "Radiant - Console Log", ui::alert_type::OK);
515 // force console logging on! (will go in prefs too)
516 g_GamesDialog.m_bForceLogConsole = true;
519 // create one, will remove right after entering message loop
520 pid = fopen(g_pidGameFile.c_str(), "w");
529 now the secondary game dependant .pid file
530 http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=297
532 void remove_local_pid()
534 StringOutputStream g_pidGameFile(256);
535 g_pidGameFile << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << "/radiant-game.pid";
536 remove(g_pidGameFile.c_str());
539 void user_shortcuts_init()
541 StringOutputStream path(256);
542 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
543 LoadCommandMap(path.c_str());
544 SaveCommandMap(path.c_str());
547 void user_shortcuts_save()
549 StringOutputStream path(256);
550 path << SettingsPath_get() << g_pGameDescription->mGameFile.c_str() << '/';
551 SaveCommandMap(path.c_str());
554 int main(int argc, char *argv[])
562 lib = LoadLibrary( "dwmapi.dll" );
564 void ( WINAPI *qDwmEnableComposition )( bool bEnable ) = ( void (WINAPI *) ( bool bEnable ) )GetProcAddress( lib, "DwmEnableComposition" );
565 if ( qDwmEnableComposition ) {
566 qDwmEnableComposition( FALSE );
572 const char *mapname = NULL;
573 char const *error = NULL;
574 if (!ui::init(&argc, &argv, "<filename.map>", &error)) {
575 g_print("%s\n", error);
579 // Gtk already removed parsed `--options`
581 if (strlen(argv[1]) > 1) {
582 if (g_str_has_suffix(argv[1], ".map")) {
583 if (g_path_is_absolute(argv[1])) {
586 mapname = g_build_filename(g_get_current_dir(), argv[1], NULL);
589 g_print("bad file name, will not load: %s\n", argv[1]);
592 } else if (argc > 2) {
593 g_print("%s\n", "too many arguments");
597 // redirect Gtk warnings to the console
598 g_log_set_handler("Gdk", (GLogLevelFlags) (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
599 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG |
600 G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), error_redirect, 0);
601 g_log_set_handler("Gtk", (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 |
603 G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), error_redirect, 0);
604 g_log_set_handler("GtkGLExt", (GLogLevelFlags) (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
605 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG |
606 G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), error_redirect, 0);
607 g_log_set_handler("GLib", (GLogLevelFlags) (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
608 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG |
609 G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), error_redirect, 0);
610 g_log_set_handler(0, (GLogLevelFlags) (G_LOG_LEVEL_ERROR | G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING |
611 G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_INFO | G_LOG_LEVEL_DEBUG |
612 G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION), error_redirect, 0);
614 GlobalDebugMessageHandler::instance().setHandler(GlobalPopupDebugMessageHandler::instance());
616 environment_init(argc, (char const **) argv);
620 if (!check_version()) {
628 GlobalPreferences_Init();
630 g_GamesDialog.Init();
632 g_strGameToolsPath = g_pGameDescription->mGameToolsPath;
636 g_Preferences.Init(); // must occur before create_local_pid() to allow preferences to be reset
640 // in a very particular post-.pid startup
641 // we may have the console turned on and want to keep it that way
642 // so we use a latching system
643 if (g_GamesDialog.m_bForceLogConsole) {
645 g_Console_enableLogging = true;
646 g_GamesDialog.m_bForceLogConsole = false;
650 Radiant_Initialise();
652 user_shortcuts_init();
654 OpenGLBinding &GL = GlobalOpenGLModule::getTable();
655 g_pParentWnd = new MainFrame(GL);
659 if (mapname != NULL) {
660 Map_LoadFile(mapname);
661 } else if (g_bLoadLastMap && !g_strLastMap.empty()) {
662 Map_LoadFile(g_strLastMap.c_str());
667 // load up shaders now that we have the map loaded
669 TextureBrowser_ShowStartupShaders(GlobalTextureBrowser());
676 // avoid saving prefs when the app is minimized
677 if (g_pParentWnd->IsSleeping()) {
678 globalOutputStream() << "Shutdown while sleeping, not saving prefs\n";
679 g_preferences_globals.disable_ini = true;
684 if (!Map_Unnamed(g_map)) {
685 g_strLastMap = Map_Name(g_map);
690 user_shortcuts_save();
694 // close the log file if any