From c58cfdb87dfaa90bbb0d3b4710e3198d651819aa Mon Sep 17 00:00:00 2001 From: Geert Janssens Date: Fri, 29 May 2020 14:06:21 +0200 Subject: [PATCH] Extract common setup bits from gnucash and gnucash-cli into a separate class Gnucash::Base This is just a first start, more refactoring will follow in later commits. The idea is to have an application class that provides the basic framework gnucash and gnucash-cli will become specializations of this class adding their specific functionality. I'm splitting this over several commits to be able to keep track of all the many changes. --- gnucash/CMakeLists.txt | 14 +- gnucash/gnucash-base.cpp | 714 +++++++++++++++++++++++++++++++++++++++ gnucash/gnucash-base.hpp | 59 ++++ gnucash/gnucash-cli.cpp | 532 +---------------------------- gnucash/gnucash.cpp | 551 ++---------------------------- po/POTFILES.in | 1 + 6 files changed, 818 insertions(+), 1053 deletions(-) create mode 100644 gnucash/gnucash-base.cpp create mode 100644 gnucash/gnucash-base.hpp diff --git a/gnucash/CMakeLists.txt b/gnucash/CMakeLists.txt index 7ec6eda170..a162c5f315 100644 --- a/gnucash/CMakeLists.txt +++ b/gnucash/CMakeLists.txt @@ -31,14 +31,20 @@ else() endif() +set(gnucash_noinst_HEADERS + gnucash-base.hpp +) + set (gnucash_SOURCES gnucash.cpp + gnucash-base.cpp gnucash-gresources.c ${GNUCASH_RESOURCE_FILE} ) add_executable (gnucash - ${gnucash_SOURCES} + ${gnucash_SOURCES} + ${gnucash_noinst_HEADERS} ) add_dependencies (gnucash gnucash-manpage) @@ -55,7 +61,9 @@ target_link_libraries (gnucash add_executable (gnucash-cli gnucash-cli.cpp + gnucash-base.cpp ${GNUCASH_RESOURCE_FILE} + ${gnucash_noinst_HEADERS} ) add_dependencies (gnucash-cli gnucash) @@ -275,8 +283,8 @@ gnc_add_scheme_targets(price-quotes DEPENDS "scm-engine;scm-app-utils;scm-gnome-utils") set_local_dist(gnucash_DIST_local CMakeLists.txt environment.in generate-gnc-script - gnucash.cpp gnucash-cli.cpp gnucash.rc.in gnucash-valgrind.in gnucash-gresources.xml ${gresource_files} - price-quotes.scm ${gnucash_EXTRA_DIST}) + gnucash.cpp gnucash-cli.cpp gnucash-base.cpp gnucash.rc.in gnucash-valgrind.in gnucash-gresources.xml ${gresource_files} + price-quotes.scm ${gnucash_noinst_HEADERS} ${gnucash_EXTRA_DIST}) set (gnucash_DIST ${gnucash_DIST_local} ${gnome_DIST} ${gnome_search_DIST} ${gnome_utils_DIST} ${gschemas_DIST} ${gtkbuilder_DIST} ${html_DIST} ${import_export_DIST} ${python_DIST} ${register_DIST} diff --git a/gnucash/gnucash-base.cpp b/gnucash/gnucash-base.cpp new file mode 100644 index 0000000000..ce30633061 --- /dev/null +++ b/gnucash/gnucash-base.cpp @@ -0,0 +1,714 @@ +/* + * gnucash-base.cpp -- Basic application object for gnucash binaries + * + * Copyright (C) 2020 Geert Janssens + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact: + * + * Free Software Foundation Voice: +1-617-542-5942 + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 + * Boston, MA 02110-1301, USA gnu@gnu.org + */ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __MINGW32__ +#include +#include +#endif + +#include "gnucash-base.hpp" + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + +/* This static indicates the debugging module that this .o belongs to. */ +static QofLogModule log_module = GNC_MOD_GUI; + +/* Change the following to have a console window attached to GnuCash + * for displaying stdout and stderr on Windows. + */ +#define __MSWIN_CONSOLE__ 0 + +#include +#include + +#ifdef MAC_INTEGRATION +# include +#endif + +/* GNC_VCS is defined whenever we're building from an svn/svk/git/bzr tree */ +#ifdef GNC_VCS +static int is_development_version = TRUE; +#else +static int is_development_version = FALSE; +#define GNC_VCS "" +#endif + + + +static gchar *userdata_migration_msg = NULL; + +static void +gnc_print_unstable_message(void) +{ + if (!is_development_version) return; + + g_print("\n\n%s\n%s\n%s %s\n%s %s\n", + _("This is a development version. It may or may not work."), + _("Report bugs and other problems to gnucash-devel@gnucash.org"), + /* Translators: An URLs follows*/ + _("You can also lookup and file bug reports at"), PACKAGE_BUGREPORT, + /* Translators: An URLs follows*/ + _("To find the last stable version, please refer to"), PACKAGE_URL); +} + +#ifdef MAC_INTEGRATION +static void +mac_set_currency_locale(NSLocale *locale, NSString *locale_str) +{ + /* If the currency doesn't match the base locale, we need to find a locale that does match, because setlocale won't know what to do with just a currency identifier. */ + NSLocale *cur_locale = [[NSLocale alloc] initWithLocaleIdentifier: locale_str]; + if (![[locale objectForKey: NSLocaleCurrencyCode] isEqualToString: + [cur_locale objectForKey: NSLocaleCurrencyCode]]) + { + NSArray *all_locales = [NSLocale availableLocaleIdentifiers]; + NSEnumerator *locale_iter = [all_locales objectEnumerator]; + NSString *this_locale; + NSString *currency = [locale objectForKey: NSLocaleCurrencyCode]; + NSString *money_locale = nil; + while ((this_locale = (NSString*)[locale_iter nextObject])) + { + NSLocale *templocale = [[NSLocale alloc] + initWithLocaleIdentifier: this_locale]; + if ([[templocale objectForKey: NSLocaleCurrencyCode] + isEqualToString: currency]) + { + money_locale = this_locale; + [templocale release]; + break; + } + [templocale release]; + } + if (money_locale) + setlocale(LC_MONETARY, [money_locale UTF8String]); + } + [cur_locale release]; +} +/* The locale that we got from AppKit isn't a supported POSIX one, so we need to + * find something close. First see if we can find another locale for the + * country; failing that, try the language. Ultimately fall back on en_US. + */ +static NSString* +mac_find_close_country(NSString *locale_str, NSString *country_str, + NSString *lang_str) +{ + NSArray *all_locales = [NSLocale availableLocaleIdentifiers]; + NSEnumerator *locale_iter = [all_locales objectEnumerator]; + NSString *this_locale, *new_locale = nil; + PWARN("Apple Locale is set to a value %s not supported" + " by the C runtime", [locale_str UTF8String]); + while ((this_locale = (NSString*)[locale_iter nextObject])) + if ([[[NSLocale componentsFromLocaleIdentifier: this_locale] + objectForKey: NSLocaleCountryCode] + isEqualToString: country_str] && + setlocale (LC_ALL, [this_locale UTF8String])) + { + new_locale = this_locale; + break; + } + if (!new_locale) + while ((this_locale = (NSString*)[locale_iter nextObject])) + if ([[[NSLocale componentsFromLocaleIdentifier: this_locale] + objectForKey: NSLocaleLanguageCode] + isEqualToString: lang_str] && + setlocale (LC_ALL, [this_locale UTF8String])) + { + new_locale = this_locale; + break; + } + if (new_locale) + locale_str = new_locale; + else + { + locale_str = @"en_US"; + setlocale(LC_ALL, [locale_str UTF8String]); + } + PWARN("Using %s instead.", [locale_str UTF8String]); + return locale_str; +} + +/* Language subgroups (e.g., US English) are reported in the form "ll-SS" + * (e.g. again, "en-US"), not what gettext wants. We convert those to + * old-style locales, which is easy for most cases. There are two where it + * isn't, though: Simplified Chinese (zh-Hans) and traditional Chinese + * (zh-Hant), which are normally assigned the locales zh_CN and zh_TW, + * respectively. Those are handled specially. + */ +static NSString* +mac_convert_complex_language(NSString* this_lang) +{ + NSArray *elements = [this_lang componentsSeparatedByString: @"-"]; + if ([elements count] == 1) + return this_lang; + if ([[elements objectAtIndex: 0] isEqualToString: @"zh"]) { + if ([[elements objectAtIndex: 1] isEqualToString: @"Hans"]) + this_lang = @"zh_CN"; + else + this_lang = @"zh_TW"; + } + else + this_lang = [elements componentsJoinedByString: @"_"]; + return this_lang; +} + +static void +mac_set_languages(NSArray* languages, NSString *lang_str) +{ + /* Process the language list. */ + + const gchar *langs = NULL; + NSEnumerator *lang_iter = [languages objectEnumerator]; + NSArray *new_languages = [NSArray array]; + NSString *this_lang = NULL; + NSRange not_found = {NSNotFound, 0}; + while ((this_lang = [lang_iter nextObject])) { + this_lang = [this_lang stringByTrimmingCharactersInSet: + [NSCharacterSet characterSetWithCharactersInString: @"\""]]; + this_lang = mac_convert_complex_language(this_lang); + new_languages = [new_languages arrayByAddingObject: this_lang]; +/* If it's an English language, add the "C" locale after it so that + * any messages can default to it */ + if (!NSEqualRanges([this_lang rangeOfString: @"en"], not_found)) + new_languages = [new_languages arrayByAddingObject: @"C"]; + if (![new_languages containsObject: lang_str]) { + NSArray *temp_array = [NSArray arrayWithObject: lang_str]; + new_languages = [temp_array arrayByAddingObjectsFromArray: new_languages]; + } + langs = [[new_languages componentsJoinedByString:@":"] UTF8String]; + } + if (langs && strlen(langs) > 0) + { + PWARN("Language list: %s", langs); + g_setenv("LANGUAGE", langs, TRUE); + } +} + +static void +set_mac_locale() +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; + NSLocale *locale = [NSLocale currentLocale]; + NSString *lang_str, *country_str, *locale_str; + NSArray *languages = [[defs arrayForKey: @"AppleLanguages"] retain]; + @try + { + lang_str = [locale objectForKey: NSLocaleLanguageCode]; + country_str = [locale objectForKey: NSLocaleCountryCode]; + locale_str = [[lang_str stringByAppendingString: @"_"] + stringByAppendingString: country_str]; + } + @catch (NSException *err) + { + PWARN("Locale detection raised error %s: %s. " + "Check that your locale settings in " + "System Preferences>Languages & Text are set correctly.", + [[err name] UTF8String], [[err reason] UTF8String]); + locale_str = @"_"; + } +/* If we didn't get a valid current locale, the string will be just "_" */ + if ([locale_str isEqualToString: @"_"]) + locale_str = @"en_US"; + + lang_str = mac_convert_complex_language(lang_str); + if (!setlocale(LC_ALL, [locale_str UTF8String])) + locale_str = mac_find_close_country(locale_str, country_str, lang_str); + if (g_getenv("LANG") == NULL) + g_setenv("LANG", [locale_str UTF8String], TRUE); + mac_set_currency_locale(locale, locale_str); +/* Now call gnc_localeconv() to force creation of the app locale + * before another call to setlocale messes it up. */ + gnc_localeconv (); + /* Process the languages, including the one from the Apple locale. */ + if ([languages count] > 0) + mac_set_languages(languages, lang_str); + else + g_setenv("LANGUAGE", [lang_str UTF8String], TRUE); + [languages release]; + [pool drain]; +} +#endif /* MAC_INTEGRATION */ + +static gboolean +try_load_config_array(const gchar *fns[]) +{ + gchar *filename; + int i; + + for (i = 0; fns[i]; i++) + { + filename = gnc_build_userdata_path(fns[i]); + if (gfec_try_load(filename)) + { + g_free(filename); + return TRUE; + } + g_free(filename); + } + return FALSE; +} + +static void +update_message(const gchar *msg) +{ + gnc_update_splash_screen(msg, GNC_SPLASH_PERCENTAGE_UNKNOWN); + g_message("%s", msg); +} + +static void +load_system_config(void) +{ + static int is_system_config_loaded = FALSE; + gchar *system_config_dir; + gchar *system_config; + + if (is_system_config_loaded) return; + + update_message("loading system configuration"); + system_config_dir = gnc_path_get_pkgsysconfdir(); + system_config = g_build_filename(system_config_dir, "config", NULL); + is_system_config_loaded = gfec_try_load(system_config); + g_free(system_config_dir); + g_free(system_config); +} + +static void +load_user_config(void) +{ + /* Don't continue adding to this list. When 3.0 rolls around bump + the 2.4 files off the list. */ + static const gchar *saved_report_files[] = + { + SAVED_REPORTS_FILE, SAVED_REPORTS_FILE_OLD_REV, NULL + }; + static const gchar *stylesheet_files[] = { "stylesheets-2.0", NULL}; + static int is_user_config_loaded = FALSE; + + if (is_user_config_loaded) + return; + else is_user_config_loaded = TRUE; + + update_message("loading user configuration"); + { + gchar *config_filename; + config_filename = g_build_filename (gnc_userconfig_dir (), + "config-user.scm", (char *)NULL); + gfec_try_load(config_filename); + g_free(config_filename); + } + + update_message("loading saved reports"); + try_load_config_array(saved_report_files); + update_message("loading stylesheets"); + try_load_config_array(stylesheet_files); +} + + +static void +gnc_log_init (gchar **log_flags, gchar *log_to_filename) +{ + if (log_to_filename != NULL) + qof_log_init_filename_special(log_to_filename); + else + { + /* initialize logging to our file. */ + gchar *tracefilename; + tracefilename = g_build_filename(g_get_tmp_dir(), "gnucash.trace", + (gchar *)NULL); + qof_log_init_filename(tracefilename); + g_free(tracefilename); + } + + // set a reasonable default. + qof_log_set_default(QOF_LOG_WARNING); + + gnc_log_default(); + + if (gnc_prefs_is_debugging_enabled()) + { + qof_log_set_level("", QOF_LOG_INFO); + qof_log_set_level("qof", QOF_LOG_INFO); + qof_log_set_level("gnc", QOF_LOG_INFO); + } + + { + gchar *log_config_filename; + log_config_filename = g_build_filename (gnc_userconfig_dir (), + "log.conf", (char *)NULL); + if (g_file_test(log_config_filename, G_FILE_TEST_EXISTS)) + qof_log_parse_log_config(log_config_filename); + g_free(log_config_filename); + } + + if (log_flags != NULL) + { + int i = 0; + for (; log_flags[i] != NULL; i++) + { + QofLogLevel level; + gchar **parts = NULL; + + gchar *log_opt = log_flags[i]; + parts = g_strsplit(log_opt, "=", 2); + if (parts == NULL || parts[0] == NULL || parts[1] == NULL) + { + g_warning("string [%s] not parseable", log_opt); + continue; + } + + level = qof_log_level_from_string(parts[1]); + qof_log_set_level(parts[0], level); + g_strfreev(parts); + } + } +} + +#ifdef __MINGW32__ +/* If one of the Unix locale variables LC_ALL, LC_MESSAGES, or LANG is + * set in the environment check to see if it's a valid locale and if + * it is set both the Windows and POSIX locales to that. If not + * retrieve the Windows locale and set POSIX to match. + */ +static void +set_win32_thread_locale() +{ + WCHAR lpLocaleName[LOCALE_NAME_MAX_LENGTH]; + char *locale = NULL; + + if (((locale = getenv ("LC_ALL")) != NULL && locale[0] != '\0') || + ((locale = getenv ("LC_MESSAGES")) != NULL && locale[0] != '\0') || + ((locale = getenv ("LANG")) != NULL && locale[0] != '\0')) + { + gunichar2* wlocale = NULL; + int len = 0; + len = strchr(locale, '.') - locale; + locale[2] = '-'; + wlocale = g_utf8_to_utf16 (locale, len, NULL, NULL, NULL); + if (IsValidLocaleName(wlocale)) + { + LCID lcid = LocaleNameToLCID(wlocale, LOCALE_ALLOW_NEUTRAL_NAMES); + SetThreadLocale(lcid); + locale[2] = '_'; + setlocale (LC_ALL, locale); + sys_locale = locale; + g_free(wlocale); + return; + } + g_free(locale); + g_free(wlocale); + } + if (GetUserDefaultLocaleName(lpLocaleName, LOCALE_NAME_MAX_LENGTH)) + { + sys_locale = g_utf16_to_utf8((gunichar2*)lpLocaleName, + LOCALE_NAME_MAX_LENGTH, + NULL, NULL, NULL); + sys_locale[2] = '_'; + setlocale (LC_ALL, sys_locale); + return; + } +} +#endif + +/* Creates a console window on MSWindows to display stdout and stderr + * when __MSWIN_CONSOLE__ is defined at the top of the file. + * + * Useful for displaying the diagnostics printed before logging is + * started and if logging is redirected with --logto=stderr. + */ +static void +redirect_stdout (void) +{ +#if defined __MINGW32__ && __MSWIN_CONSOLE__ + static const WORD MAX_CONSOLE_LINES = 500; + int hConHandle; + long lStdHandle; + CONSOLE_SCREEN_BUFFER_INFO coninfo; + FILE *fp; + + // allocate a console for this app + AllocConsole(); + + // set the screen buffer to be big enough to let us scroll text + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); + coninfo.dwSize.Y = MAX_CONSOLE_LINES; + SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); + + // redirect unbuffered STDOUT to the console + lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE); + hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); + fp = _fdopen( hConHandle, "w" ); + *stdout = *fp; + setvbuf( stdout, NULL, _IONBF, 0 ); + + // redirect unbuffered STDIN to the console + lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE); + hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); + fp = _fdopen( hConHandle, "r" ); + *stdin = *fp; + setvbuf( stdin, NULL, _IONBF, 0 ); + + // redirect unbuffered STDERR to the console + lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE); + hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); + fp = _fdopen( hConHandle, "w" ); + *stderr = *fp; + setvbuf( stderr, NULL, _IONBF, 0 ); +#endif +} + +Gnucash::Base::Base () +{ + #if !defined(G_THREADS_ENABLED) || defined(G_THREADS_IMPL_NONE) + # error "No GLib thread implementation available!" + #endif + #ifdef ENABLE_BINRELOC + { + GError *binreloc_error = NULL; + if (!gnc_gbr_init(&binreloc_error)) + { + g_print("main: Error on gnc_gbr_init: %s\n", binreloc_error->message); + g_error_free(binreloc_error); + } + } + #endif + redirect_stdout (); + + /* This should be called before gettext is initialized + * The user may have configured a different language via + * the environment file. + */ + #ifdef MAC_INTEGRATION + set_mac_locale(); + #elif defined __MINGW32__ + set_win32_thread_locale(); + #endif + gnc_environment_setup(); + #if ! defined MAC_INTEGRATION && ! defined __MINGW32__/* setlocale already done */ + sys_locale = g_strdup (setlocale (LC_ALL, "")); + if (!sys_locale) + { + g_print ("The locale defined in the environment isn't supported. " + "Falling back to the 'C' (US English) locale\n"); + g_setenv ("LC_ALL", "C", TRUE); + setlocale (LC_ALL, "C"); + } + #endif + + auto localedir = gnc_path_get_localedir (); + bindtextdomain(PROJECT_NAME, localedir); + bindtextdomain("iso_4217", localedir); // For win32 to find currency name translations + bind_textdomain_codeset("iso_4217", "UTF-8"); + textdomain(PROJECT_NAME); + bind_textdomain_codeset(PROJECT_NAME, "UTF-8"); + g_free(localedir); +} + +/* Parse command line options, using GOption interface. + * We can't let gtk_init_with_args do it because it fails + * before parsing any arguments if the GUI can't be initialized. + */ +void +Gnucash::Base::parse_command_line (int *argc, char ***argv) +{ +#ifdef __MINGW64__ + wchar_t *tmp_log_to_filename = NULL; +#else + char *tmp_log_to_filename = NULL; +#endif + + GOptionEntry options[] = + { + { + "version", 'v', 0, G_OPTION_ARG_NONE, &gnucash_show_version, + N_("Show GnuCash version"), NULL + }, + + { + "debug", '\0', 0, G_OPTION_ARG_NONE, &debugging, + N_("Enable debugging mode: provide deep detail in the logs.\nThis is equivalent to: --log \"=info\" --log \"qof=info\" --log \"gnc=info\""), NULL + }, + + { + "extra", '\0', 0, G_OPTION_ARG_NONE, &extra, + N_("Enable extra/development/debugging features."), NULL + }, + + { + "log", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &log_flags, + N_("Log level overrides, of the form \"modulename={debug,info,warn,crit,error}\"\nExamples: \"--log qof=debug\" or \"--log gnc.backend.file.sx=info\"\nThis can be invoked multiple times."), + NULL + }, + + { + "logto", '\0', 0, G_OPTION_ARG_STRING, &tmp_log_to_filename, + N_("File to log into; defaults to \"/tmp/gnucash.trace\"; can be \"stderr\" or \"stdout\"."), + NULL + }, + + { + "nofile", '\0', 0, G_OPTION_ARG_NONE, &nofile, + N_("Do not load the last file opened"), NULL + }, + { + "gsettings-prefix", '\0', 0, G_OPTION_ARG_STRING, &gsettings_prefix, + N_("Set the prefix for gsettings schemas for gsettings queries. This can be useful to have a different settings tree while debugging."), + /* Translators: Argument description for autohelp; see + * http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ + N_("GSETTINGSPREFIX") + }, + { + "add-price-quotes", '\0', 0, G_OPTION_ARG_STRING, &add_quotes_file, + N_("Add price quotes to given GnuCash datafile"), + /* Translators: Argument description for autohelp; see + * http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ + N_("FILE") + }, + { + "namespace", '\0', 0, G_OPTION_ARG_STRING, &namespace_regexp, + N_("Regular expression determining which namespace commodities will be retrieved"), + /* Translators: Argument description for autohelp; see + * http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ + N_("REGEXP") + }, + { + G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args_remaining, NULL, N_("[datafile]") }, + { NULL } + }; + + GError *error = NULL; + GOptionContext *context = g_option_context_new (_("- GnuCash, accounting for personal and small business finance")); + + g_option_context_add_main_entries (context, options, PROJECT_NAME); + g_option_context_add_group (context, gtk_get_option_group(FALSE)); + if (!g_option_context_parse (context, argc, argv, &error)) + { + g_printerr (_("%s\nRun '%s --help' to see a full list of available command line options.\n"), + error->message, *argv[0]); + g_error_free (error); + exit (1); + } + g_option_context_free (context); + + if (tmp_log_to_filename != NULL) + { +#ifdef __MINGW64__ + log_to_filename = g_utf16_to_utf8(tmp_log_to_filename, -1, NULL, NULL, NULL); + g_free (tmp_log_to_filename); +#else + log_to_filename = tmp_log_to_filename; +#endif + } + + if (gnucash_show_version) + { + const char *format_string; + if (is_development_version) + format_string = _("GnuCash %s development version"); + else + format_string = _("GnuCash %s"); + + g_print (format_string, gnc_version()); + g_print ("\n%s: %s\n", _("Build ID"), gnc_build_id()); + exit(0); + } + + gnc_prefs_set_debugging(debugging); + gnc_prefs_set_extra(extra); + + if (gsettings_prefix) + gnc_gsettings_set_prefix(g_strdup(gsettings_prefix)); + + if (namespace_regexp) + gnc_prefs_set_namespace_regexp(namespace_regexp); + + if (args_remaining) + file_to_load = args_remaining[0]; +} + +const char* +Gnucash::Base::get_file_to_load (void) +{ + return file_to_load; +} + +int +Gnucash::Base::get_no_file (void) +{ + return nofile; +} + +const char* +Gnucash::Base::get_quotes_file (void) +{ + return add_quotes_file; +} + +void +Gnucash::Base::start (void) +{ + gnc_print_unstable_message(); + + /* Make sure gnucash' user data directory is properly set up + * This must be done before any guile code is called as that would + * fail the migration message */ + userdata_migration_msg = gnc_filepath_init(); + if (userdata_migration_msg) + g_print("\n\n%s\n", userdata_migration_msg); + + gnc_log_init (log_flags, log_to_filename); + gnc_engine_init (0, NULL); + + /* Write some locale details to the log to simplify debugging */ + PINFO ("System locale returned %s", sys_locale ? sys_locale : "(null)"); + PINFO ("Effective locale set to %s.", setlocale (LC_ALL, NULL)); + g_free (sys_locale); + sys_locale = NULL; +} diff --git a/gnucash/gnucash-base.hpp b/gnucash/gnucash-base.hpp new file mode 100644 index 0000000000..11652b4e16 --- /dev/null +++ b/gnucash/gnucash-base.hpp @@ -0,0 +1,59 @@ +/* + * gnucash-base.hpp -- Basic application object for gnucash binaries + * + * Copyright (C) 2020 Geert Janssens + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact: + * + * Free Software Foundation Voice: +1-617-542-5942 + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 + * Boston, MA 02110-1301, USA gnu@gnu.org + */ + +#ifndef GNUCASH_BASE_HPP +#define GNUCASH_BASE_HPP + +namespace Gnucash { + +class Base +{ +public: + Base (); + + void parse_command_line (int *argc, char ***argv); + void start (void); + + const char *get_file_to_load (void); + int get_no_file (void); + const char *get_quotes_file (void); + +private: + + /* Command-line option variables */ + int gnucash_show_version = 0; + int debugging = 0; + int extra = 0; + gchar **log_flags = NULL; + char *log_to_filename = NULL; + int nofile = 0; + const gchar *gsettings_prefix = NULL; + const char *add_quotes_file = NULL; + char *namespace_regexp = NULL; + const char *file_to_load = NULL; + gchar **args_remaining = NULL; + gchar *sys_locale = NULL; +}; + +} +#endif diff --git a/gnucash/gnucash-cli.cpp b/gnucash/gnucash-cli.cpp index e4b3c990e8..ea89f7cf40 100644 --- a/gnucash/gnucash-cli.cpp +++ b/gnucash/gnucash-cli.cpp @@ -40,6 +40,8 @@ #include #endif +#include "gnucash-base.hpp" + extern "C" { #include #include @@ -80,83 +82,6 @@ static int is_development_version = FALSE; #define GNC_VCS "" #endif -/* Command-line option variables */ -static int gnucash_show_version = 0; -static int debugging = 0; -static int extra = 0; -static gchar **log_flags = NULL; -#ifdef __MINGW64__ -static wchar_t *log_to_filename = NULL; -#else -static char *log_to_filename = NULL; -#endif -static int nofile = 0; -static const gchar *gsettings_prefix = NULL; -static const char *add_quotes_file = NULL; -static char *namespace_regexp = NULL; -static const char *file_to_load = NULL; -static gchar **args_remaining = NULL; -static gchar *sys_locale = NULL; - -static GOptionEntry options[] = -{ - { - "version", 'v', 0, G_OPTION_ARG_NONE, &gnucash_show_version, - N_("Show GnuCash version"), NULL - }, - - { - "debug", '\0', 0, G_OPTION_ARG_NONE, &debugging, - N_("Enable debugging mode: provide deep detail in the logs.\nThis is equivalent to: --log \"=info\" --log \"qof=info\" --log \"gnc=info\""), NULL - }, - - { - "extra", '\0', 0, G_OPTION_ARG_NONE, &extra, - N_("Enable extra/development/debugging features."), NULL - }, - - { - "log", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &log_flags, - N_("Log level overrides, of the form \"modulename={debug,info,warn,crit,error}\"\nExamples: \"--log qof=debug\" or \"--log gnc.backend.file.sx=info\"\nThis can be invoked multiple times."), - NULL - }, - - { - "logto", '\0', 0, G_OPTION_ARG_STRING, &log_to_filename, - N_("File to log into; defaults to \"/tmp/gnucash.trace\"; can be \"stderr\" or \"stdout\"."), - NULL - }, - - { - "nofile", '\0', 0, G_OPTION_ARG_NONE, &nofile, - N_("Do not load the last file opened"), NULL - }, - { - "gsettings-prefix", '\0', 0, G_OPTION_ARG_STRING, &gsettings_prefix, - N_("Set the prefix for gsettings schemas for gsettings queries. This can be useful to have a different settings tree while debugging."), - /* Translators: Argument description for autohelp; see - http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ - N_("GSETTINGSPREFIX") - }, - { - "add-price-quotes", '\0', 0, G_OPTION_ARG_STRING, &add_quotes_file, - N_("Add price quotes to given GnuCash datafile"), - /* Translators: Argument description for autohelp; see - http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ - N_("FILE") - }, - { - "namespace", '\0', 0, G_OPTION_ARG_STRING, &namespace_regexp, - N_("Regular expression determining which namespace commodities will be retrieved"), - /* Translators: Argument description for autohelp; see - http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ - N_("REGEXP") - }, - { - G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args_remaining, NULL, N_("[datafile]") }, - { NULL } -}; - static gchar *userdata_migration_msg = NULL; static void @@ -173,183 +98,6 @@ gnc_print_unstable_message(void) _("To find the last stable version, please refer to"), PACKAGE_URL); } -#ifdef MAC_INTEGRATION -static void -mac_set_currency_locale(NSLocale *locale, NSString *locale_str) -{ - /* If the currency doesn't match the base locale, we need to find a locale that does match, because setlocale won't know what to do with just a currency identifier. */ - NSLocale *cur_locale = [[NSLocale alloc] initWithLocaleIdentifier: locale_str]; - if (![[locale objectForKey: NSLocaleCurrencyCode] isEqualToString: - [cur_locale objectForKey: NSLocaleCurrencyCode]]) - { - NSArray *all_locales = [NSLocale availableLocaleIdentifiers]; - NSEnumerator *locale_iter = [all_locales objectEnumerator]; - NSString *this_locale; - NSString *currency = [locale objectForKey: NSLocaleCurrencyCode]; - NSString *money_locale = nil; - while ((this_locale = (NSString*)[locale_iter nextObject])) - { - NSLocale *templocale = [[NSLocale alloc] - initWithLocaleIdentifier: this_locale]; - if ([[templocale objectForKey: NSLocaleCurrencyCode] - isEqualToString: currency]) - { - money_locale = this_locale; - [templocale release]; - break; - } - [templocale release]; - } - if (money_locale) - setlocale(LC_MONETARY, [money_locale UTF8String]); - } - [cur_locale release]; -} -/* The locale that we got from AppKit isn't a supported POSIX one, so we need to - * find something close. First see if we can find another locale for the - * country; failing that, try the language. Ultimately fall back on en_US. - */ -static NSString* -mac_find_close_country(NSString *locale_str, NSString *country_str, - NSString *lang_str) -{ - NSArray *all_locales = [NSLocale availableLocaleIdentifiers]; - NSEnumerator *locale_iter = [all_locales objectEnumerator]; - NSString *this_locale, *new_locale = nil; - PWARN("Apple Locale is set to a value %s not supported" - " by the C runtime", [locale_str UTF8String]); - while ((this_locale = (NSString*)[locale_iter nextObject])) - if ([[[NSLocale componentsFromLocaleIdentifier: this_locale] - objectForKey: NSLocaleCountryCode] - isEqualToString: country_str] && - setlocale (LC_ALL, [this_locale UTF8String])) - { - new_locale = this_locale; - break; - } - if (!new_locale) - while ((this_locale = (NSString*)[locale_iter nextObject])) - if ([[[NSLocale componentsFromLocaleIdentifier: this_locale] - objectForKey: NSLocaleLanguageCode] - isEqualToString: lang_str] && - setlocale (LC_ALL, [this_locale UTF8String])) - { - new_locale = this_locale; - break; - } - if (new_locale) - locale_str = new_locale; - else - { - locale_str = @"en_US"; - setlocale(LC_ALL, [locale_str UTF8String]); - } - PWARN("Using %s instead.", [locale_str UTF8String]); - return locale_str; -} - -/* Language subgroups (e.g., US English) are reported in the form "ll-SS" - * (e.g. again, "en-US"), not what gettext wants. We convert those to - * old-style locales, which is easy for most cases. There are two where it - * isn't, though: Simplified Chinese (zh-Hans) and traditional Chinese - * (zh-Hant), which are normally assigned the locales zh_CN and zh_TW, - * respectively. Those are handled specially. - */ -static NSString* -mac_convert_complex_language(NSString* this_lang) -{ - NSArray *elements = [this_lang componentsSeparatedByString: @"-"]; - if ([elements count] == 1) - return this_lang; - if ([[elements objectAtIndex: 0] isEqualToString: @"zh"]) { - if ([[elements objectAtIndex: 1] isEqualToString: @"Hans"]) - this_lang = @"zh_CN"; - else - this_lang = @"zh_TW"; - } - else - this_lang = [elements componentsJoinedByString: @"_"]; - return this_lang; -} - -static void -mac_set_languages(NSArray* languages, NSString *lang_str) -{ - /* Process the language list. */ - - const gchar *langs = NULL; - NSEnumerator *lang_iter = [languages objectEnumerator]; - NSArray *new_languages = [NSArray array]; - NSString *this_lang = NULL; - NSRange not_found = {NSNotFound, 0}; - while ((this_lang = [lang_iter nextObject])) { - this_lang = [this_lang stringByTrimmingCharactersInSet: - [NSCharacterSet characterSetWithCharactersInString: @"\""]]; - this_lang = mac_convert_complex_language(this_lang); - new_languages = [new_languages arrayByAddingObject: this_lang]; -/* If it's an English language, add the "C" locale after it so that - * any messages can default to it */ - if (!NSEqualRanges([this_lang rangeOfString: @"en"], not_found)) - new_languages = [new_languages arrayByAddingObject: @"C"]; - if (![new_languages containsObject: lang_str]) { - NSArray *temp_array = [NSArray arrayWithObject: lang_str]; - new_languages = [temp_array arrayByAddingObjectsFromArray: new_languages]; - } - langs = [[new_languages componentsJoinedByString:@":"] UTF8String]; - } - if (langs && strlen(langs) > 0) - { - PWARN("Language list: %s", langs); - g_setenv("LANGUAGE", langs, TRUE); - } -} - -static void -set_mac_locale() -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; - NSLocale *locale = [NSLocale currentLocale]; - NSString *lang_str, *country_str, *locale_str; - NSArray *languages = [[defs arrayForKey: @"AppleLanguages"] retain]; - @try - { - lang_str = [locale objectForKey: NSLocaleLanguageCode]; - country_str = [locale objectForKey: NSLocaleCountryCode]; - locale_str = [[lang_str stringByAppendingString: @"_"] - stringByAppendingString: country_str]; - } - @catch (NSException *err) - { - PWARN("Locale detection raised error %s: %s. " - "Check that your locale settings in " - "System Preferences>Languages & Text are set correctly.", - [[err name] UTF8String], [[err reason] UTF8String]); - locale_str = @"_"; - } -/* If we didn't get a valid current locale, the string will be just "_" */ - if ([locale_str isEqualToString: @"_"]) - locale_str = @"en_US"; - - lang_str = mac_convert_complex_language(lang_str); - if (!setlocale(LC_ALL, [locale_str UTF8String])) - locale_str = mac_find_close_country(locale_str, country_str, lang_str); - if (g_getenv("LANG") == NULL) - g_setenv("LANG", [locale_str UTF8String], TRUE); - mac_set_currency_locale(locale, locale_str); -/* Now call gnc_localeconv() to force creation of the app locale - * before another call to setlocale messes it up. */ - gnc_localeconv (); - /* Process the languages, including the one from the Apple locale. */ - if ([languages count] > 0) - mac_set_languages(languages, lang_str); - else - g_setenv("LANGUAGE", [lang_str UTF8String], TRUE); - [languages release]; - [pool drain]; -} -#endif /* MAC_INTEGRATION */ - static gboolean try_load_config_array(const gchar *fns[]) { @@ -424,55 +172,10 @@ load_user_config(void) try_load_config_array(stylesheet_files); } -/* Parse command line options, using GOption interface. - * We can't let gtk_init_with_args do it because it fails - * before parsing any arguments if the GUI can't be initialized. - */ static void -gnc_parse_command_line(int *argc, char ***argv) -{ - GError *error = NULL; - GOptionContext *context = g_option_context_new (_("- GnuCash, accounting for personal and small business finance")); - - g_option_context_add_main_entries (context, options, PROJECT_NAME); - g_option_context_add_group (context, gtk_get_option_group(FALSE)); - if (!g_option_context_parse (context, argc, argv, &error)) - { - g_printerr (_("%s\nRun '%s --help' to see a full list of available command line options.\n"), - error->message, *argv[0]); - g_error_free (error); - exit (1); - } - g_option_context_free (context); - if (gnucash_show_version) - { - const char *format_string; - if (is_development_version) - format_string = _("GnuCash %s development version"); - else - format_string = _("GnuCash %s"); - - g_print (format_string, gnc_version()); - g_print ("\n%s: %s\n", _("Build ID"), gnc_build_id()); - exit(0); - } - - gnc_prefs_set_debugging(debugging); - gnc_prefs_set_extra(extra); - - if (gsettings_prefix) - gnc_gsettings_set_prefix(g_strdup(gsettings_prefix)); - - if (namespace_regexp) - gnc_prefs_set_namespace_regexp(namespace_regexp); - - if (args_remaining) - file_to_load = args_remaining[0]; -} - -static void -inner_main_add_price_quotes(void *closure, int argc, char **argv) +inner_main_add_price_quotes(void *data, int argc, char **argv) { + const char* add_quotes_file = static_cast(data); SCM mod, add_quotes, scm_book, scm_result = SCM_BOOL_F; QofSession *session = NULL; @@ -530,232 +233,17 @@ fail: gnc_shutdown(1); } -static void -gnc_log_init() -{ - if (log_to_filename != NULL) - { -#ifdef __MINGW64__ - char* filename = g_utf16_to_utf8(log_to_filename, -1, NULL, NULL, NULL); -#else - char* filename = log_to_filename; -#endif - qof_log_init_filename_special(filename); - } - else - { - /* initialize logging to our file. */ - gchar *tracefilename; - tracefilename = g_build_filename(g_get_tmp_dir(), "gnucash.trace", - (gchar *)NULL); - qof_log_init_filename(tracefilename); - g_free(tracefilename); - } - - // set a reasonable default. - qof_log_set_default(QOF_LOG_WARNING); - - gnc_log_default(); - - if (gnc_prefs_is_debugging_enabled()) - { - qof_log_set_level("", QOF_LOG_INFO); - qof_log_set_level("qof", QOF_LOG_INFO); - qof_log_set_level("gnc", QOF_LOG_INFO); - } - - { - gchar *log_config_filename; - log_config_filename = g_build_filename (gnc_userconfig_dir (), - "log.conf", (char *)NULL); - if (g_file_test(log_config_filename, G_FILE_TEST_EXISTS)) - qof_log_parse_log_config(log_config_filename); - g_free(log_config_filename); - } - - if (log_flags != NULL) - { - int i = 0; - for (; log_flags[i] != NULL; i++) - { - QofLogLevel level; - gchar **parts = NULL; - - gchar *log_opt = log_flags[i]; - parts = g_strsplit(log_opt, "=", 2); - if (parts == NULL || parts[0] == NULL || parts[1] == NULL) - { - g_warning("string [%s] not parseable", log_opt); - continue; - } - - level = qof_log_level_from_string(parts[1]); - qof_log_set_level(parts[0], level); - g_strfreev(parts); - } - } -} - -#ifdef __MINGW32__ -/* If one of the Unix locale variables LC_ALL, LC_MESSAGES, or LANG is - * set in the environment check to see if it's a valid locale and if - * it is set both the Windows and POSIX locales to that. If not - * retrieve the Windows locale and set POSIX to match. - */ -static void -set_win32_thread_locale() -{ - WCHAR lpLocaleName[LOCALE_NAME_MAX_LENGTH]; - char *locale = NULL; - - if (((locale = getenv ("LC_ALL")) != NULL && locale[0] != '\0') || - ((locale = getenv ("LC_MESSAGES")) != NULL && locale[0] != '\0') || - ((locale = getenv ("LANG")) != NULL && locale[0] != '\0')) - { - gunichar2* wlocale = NULL; - int len = 0; - len = strchr(locale, '.') - locale; - locale[2] = '-'; - wlocale = g_utf8_to_utf16 (locale, len, NULL, NULL, NULL); - if (IsValidLocaleName(wlocale)) - { - LCID lcid = LocaleNameToLCID(wlocale, LOCALE_ALLOW_NEUTRAL_NAMES); - SetThreadLocale(lcid); - locale[2] = '_'; - setlocale (LC_ALL, locale); - sys_locale = locale; - g_free(wlocale); - return; - } - g_free(locale); - g_free(wlocale); - } - if (GetUserDefaultLocaleName(lpLocaleName, LOCALE_NAME_MAX_LENGTH)) - { - sys_locale = g_utf16_to_utf8((gunichar2*)lpLocaleName, - LOCALE_NAME_MAX_LENGTH, - NULL, NULL, NULL); - sys_locale[2] = '_'; - setlocale (LC_ALL, sys_locale); - return; - } -} -#endif - -/* Creates a console window on MSWindows to display stdout and stderr - * when __MSWIN_CONSOLE__ is defined at the top of the file. - * - * Useful for displaying the diagnostics printed before logging is - * started and if logging is redirected with --logto=stderr. - */ -static void -redirect_stdout (void) -{ -#if defined __MINGW32__ && __MSWIN_CONSOLE__ - static const WORD MAX_CONSOLE_LINES = 500; - int hConHandle; - long lStdHandle; - CONSOLE_SCREEN_BUFFER_INFO coninfo; - FILE *fp; - - // allocate a console for this app - AllocConsole(); - - // set the screen buffer to be big enough to let us scroll text - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); - coninfo.dwSize.Y = MAX_CONSOLE_LINES; - SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); - - // redirect unbuffered STDOUT to the console - lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE); - hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); - fp = _fdopen( hConHandle, "w" ); - *stdout = *fp; - setvbuf( stdout, NULL, _IONBF, 0 ); - - // redirect unbuffered STDIN to the console - lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE); - hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); - fp = _fdopen( hConHandle, "r" ); - *stdin = *fp; - setvbuf( stdin, NULL, _IONBF, 0 ); - - // redirect unbuffered STDERR to the console - lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE); - hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); - fp = _fdopen( hConHandle, "w" ); - *stderr = *fp; - setvbuf( stderr, NULL, _IONBF, 0 ); -#endif -} - int main(int argc, char ** argv) { - gchar *localedir = gnc_path_get_localedir(); -#if !defined(G_THREADS_ENABLED) || defined(G_THREADS_IMPL_NONE) -# error "No GLib thread implementation available!" -#endif -#ifdef ENABLE_BINRELOC - { - GError *binreloc_error = NULL; - if (!gnc_gbr_init(&binreloc_error)) - { - g_print("main: Error on gnc_gbr_init: %s\n", binreloc_error->message); - g_error_free(binreloc_error); - } - } -#endif - redirect_stdout (); + Gnucash::Base application; - /* This should be called before gettext is initialized - * The user may have configured a different language via - * the environment file. - */ -#ifdef MAC_INTEGRATION - set_mac_locale(); -#elif defined __MINGW32__ - set_win32_thread_locale(); -#endif - gnc_environment_setup(); -#if ! defined MAC_INTEGRATION && ! defined __MINGW32__/* setlocale already done */ - sys_locale = g_strdup (setlocale (LC_ALL, "")); - if (!sys_locale) - { - g_print ("The locale defined in the environment isn't supported. " - "Falling back to the 'C' (US English) locale\n"); - g_setenv ("LC_ALL", "C", TRUE); - setlocale (LC_ALL, "C"); - } -#endif - bindtextdomain(PROJECT_NAME, localedir); - bindtextdomain("iso_4217", localedir); // For win32 to find currency name translations - bind_textdomain_codeset("iso_4217", "UTF-8"); - textdomain(PROJECT_NAME); - bind_textdomain_codeset(PROJECT_NAME, "UTF-8"); - g_free(localedir); + application.parse_command_line (&argc, &argv); + application.start (); - gnc_parse_command_line(&argc, &argv); - gnc_print_unstable_message(); - - /* Make sure gnucash' user data directory is properly set up - This must be done before any guile code is called as that would - fail the migration message */ - userdata_migration_msg = gnc_filepath_init(); - if (userdata_migration_msg) - g_print("\n\n%s\n", userdata_migration_msg); - - gnc_log_init(); - gnc_engine_init (0, NULL); - - /* Write some locale details to the log to simplify debugging */ - PINFO ("System locale returned %s", sys_locale ? sys_locale : "(null)"); - PINFO ("Effective locale set to %s.", setlocale (LC_ALL, NULL)); - g_free (sys_locale); - sys_locale = NULL; - - if (add_quotes_file) - scm_boot_guile(argc, argv, inner_main_add_price_quotes, 0); + auto quotes_file = application.get_quotes_file (); + if (quotes_file) + scm_boot_guile (argc, argv, inner_main_add_price_quotes, (void *)quotes_file); exit(0); /* never reached */ } diff --git a/gnucash/gnucash.cpp b/gnucash/gnucash.cpp index 8419a739c0..ad409f9edd 100644 --- a/gnucash/gnucash.cpp +++ b/gnucash/gnucash.cpp @@ -42,6 +42,8 @@ #include #endif +#include "gnucash-base.hpp" + extern "C" { #include #include @@ -97,83 +99,6 @@ static int is_development_version = FALSE; #define GNC_VCS "" #endif -/* Command-line option variables */ -static int gnucash_show_version = 0; -static int debugging = 0; -static int extra = 0; -static gchar **log_flags = NULL; -#ifdef __MINGW64__ -static wchar_t *log_to_filename = NULL; -#else -static char *log_to_filename = NULL; -#endif -static int nofile = 0; -static const gchar *gsettings_prefix = NULL; -static const char *add_quotes_file = NULL; -static char *namespace_regexp = NULL; -static const char *file_to_load = NULL; -static gchar **args_remaining = NULL; -static gchar *sys_locale = NULL; - -static GOptionEntry options[] = -{ - { - "version", 'v', 0, G_OPTION_ARG_NONE, &gnucash_show_version, - N_("Show GnuCash version"), NULL - }, - - { - "debug", '\0', 0, G_OPTION_ARG_NONE, &debugging, - N_("Enable debugging mode: provide deep detail in the logs.\nThis is equivalent to: --log \"=info\" --log \"qof=info\" --log \"gnc=info\""), NULL - }, - - { - "extra", '\0', 0, G_OPTION_ARG_NONE, &extra, - N_("Enable extra/development/debugging features."), NULL - }, - - { - "log", '\0', 0, G_OPTION_ARG_STRING_ARRAY, &log_flags, - N_("Log level overrides, of the form \"modulename={debug,info,warn,crit,error}\"\nExamples: \"--log qof=debug\" or \"--log gnc.backend.file.sx=info\"\nThis can be invoked multiple times."), - NULL - }, - - { - "logto", '\0', 0, G_OPTION_ARG_STRING, &log_to_filename, - N_("File to log into; defaults to \"/tmp/gnucash.trace\"; can be \"stderr\" or \"stdout\"."), - NULL - }, - - { - "nofile", '\0', 0, G_OPTION_ARG_NONE, &nofile, - N_("Do not load the last file opened"), NULL - }, - { - "gsettings-prefix", '\0', 0, G_OPTION_ARG_STRING, &gsettings_prefix, - N_("Set the prefix for gsettings schemas for gsettings queries. This can be useful to have a different settings tree while debugging."), - /* Translators: Argument description for autohelp; see - http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ - N_("GSETTINGSPREFIX") - }, - { - "add-price-quotes", '\0', 0, G_OPTION_ARG_STRING, &add_quotes_file, - N_("Add price quotes to given GnuCash datafile"), - /* Translators: Argument description for autohelp; see - http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ - N_("FILE") - }, - { - "namespace", '\0', 0, G_OPTION_ARG_STRING, &namespace_regexp, - N_("Regular expression determining which namespace commodities will be retrieved"), - /* Translators: Argument description for autohelp; see - http://developer.gnome.org/doc/API/2.0/glib/glib-Commandline-option-parser.html */ - N_("REGEXP") - }, - { - G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_STRING_ARRAY, &args_remaining, NULL, N_("[datafile]") }, - { NULL } -}; - static gchar *userdata_migration_msg = NULL; static void @@ -190,183 +115,6 @@ gnc_print_unstable_message(void) _("To find the last stable version, please refer to"), PACKAGE_URL); } -#ifdef MAC_INTEGRATION -static void -mac_set_currency_locale(NSLocale *locale, NSString *locale_str) -{ - /* If the currency doesn't match the base locale, we need to find a locale that does match, because setlocale won't know what to do with just a currency identifier. */ - NSLocale *cur_locale = [[NSLocale alloc] initWithLocaleIdentifier: locale_str]; - if (![[locale objectForKey: NSLocaleCurrencyCode] isEqualToString: - [cur_locale objectForKey: NSLocaleCurrencyCode]]) - { - NSArray *all_locales = [NSLocale availableLocaleIdentifiers]; - NSEnumerator *locale_iter = [all_locales objectEnumerator]; - NSString *this_locale; - NSString *currency = [locale objectForKey: NSLocaleCurrencyCode]; - NSString *money_locale = nil; - while ((this_locale = (NSString*)[locale_iter nextObject])) - { - NSLocale *templocale = [[NSLocale alloc] - initWithLocaleIdentifier: this_locale]; - if ([[templocale objectForKey: NSLocaleCurrencyCode] - isEqualToString: currency]) - { - money_locale = this_locale; - [templocale release]; - break; - } - [templocale release]; - } - if (money_locale) - setlocale(LC_MONETARY, [money_locale UTF8String]); - } - [cur_locale release]; -} -/* The locale that we got from AppKit isn't a supported POSIX one, so we need to - * find something close. First see if we can find another locale for the - * country; failing that, try the language. Ultimately fall back on en_US. - */ -static NSString* -mac_find_close_country(NSString *locale_str, NSString *country_str, - NSString *lang_str) -{ - NSArray *all_locales = [NSLocale availableLocaleIdentifiers]; - NSEnumerator *locale_iter = [all_locales objectEnumerator]; - NSString *this_locale, *new_locale = nil; - PWARN("Apple Locale is set to a value %s not supported" - " by the C runtime", [locale_str UTF8String]); - while ((this_locale = (NSString*)[locale_iter nextObject])) - if ([[[NSLocale componentsFromLocaleIdentifier: this_locale] - objectForKey: NSLocaleCountryCode] - isEqualToString: country_str] && - setlocale (LC_ALL, [this_locale UTF8String])) - { - new_locale = this_locale; - break; - } - if (!new_locale) - while ((this_locale = (NSString*)[locale_iter nextObject])) - if ([[[NSLocale componentsFromLocaleIdentifier: this_locale] - objectForKey: NSLocaleLanguageCode] - isEqualToString: lang_str] && - setlocale (LC_ALL, [this_locale UTF8String])) - { - new_locale = this_locale; - break; - } - if (new_locale) - locale_str = new_locale; - else - { - locale_str = @"en_US"; - setlocale(LC_ALL, [locale_str UTF8String]); - } - PWARN("Using %s instead.", [locale_str UTF8String]); - return locale_str; -} - -/* Language subgroups (e.g., US English) are reported in the form "ll-SS" - * (e.g. again, "en-US"), not what gettext wants. We convert those to - * old-style locales, which is easy for most cases. There are two where it - * isn't, though: Simplified Chinese (zh-Hans) and traditional Chinese - * (zh-Hant), which are normally assigned the locales zh_CN and zh_TW, - * respectively. Those are handled specially. - */ -static NSString* -mac_convert_complex_language(NSString* this_lang) -{ - NSArray *elements = [this_lang componentsSeparatedByString: @"-"]; - if ([elements count] == 1) - return this_lang; - if ([[elements objectAtIndex: 0] isEqualToString: @"zh"]) { - if ([[elements objectAtIndex: 1] isEqualToString: @"Hans"]) - this_lang = @"zh_CN"; - else - this_lang = @"zh_TW"; - } - else - this_lang = [elements componentsJoinedByString: @"_"]; - return this_lang; -} - -static void -mac_set_languages(NSArray* languages, NSString *lang_str) -{ - /* Process the language list. */ - - const gchar *langs = NULL; - NSEnumerator *lang_iter = [languages objectEnumerator]; - NSArray *new_languages = [NSArray array]; - NSString *this_lang = NULL; - NSRange not_found = {NSNotFound, 0}; - while ((this_lang = [lang_iter nextObject])) { - this_lang = [this_lang stringByTrimmingCharactersInSet: - [NSCharacterSet characterSetWithCharactersInString: @"\""]]; - this_lang = mac_convert_complex_language(this_lang); - new_languages = [new_languages arrayByAddingObject: this_lang]; -/* If it's an English language, add the "C" locale after it so that - * any messages can default to it */ - if (!NSEqualRanges([this_lang rangeOfString: @"en"], not_found)) - new_languages = [new_languages arrayByAddingObject: @"C"]; - if (![new_languages containsObject: lang_str]) { - NSArray *temp_array = [NSArray arrayWithObject: lang_str]; - new_languages = [temp_array arrayByAddingObjectsFromArray: new_languages]; - } - langs = [[new_languages componentsJoinedByString:@":"] UTF8String]; - } - if (langs && strlen(langs) > 0) - { - PWARN("Language list: %s", langs); - g_setenv("LANGUAGE", langs, TRUE); - } -} - -static void -set_mac_locale() -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; - NSLocale *locale = [NSLocale currentLocale]; - NSString *lang_str, *country_str, *locale_str; - NSArray *languages = [[defs arrayForKey: @"AppleLanguages"] retain]; - @try - { - lang_str = [locale objectForKey: NSLocaleLanguageCode]; - country_str = [locale objectForKey: NSLocaleCountryCode]; - locale_str = [[lang_str stringByAppendingString: @"_"] - stringByAppendingString: country_str]; - } - @catch (NSException *err) - { - PWARN("Locale detection raised error %s: %s. " - "Check that your locale settings in " - "System Preferences>Languages & Text are set correctly.", - [[err name] UTF8String], [[err reason] UTF8String]); - locale_str = @"_"; - } -/* If we didn't get a valid current locale, the string will be just "_" */ - if ([locale_str isEqualToString: @"_"]) - locale_str = @"en_US"; - - lang_str = mac_convert_complex_language(lang_str); - if (!setlocale(LC_ALL, [locale_str UTF8String])) - locale_str = mac_find_close_country(locale_str, country_str, lang_str); - if (g_getenv("LANG") == NULL) - g_setenv("LANG", [locale_str UTF8String], TRUE); - mac_set_currency_locale(locale, locale_str); -/* Now call gnc_localeconv() to force creation of the app locale - * before another call to setlocale messes it up. */ - gnc_localeconv (); - /* Process the languages, including the one from the Apple locale. */ - if ([languages count] > 0) - mac_set_languages(languages, lang_str); - else - g_setenv("LANGUAGE", [lang_str UTF8String], TRUE); - [languages release]; - [pool drain]; -} -#endif /* MAC_INTEGRATION */ - static gboolean try_load_config_array(const gchar *fns[]) { @@ -441,52 +189,6 @@ load_user_config(void) try_load_config_array(stylesheet_files); } -/* Parse command line options, using GOption interface. - * We can't let gtk_init_with_args do it because it fails - * before parsing any arguments if the GUI can't be initialized. - */ -static void -gnc_parse_command_line(int *argc, char ***argv) -{ - GError *error = NULL; - GOptionContext *context = g_option_context_new (_("- GnuCash, accounting for personal and small business finance")); - - g_option_context_add_main_entries (context, options, PROJECT_NAME); - g_option_context_add_group (context, gtk_get_option_group(FALSE)); - if (!g_option_context_parse (context, argc, argv, &error)) - { - g_printerr (_("%s\nRun '%s --help' to see a full list of available command line options.\n"), - error->message, *argv[0]); - g_error_free (error); - exit (1); - } - g_option_context_free (context); - if (gnucash_show_version) - { - const char *format_string; - if (is_development_version) - format_string = _("GnuCash %s development version"); - else - format_string = _("GnuCash %s"); - - g_print (format_string, gnc_version()); - g_print ("\n%s: %s\n", _("Build ID"), gnc_build_id()); - exit(0); - } - - gnc_prefs_set_debugging(debugging); - gnc_prefs_set_extra(extra); - - if (gsettings_prefix) - gnc_gsettings_set_prefix(g_strdup(gsettings_prefix)); - - if (namespace_regexp) - gnc_prefs_set_namespace_regexp(namespace_regexp); - - if (args_remaining) - file_to_load = args_remaining[0]; -} - static void load_gnucash_plugins() { @@ -529,8 +231,9 @@ load_gnucash_modules() } static void -inner_main_add_price_quotes(void *closure, int argc, char **argv) +inner_main_add_price_quotes(void *data, int argc, char **argv) { + const char* add_quotes_file = static_cast(data); SCM mod, add_quotes, scm_book, scm_result = SCM_BOOL_F; QofSession *session = NULL; @@ -589,7 +292,7 @@ fail: } static char * -get_file_to_load() +get_file_to_load (const char* file_to_load) { if (file_to_load) return g_strdup(file_to_load); @@ -600,9 +303,15 @@ get_file_to_load() extern SCM scm_init_sw_gnome_module(void); +struct t_file_spec { + int nofile; + const char *file_to_load; +}; + static void -inner_main (void *closure, int argc, char **argv) +inner_main (void *data, int argc, char **argv) { + auto user_file_spec = static_cast(data); SCM main_mod; char* fn = NULL; @@ -648,7 +357,7 @@ inner_main (void *closure, int argc, char **argv) gnc_hook_run(HOOK_STARTUP, NULL); - if (!nofile && (fn = get_file_to_load()) && *fn ) + if (!user_file_spec->nofile && (fn = get_file_to_load (user_file_spec->file_to_load)) && *fn ) { gnc_update_splash_screen(_("Loading data..."), GNC_SPLASH_PERCENTAGE_UNKNOWN); gnc_file_open_file(gnc_get_splash_screen(), fn, /*open_readonly*/ FALSE); @@ -687,236 +396,20 @@ inner_main (void *closure, int argc, char **argv) return; } -static void -gnc_log_init() -{ - if (log_to_filename != NULL) - { -#ifdef __MINGW64__ - char* filename = g_utf16_to_utf8(log_to_filename, -1, NULL, NULL, NULL); -#else - char* filename = log_to_filename; -#endif - qof_log_init_filename_special(filename); - } - else - { - /* initialize logging to our file. */ - gchar *tracefilename; - tracefilename = g_build_filename(g_get_tmp_dir(), "gnucash.trace", - (gchar *)NULL); - qof_log_init_filename(tracefilename); - g_free(tracefilename); - } - - // set a reasonable default. - qof_log_set_default(QOF_LOG_WARNING); - - gnc_log_default(); - - if (gnc_prefs_is_debugging_enabled()) - { - qof_log_set_level("", QOF_LOG_INFO); - qof_log_set_level("qof", QOF_LOG_INFO); - qof_log_set_level("gnc", QOF_LOG_INFO); - } - - { - gchar *log_config_filename; - log_config_filename = g_build_filename (gnc_userconfig_dir (), - "log.conf", (char *)NULL); - if (g_file_test(log_config_filename, G_FILE_TEST_EXISTS)) - qof_log_parse_log_config(log_config_filename); - g_free(log_config_filename); - } - - if (log_flags != NULL) - { - int i = 0; - for (; log_flags[i] != NULL; i++) - { - QofLogLevel level; - gchar **parts = NULL; - - gchar *log_opt = log_flags[i]; - parts = g_strsplit(log_opt, "=", 2); - if (parts == NULL || parts[0] == NULL || parts[1] == NULL) - { - g_warning("string [%s] not parseable", log_opt); - continue; - } - - level = qof_log_level_from_string(parts[1]); - qof_log_set_level(parts[0], level); - g_strfreev(parts); - } - } -} - -#ifdef __MINGW32__ -/* If one of the Unix locale variables LC_ALL, LC_MESSAGES, or LANG is - * set in the environment check to see if it's a valid locale and if - * it is set both the Windows and POSIX locales to that. If not - * retrieve the Windows locale and set POSIX to match. - */ -static void -set_win32_thread_locale() -{ - WCHAR lpLocaleName[LOCALE_NAME_MAX_LENGTH]; - char *locale = NULL; - - if (((locale = getenv ("LC_ALL")) != NULL && locale[0] != '\0') || - ((locale = getenv ("LC_MESSAGES")) != NULL && locale[0] != '\0') || - ((locale = getenv ("LANG")) != NULL && locale[0] != '\0')) - { - gunichar2* wlocale = NULL; - int len = 0; - len = strchr(locale, '.') - locale; - locale[2] = '-'; - wlocale = g_utf8_to_utf16 (locale, len, NULL, NULL, NULL); - if (IsValidLocaleName(wlocale)) - { - LCID lcid = LocaleNameToLCID(wlocale, LOCALE_ALLOW_NEUTRAL_NAMES); - SetThreadLocale(lcid); - locale[2] = '_'; - setlocale (LC_ALL, locale); - sys_locale = locale; - g_free(wlocale); - return; - } - g_free(locale); - g_free(wlocale); - } - if (GetUserDefaultLocaleName(lpLocaleName, LOCALE_NAME_MAX_LENGTH)) - { - sys_locale = g_utf16_to_utf8((gunichar2*)lpLocaleName, - LOCALE_NAME_MAX_LENGTH, - NULL, NULL, NULL); - sys_locale[2] = '_'; - setlocale (LC_ALL, sys_locale); - return; - } -} -#endif - -/* Creates a console window on MSWindows to display stdout and stderr - * when __MSWIN_CONSOLE__ is defined at the top of the file. - * - * Useful for displaying the diagnostics printed before logging is - * started and if logging is redirected with --logto=stderr. - */ -static void -redirect_stdout (void) -{ -#if defined __MINGW32__ && __MSWIN_CONSOLE__ - static const WORD MAX_CONSOLE_LINES = 500; - int hConHandle; - long lStdHandle; - CONSOLE_SCREEN_BUFFER_INFO coninfo; - FILE *fp; - - // allocate a console for this app - AllocConsole(); - - // set the screen buffer to be big enough to let us scroll text - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); - coninfo.dwSize.Y = MAX_CONSOLE_LINES; - SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); - - // redirect unbuffered STDOUT to the console - lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE); - hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); - fp = _fdopen( hConHandle, "w" ); - *stdout = *fp; - setvbuf( stdout, NULL, _IONBF, 0 ); - - // redirect unbuffered STDIN to the console - lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE); - hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); - fp = _fdopen( hConHandle, "r" ); - *stdin = *fp; - setvbuf( stdin, NULL, _IONBF, 0 ); - - // redirect unbuffered STDERR to the console - lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE); - hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); - fp = _fdopen( hConHandle, "w" ); - *stderr = *fp; - setvbuf( stderr, NULL, _IONBF, 0 ); -#endif -} - int main(int argc, char ** argv) { - gchar *localedir; -#if !defined(G_THREADS_ENABLED) || defined(G_THREADS_IMPL_NONE) -# error "No GLib thread implementation available!" -#endif -#ifdef ENABLE_BINRELOC - { - GError *binreloc_error = NULL; - if (!gnc_gbr_init(&binreloc_error)) - { - g_print("main: Error on gnc_gbr_init: %s\n", binreloc_error->message); - g_error_free(binreloc_error); - } - } -#endif - redirect_stdout (); + Gnucash::Base application; - /* This should be called before gettext is initialized - * The user may have configured a different language via - * the environment file. - */ -#ifdef MAC_INTEGRATION - set_mac_locale(); -#elif defined __MINGW32__ - set_win32_thread_locale(); -#endif - gnc_environment_setup(); -#if ! defined MAC_INTEGRATION && ! defined __MINGW32__/* setlocale already done */ - sys_locale = g_strdup (setlocale (LC_ALL, "")); - if (!sys_locale) - { - g_print ("The locale defined in the environment isn't supported. " - "Falling back to the 'C' (US English) locale\n"); - g_setenv ("LC_ALL", "C", TRUE); - setlocale (LC_ALL, "C"); - } -#endif - localedir = gnc_path_get_localedir(); - bindtextdomain(PROJECT_NAME, localedir); - bindtextdomain("iso_4217", localedir); // For win32 to find currency name translations - bind_textdomain_codeset("iso_4217", "UTF-8"); - textdomain(PROJECT_NAME); - bind_textdomain_codeset(PROJECT_NAME, "UTF-8"); - g_free(localedir); - - gnc_parse_command_line(&argc, &argv); - gnc_print_unstable_message(); - - /* Make sure gnucash' user data directory is properly set up - This must be done before any guile code is called as that would - fail the migration message */ - userdata_migration_msg = gnc_filepath_init(); - if (userdata_migration_msg) - g_print("\n\n%s\n", userdata_migration_msg); - - gnc_log_init(); - gnc_engine_init (0, NULL); - - /* Write some locale details to the log to simplify debugging */ - PINFO ("System locale returned %s", sys_locale ? sys_locale : "(null)"); - PINFO ("Effective locale set to %s.", setlocale (LC_ALL, NULL)); - g_free (sys_locale); - sys_locale = NULL; + application.parse_command_line (&argc, &argv); + application.start(); /* If asked via a command line parameter, fetch quotes only */ - if (add_quotes_file) + auto quotes_file = application.get_quotes_file (); + if (quotes_file) { - scm_boot_guile(argc, argv, inner_main_add_price_quotes, 0); - exit(0); /* never reached */ + scm_boot_guile (argc, argv, inner_main_add_price_quotes, (void*) quotes_file); + exit (0); /* never reached */ } /* We need to initialize gtk before looking up all modules */ @@ -934,6 +427,8 @@ main(int argc, char ** argv) gnc_module_system_init(); gnc_gui_init(); - scm_boot_guile(argc, argv, inner_main, 0); + + auto user_file_spec = t_file_spec {application.get_no_file (), application.get_file_to_load ()}; + scm_boot_guile (argc, argv, inner_main, &user_file_spec); exit(0); /* never reached */ } diff --git a/po/POTFILES.in b/po/POTFILES.in index bd2dfb80e7..2353a72dcb 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -210,6 +210,7 @@ gnucash/gnome-utils/print-session.c gnucash/gnome-utils/search-param.c gnucash/gnome-utils/tree-view-utils.c gnucash/gnome-utils/window-main-summarybar.c +gnucash/gnucash-base.cpp gnucash/gnucash-cli.cpp gnucash/gnucash.cpp gnucash/gschemas/org.gnucash.dialogs.business.gschema.xml.in