# HG changeset patch # User Ryan C. Gordon # Date 1587708447 14400 # Fri Apr 24 02:07:27 2020 -0400 # Node ID 7d6a82af06b3ad6f0d012418ccf179ced98dc5f2 # Parent a564e72e3b2d0e422c8d8a80a7a5ad46487fb5d2 locale: First shot at SDL_GetPreferredLocales(). This was something I proposed a long time ago, Sylvain Becker did additional work on it, then back to me. This is barely tested! Fixes Bugzilla #2131. diff --git a/Android.mk b/Android.mk --- a/Android.mk +++ b/Android.mk @@ -35,6 +35,8 @@ $(wildcard $(LOCAL_PATH)/src/joystick/hidapi/*.c) \ $(wildcard $(LOCAL_PATH)/src/joystick/virtual/*.c) \ $(wildcard $(LOCAL_PATH)/src/loadso/dlopen/*.c) \ + $(wildcard $(LOCAL_PATH)/src/locale/*.c) \ + $(wildcard $(LOCAL_PATH)/src/locale/android/*.c) \ $(wildcard $(LOCAL_PATH)/src/power/*.c) \ $(wildcard $(LOCAL_PATH)/src/power/android/*.c) \ $(wildcard $(LOCAL_PATH)/src/filesystem/android/*.c) \ diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -307,7 +307,7 @@ set(SDL_SUBSYSTEMS Atomic Audio Video Render Events Joystick Haptic Power Threads Timers - File Loadso CPUinfo Filesystem Dlopen Sensor) + File Loadso CPUinfo Filesystem Dlopen Sensor Locale) foreach(_SUB ${SDL_SUBSYSTEMS}) string(TOUPPER ${_SUB} _OPT) if (NOT DEFINED SDL_${_OPT}_ENABLED_BY_DEFAULT) @@ -404,6 +404,7 @@ ${SDL2_SOURCE_DIR}/src/events/*.c ${SDL2_SOURCE_DIR}/src/file/*.c ${SDL2_SOURCE_DIR}/src/libm/*.c + ${SDL2_SOURCE_DIR}/src/locale/*.c ${SDL2_SOURCE_DIR}/src/render/*.c ${SDL2_SOURCE_DIR}/src/render/*/*.c ${SDL2_SOURCE_DIR}/src/stdlib/*.c @@ -885,6 +886,8 @@ file(GLOB POWER_SOURCES ${SDL2_SOURCE_DIR}/src/power/*.c) set(SOURCE_FILES ${SOURCE_FILES} ${POWER_SOURCES}) endif() + + # TODO: in configure.ac, the test for LOADSO and SDL_DLOPEN is a bit weird: # if LOADSO is not wanted, SDL_LOADSO_DISABLED is set # If however on Unix or APPLE dlopen() is detected via CheckDLOPEN(), @@ -993,6 +996,11 @@ set(SOURCE_FILES ${SOURCE_FILES} ${ANDROID_POWER_SOURCES}) set(HAVE_SDL_POWER TRUE) endif() + if(SDL_LOCALE) + file(GLOB ANDROID_LOCALE_SOURCES ${SDL2_SOURCE_DIR}/src/locale/android/*.c) + set(SOURCE_FILES ${SOURCE_FILES} ${ANDROID_LOCALE_SOURCES}) + set(HAVE_SDL_LOCALE TRUE) + endif() if(SDL_TIMERS) set(SDL_TIMER_UNIX 1) file(GLOB TIMER_SOURCES ${SDL2_SOURCE_DIR}/src/timer/unix/*.c) @@ -1272,6 +1280,12 @@ endif() endif() + if(SDL_LOCALE) + file(GLOB LOCALE_SOURCES ${SDL2_SOURCE_DIR}/src/locale/unix/*.c) + set(SOURCE_FILES ${SOURCE_FILES} ${LOCALE_SOURCES}) + set(HAVE_SDL_LOCALE TRUE) + endif() + if(SDL_FILESYSTEM) set(SDL_FILESYSTEM_UNIX 1) file(GLOB FILESYSTEM_SOURCES ${SDL2_SOURCE_DIR}/src/filesystem/unix/*.c) @@ -1438,6 +1452,12 @@ set(HAVE_SDL_POWER TRUE) endif() + if(SDL_LOCALE) + file(GLOB LOCALE_SOURCES ${SDL2_SOURCE_DIR}/src/locale/windows/*.c) + set(SOURCE_FILES ${SOURCE_FILES} ${LOCALE_SOURCES}) + set(HAVE_SDL_LOCALE TRUE) + endif() + if(SDL_FILESYSTEM) set(SDL_FILESYSTEM_WINDOWS 1) file(GLOB FILESYSTEM_SOURCES ${SDL2_SOURCE_DIR}/src/filesystem/windows/*.c) @@ -1629,6 +1649,12 @@ set(HAVE_SDL_POWER TRUE) endif() + if(SDL_LOCALE) + file(GLOB LOCALE_SOURCES ${SDL2_SOURCE_DIR}/src/locale/macosx/*.m) + set(SOURCE_FILES ${SOURCE_FILES} ${LOCALE_SOURCES}) + set(HAVE_SDL_LOCALE TRUE) + endif() + if(SDL_TIMERS) set(SDL_TIMER_UNIX 1) file(GLOB TIMER_SOURCES ${SDL2_SOURCE_DIR}/src/timer/unix/*.c) @@ -1837,6 +1863,12 @@ set(HAVE_SDL_TIMERS TRUE) endif() + if(SDL_LOCALE) + file(GLOB LOCALE_SOURCES ${SDL2_SOURCE_DIR}/src/locale/haiku/*.cc) + set(SOURCE_FILES ${SOURCE_FILES} ${LOCALE_SOURCES}) + set(HAVE_SDL_LOCALE TRUE) + endif() + CheckPTHREAD() elseif(RISCOS) @@ -1899,6 +1931,11 @@ file(GLOB FILESYSTEM_SOURCES ${SDL2_SOURCE_DIR}/src/filesystem/dummy/*.c) set(SOURCE_FILES ${SOURCE_FILES} ${FILESYSTEM_SOURCES}) endif() +if(NOT HAVE_SDL_LOCALE) + set(SDL_LOCALE_DISABLED 1) + file(GLOB LOCALE_SOURCES ${SDL2_SOURCE_DIR}/src/locale/dummy/*.c) + set(SOURCE_FILES ${SOURCE_FILES} ${LOCALE_SOURCES}) +endif() # We always need to have threads and timers around if(NOT HAVE_SDL_THREADS) diff --git a/Makefile.in b/Makefile.in --- a/Makefile.in +++ b/Makefile.in @@ -82,6 +82,7 @@ SDL_keyboard.h \ SDL_keycode.h \ SDL_loadso.h \ + SDL_locale.h \ SDL_log.h \ SDL_main.h \ SDL_messagebox.h \ diff --git a/configure b/configure --- a/configure +++ b/configure @@ -770,6 +770,7 @@ docdir oldincludedir includedir +runstatedir localstatedir sharedstatedir sysconfdir @@ -981,6 +982,7 @@ sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE}' @@ -1233,6 +1235,15 @@ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1370,7 +1381,7 @@ for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir + libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -1523,6 +1534,7 @@ --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] @@ -17309,6 +17321,7 @@ SOURCES="$SOURCES $srcdir/src/timer/*.c" SOURCES="$SOURCES $srcdir/src/video/*.c" SOURCES="$SOURCES $srcdir/src/video/yuv2rgb/*.c" +SOURCES="$SOURCES $srcdir/src/locale/*.c" # Check whether --enable-atomic was given. @@ -24659,6 +24672,8 @@ CheckEventSignals +have_locale=no + case "$host" in *-*-linux*|*-*-uclinux*|*-*-gnu*|*-*-k*bsd*-gnu|*-*-bsdi*|*-*-freebsd*|*-*-dragonfly*|*-*-netbsd*|*-*-openbsd*|*-*-sysv5*|*-*-solaris*|*-*-hpux*|*-*-aix*|*-*-minix*|*-*-nto*) case "$host" in @@ -24746,6 +24761,9 @@ CheckRPATH CheckVivanteVideo + SOURCES="$SOURCES $srcdir/src/locale/unix/*.c" + have_locale=yes + # Set up files for the audio library if test x$enable_audio = xyes; then case $ARCH in @@ -24923,6 +24941,10 @@ # Set up the core platform files SOURCES="$SOURCES $srcdir/src/core/windows/*.c" + # Use the Windows locale APIs. + SOURCES="$SOURCES $srcdir/src/locale/windows/*.c" + have_locale=yes + # Set up files for the video library if test x$enable_video = xyes; then @@ -25174,6 +25196,11 @@ SOURCES="$SOURCES $srcdir/src/filesystem/haiku/*.cc" have_filesystem=yes fi + + # Set up files for the locale library + SOURCES="$SOURCES $srcdir/src/locale/haiku/*.cc" + have_locale=yes + # The Haiku platform requires special setup. SOURCES="$srcdir/src/main/haiku/*.cc $SOURCES" EXTRA_LDFLAGS="$EXTRA_LDFLAGS -lroot -lbe -lmedia -lgame -ldevice -ltextencoding" @@ -25216,6 +25243,10 @@ CheckVulkan CheckPTHREAD + # Set up files for the locale library + SOURCES="$SOURCES $srcdir/src/locale/macosx/*.m" + have_locale=yes + # Set up files for the audio library if test x$enable_audio = xyes; then @@ -25337,6 +25368,10 @@ CheckPTHREAD CheckHIDAPI + # Set up files for the locale library + SOURCES="$SOURCES $srcdir/src/locale/macosx/*.m" + have_locale=yes + # Set up files for the audio library if test x$enable_audio = xyes; then @@ -25549,6 +25584,9 @@ # Verify that we have all the platform specific files we need +if test x$have_locale != xyes; then + SOURCES="$SOURCES $srcdir/src/locale/dummy/*.c" +fi if test x$have_joystick != xyes; then if test x$enable_joystick = xyes; then diff --git a/configure.ac b/configure.ac --- a/configure.ac +++ b/configure.ac @@ -413,6 +413,7 @@ SOURCES="$SOURCES $srcdir/src/timer/*.c" SOURCES="$SOURCES $srcdir/src/video/*.c" SOURCES="$SOURCES $srcdir/src/video/yuv2rgb/*.c" +SOURCES="$SOURCES $srcdir/src/locale/*.c" dnl Enable/disable various subsystems of the SDL library @@ -3439,6 +3440,8 @@ dnl Do this for every platform, but for some it doesn't mean anything, but better to catch it here anyhow. CheckEventSignals +have_locale=no + dnl Set up the configuration based on the host platform! case "$host" in *-*-linux*|*-*-uclinux*|*-*-gnu*|*-*-k*bsd*-gnu|*-*-bsdi*|*-*-freebsd*|*-*-dragonfly*|*-*-netbsd*|*-*-openbsd*|*-*-sysv5*|*-*-solaris*|*-*-hpux*|*-*-aix*|*-*-minix*|*-*-nto*) @@ -3527,6 +3530,9 @@ CheckRPATH CheckVivanteVideo + SOURCES="$SOURCES $srcdir/src/locale/unix/*.c" + have_locale=yes + # Set up files for the audio library if test x$enable_audio = xyes; then case $ARCH in @@ -3676,6 +3682,10 @@ # Set up the core platform files SOURCES="$SOURCES $srcdir/src/core/windows/*.c" + # Use the Windows locale APIs. + SOURCES="$SOURCES $srcdir/src/locale/windows/*.c" + have_locale=yes + # Set up files for the video library if test x$enable_video = xyes; then AC_DEFINE(SDL_VIDEO_DRIVER_WINDOWS, 1, [ ]) @@ -3847,6 +3857,11 @@ SOURCES="$SOURCES $srcdir/src/filesystem/haiku/*.cc" have_filesystem=yes fi + + # Set up files for the locale library + SOURCES="$SOURCES $srcdir/src/locale/haiku/*.cc" + have_locale=yes + # The Haiku platform requires special setup. SOURCES="$srcdir/src/main/haiku/*.cc $SOURCES" EXTRA_LDFLAGS="$EXTRA_LDFLAGS -lroot -lbe -lmedia -lgame -ldevice -ltextencoding" @@ -3867,6 +3882,10 @@ CheckVulkan CheckPTHREAD + # Set up files for the locale library + SOURCES="$SOURCES $srcdir/src/locale/macosx/*.m" + have_locale=yes + # Set up files for the audio library if test x$enable_audio = xyes; then AC_DEFINE(SDL_AUDIO_DRIVER_COREAUDIO, 1, [ ]) @@ -3966,6 +3985,10 @@ CheckPTHREAD CheckHIDAPI + # Set up files for the locale library + SOURCES="$SOURCES $srcdir/src/locale/macosx/*.m" + have_locale=yes + # Set up files for the audio library if test x$enable_audio = xyes; then AC_DEFINE(SDL_AUDIO_DRIVER_COREAUDIO, 1, [ ]) @@ -4141,6 +4164,9 @@ # Verify that we have all the platform specific files we need +if test x$have_locale != xyes; then + SOURCES="$SOURCES $srcdir/src/locale/dummy/*.c" +fi if test x$have_joystick != xyes; then if test x$enable_joystick = xyes; then AC_DEFINE(SDL_JOYSTICK_DUMMY, 1, [ ]) diff --git a/include/SDL.h b/include/SDL.h --- a/include/SDL.h +++ b/include/SDL.h @@ -59,6 +59,7 @@ #include "SDL_timer.h" #include "SDL_version.h" #include "SDL_video.h" +#include "SDL_locale.h" #include "begin_code.h" /* Set up for C function definitions, even when using C++ */ diff --git a/include/SDL_events.h b/include/SDL_events.h --- a/include/SDL_events.h +++ b/include/SDL_events.h @@ -85,6 +85,8 @@ Called on Android in onResume() */ + SDL_LOCALECHANGED, /**< The user's locale preferences have changed. */ + /* Display events */ SDL_DISPLAYEVENT = 0x150, /**< Display state change */ diff --git a/include/SDL_hints.h b/include/SDL_hints.h --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -1292,7 +1292,7 @@ */ #define SDL_HINT_WAVE_FACT_CHUNK "SDL_WAVE_FACT_CHUNK" -/* +/** * \brief Override for SDL_GetDisplayUsableBounds() * * If set, this hint will override the expected results for @@ -1307,6 +1307,21 @@ #define SDL_HINT_DISPLAY_USABLE_BOUNDS "SDL_DISPLAY_USABLE_BOUNDS" /** + * \brief Override for SDL_GetPreferredLocales() + * + * If set, this will be favored over anything the OS might report for the + * user's preferred locales. Changing this hint at runtime will not generate + * a SDL_LOCALECHANGED event (but if you can change the hint, you can push + * your own event, if you want). + * + * The format of this hint is a comma-separated list of language and locale, + * combined with an underscore, as is a common format: "en_GB". Locale is + * optional: "en". So you might have a list like this: "en_GB,jp,es_PT" + */ +#define SDL_HINT_PREFERRED_LOCALES "SDL_PREFERRED_LOCALES" + + +/** * \brief An enumeration of hint priorities */ typedef enum diff --git a/include/SDL_locale.h b/include/SDL_locale.h new file mode 100644 --- /dev/null +++ b/include/SDL_locale.h @@ -0,0 +1,101 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +/** + * \file SDL_locale.h + * + * Include file for SDL locale services + */ + +#ifndef _SDL_locale_h +#define _SDL_locale_h + +#include "SDL_stdinc.h" +#include "SDL_error.h" + +#include "begin_code.h" +/* Set up for C function definitions, even when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + + +typedef struct SDL_Locale +{ + const char *language; /**< A language name, like "en" for English. */ + const char *country; /**< A country, like "US" for America. Can be NULL. */ +} SDL_Locale; + +/** + * \brief Report the user's preferred locale. + * + * This returns an array of SDL_Locale structs, the final item zeroed out. + * When the caller is done with this array, it should call SDL_free() on + * the returned value; all the memory involved is allocated in a single + * block, so a single SDL_free() will suffice. + * + * Returned language strings are in the format xx, where 'xx' is an ISO-639 + * language specifier (such as "en" for English, "de" for German, etc). + * Country strings are in the format YY, where "YY" is an ISO-3166 country + * code (such as "US" for the United States, "CA" for Canada, etc). Country + * might be NULL if there's no specific guidance on them (so you might get + * { "en", "US" } for American English, but { "en", NULL } means "English + * language, generically"). Language strings are never NULL, except to + * terminate the array. + * + * Please note that not all of these strings are 2 characters; some are + * three or more. + * + * The returned list of locales are in the order of the user's preference. + * For example, a German citizen that is fluent in US English and knows + * enough Japanese to navigate around Tokyo might have a list like: + * { "de", "en_US", "jp", NULL }. Someone from England might prefer British + * English (where "color" is spelled "colour", etc), but will settle for + * anything like it: { "en_GB", "en", NULL }. + * + * This function returns NULL on error, including when the platform does not + * supply this information at all. + * + * This might be a "slow" call that has to query the operating system. It's + * best to ask for this once and save the results. However, this list can + * change, usually because the user has changed a system preference outside + * of your program; SDL will send an SDL_LOCALECHANGED event in this case, + * if possible, and you can call this function again to get an updated copy + * of preferred locales. + * + * \return array of locales, terminated with a locale with a NULL language + * field. Will return NULL on error. + */ +extern DECLSPEC SDL_Locale * SDLCALL SDL_GetPreferredLocales(void); + +/* Ends C function definitions when using C++ */ +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif +#include "close_code.h" + +#endif /* _SDL_locale_h */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -44,6 +44,8 @@ #include "../../haptic/android/SDL_syshaptic_c.h" #include +#include +#include #include #include #include @@ -2796,6 +2798,82 @@ return bPermissionRequestResult; } +int Android_JNI_GetLocale(char *buf, size_t buflen) +{ + struct LocalReferenceHolder refs = LocalReferenceHolder_Setup(__FUNCTION__); + JNIEnv* env = Android_JNI_GetEnv(); + int retval = -1; + + JNIEnv *mEnv = Android_JNI_GetEnv(); + if (!LocalReferenceHolder_Init(&refs, env)) { + LocalReferenceHolder_Cleanup(&refs); + return -1; + } + + SDL_assert(buflen > 6); + + jmethodID mid; + jobject context; + jobject assetManager; + + /* context = SDLActivity.getContext(); */ + mid = (*mEnv)->GetStaticMethodID(mEnv, mActivityClass, + "getContext","()Landroid/content/Context;"); + context = (*mEnv)->CallStaticObjectMethod(mEnv, mActivityClass, mid); + + /* assetManager = context.getAssets(); */ + mid = (*mEnv)->GetMethodID(mEnv, (*mEnv)->GetObjectClass(mEnv, context), + "getAssets", "()Landroid/content/res/AssetManager;"); + assetManager = (*mEnv)->CallObjectMethod(mEnv, context, mid); + + + /* API from NDK: android/configuration.h */ + /* API from NDK: android/asset_manager_jni.h */ + AAssetManager* asset_mgr = AAssetManager_fromJava(env, assetManager); + AConfiguration *cfg = AConfiguration_new(); + + if (asset_mgr && cfg) + { + char language[2] = {}; + char country[2] = {}; + size_t id = 0; + + AConfiguration_fromAssetManager(cfg, asset_mgr); + AConfiguration_getLanguage(cfg, language); + AConfiguration_getCountry(cfg, country); + + retval = 0; + + /* copy language (not null terminated) */ + if (language[0]) { + buf[id++] = language[0]; + if (language[1]) { + buf[id++] = language[1]; + } + } + + buf[id++] = '_'; + + /* copy country (not null terminated) */ + if (country[0]) { + buf[id++] = country[0]; + if (country[1]) { + buf[id++] = country[1]; + } + } + + buf[id++] = '\0'; + SDL_assert(id <= buflen); + } + + if (cfg) { + AConfiguration_delete(cfg); + } + + LocalReferenceHolder_Cleanup(&refs); + return retval; +} + #endif /* __ANDROID__ */ /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/core/android/SDL_android.h b/src/core/android/SDL_android.h --- a/src/core/android/SDL_android.h +++ b/src/core/android/SDL_android.h @@ -104,6 +104,9 @@ JNIEnv *Android_JNI_GetEnv(void); int Android_JNI_SetupThread(void); +/* Locale */ +int Android_JNI_GetLocale(char *buf, size_t buflen); + /* Generic messages */ int Android_JNI_SendMessage(int command, int param); diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h --- a/src/dynapi/SDL_dynapi_overrides.h +++ b/src/dynapi/SDL_dynapi_overrides.h @@ -763,3 +763,4 @@ #define SDL_Metal_GetDrawableSize SDL_Metal_GetDrawableSize_REAL #define SDL_trunc SDL_trunc_REAL #define SDL_truncf SDL_truncf_REAL +#define SDL_GetPreferredLocales SDL_GetPreferredLocales_REAL diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h --- a/src/dynapi/SDL_dynapi_procs.h +++ b/src/dynapi/SDL_dynapi_procs.h @@ -822,3 +822,4 @@ SDL_DYNAPI_PROC(void,SDL_Metal_GetDrawableSize,(SDL_Window *a, int *b, int *c),(a,b,c),) SDL_DYNAPI_PROC(double,SDL_trunc,(double a),(a),return) SDL_DYNAPI_PROC(float,SDL_truncf,(float a),(a),return) +SDL_DYNAPI_PROC(SDL_Locale *,SDL_GetPreferredLocales,(void),(),return) diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c --- a/src/events/SDL_events.c +++ b/src/events/SDL_events.c @@ -1010,6 +1010,12 @@ } int +SDL_SendLocaleChangedEvent(void) +{ + return SDL_SendAppEvent(SDL_LOCALECHANGED); +} + +int SDL_EventsInit(void) { SDL_AddHintCallback(SDL_HINT_EVENT_LOGGING, SDL_EventLoggingChanged, NULL); diff --git a/src/events/SDL_events_c.h b/src/events/SDL_events_c.h --- a/src/events/SDL_events_c.h +++ b/src/events/SDL_events_c.h @@ -46,6 +46,7 @@ extern int SDL_SendAppEvent(SDL_EventType eventType); extern int SDL_SendSysWMEvent(SDL_SysWMmsg * message); extern int SDL_SendKeymapChangedEvent(void); +extern int SDL_SendLocaleChangedEvent(void); extern int SDL_SendQuit(void); diff --git a/src/events/SDL_gesture.c b/src/events/SDL_gesture.c --- a/src/events/SDL_gesture.c +++ b/src/events/SDL_gesture.c @@ -36,7 +36,7 @@ #define MAXPATHSIZE 1024 -#define ENABLE_DOLLAR +// PATCH slvn #define ENABLE_DOLLAR #define DOLLARNPOINTS 64 diff --git a/src/locale/SDL_locale.c b/src/locale/SDL_locale.c new file mode 100644 --- /dev/null +++ b/src/locale/SDL_locale.c @@ -0,0 +1,103 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "../SDL_internal.h" +#include "SDL_syslocale.h" +#include "SDL_hints.h" + +static SDL_Locale * +build_locales_from_csv_string(char *csv) +{ + size_t num_locales = 1; /* at least one */ + size_t slen; + size_t alloclen; + char *ptr; + SDL_Locale *loc; + SDL_Locale *retval; + + if (!csv || !csv[0]) { + return NULL; /* nothing to report */ + } + + for (ptr = csv; *ptr; ptr++) { + if (*ptr == ',') { + num_locales++; + } + } + + num_locales++; /* one more for terminator */ + + slen = ((size_t) (ptr - csv)) + 1; /* strlen(csv) + 1 */ + alloclen = slen + (num_locales * sizeof (SDL_Locale)); + + loc = retval = (SDL_Locale *) SDL_calloc(1, alloclen); + if (!retval) { + SDL_OutOfMemory(); + return NULL; /* oh well */ + } + ptr = (char *) (retval + num_locales); + SDL_strlcpy(ptr, csv, slen); + + while (SDL_TRUE) { /* parse out the string */ + while (*ptr == ' ') ptr++; /* skip whitespace. */ + if (*ptr == '\0') { + break; + } + loc->language = ptr++; + while (SDL_TRUE) { + const char ch = *ptr; + if (ch == '_') { + *(ptr++) = '\0'; + loc->country = ptr; + } else if (ch == ' ') { + *(ptr++) = '\0'; /* trim ending whitespace and keep going. */ + } else if (ch == ',') { + *(ptr++) = '\0'; + loc++; + break; + } else if (ch == '\0') { + loc++; + break; + } else { + ptr++; /* just keep going, still a valid string */ + } + } + } + + return retval; +} + +SDL_Locale * +SDL_GetPreferredLocales(void) +{ + char locbuf[128]; /* enough for 21 "xx_YY," language strings. */ + const char *hint = SDL_GetHint(SDL_HINT_PREFERRED_LOCALES); + if (hint) { + SDL_strlcpy(locbuf, hint, sizeof (locbuf)); + } else { + SDL_zeroa(locbuf); + SDL_SYS_GetPreferredLocales(locbuf, sizeof (locbuf)); + } + return build_locales_from_csv_string(locbuf); +} + +/* vi: set ts=4 sw=4 expandtab: */ + diff --git a/src/locale/SDL_syslocale.h b/src/locale/SDL_syslocale.h new file mode 100644 --- /dev/null +++ b/src/locale/SDL_syslocale.h @@ -0,0 +1,29 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ +#include "SDL_config.h" + +/* This is the system specific header for the SDL locale API */ + +#include "SDL_locale.h" + +extern void SDL_SYS_GetPreferredLocales(char *buf, size_t buflen); + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/locale/android/SDL_syslocale.c b/src/locale/android/SDL_syslocale.c new file mode 100644 --- /dev/null +++ b/src/locale/android/SDL_syslocale.c @@ -0,0 +1,32 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_config.h" +#include "../SDL_syslocale.h" + +void +SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) +{ + Android_JNI_GetLocale(buf, buflen); +} + +/* vi: set ts=4 sw=4 expandtab: */ + diff --git a/src/locale/dummy/SDL_syslocale.c b/src/locale/dummy/SDL_syslocale.c new file mode 100644 --- /dev/null +++ b/src/locale/dummy/SDL_syslocale.c @@ -0,0 +1,33 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_config.h" +#include "../SDL_syslocale.h" + +void +SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) +{ + /* dummy implementation. Caller already zero'd out buffer. */ + SDL_Unsupported(); +} + +/* vi: set ts=4 sw=4 expandtab: */ + diff --git a/src/locale/haiku/SDL_syslocale.cc b/src/locale/haiku/SDL_syslocale.cc new file mode 100644 --- /dev/null +++ b/src/locale/haiku/SDL_syslocale.cc @@ -0,0 +1,72 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include + +#include "SDL_config.h" +#include "../SDL_syslocale.h" + +void +SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) +{ + BMessage msg; + if (BLocaleRoster::Default()->GetPreferredLanguages(&msg) != B_OK) { + SDL_SetError("BLocaleRoster couldn't get preferred languages"); + return; + } + + const char *key = "language"; + type_code typ = B_ANY_TYPE; + int32 numlangs = 0; + if ((msg.GetInfo(key, &typ, &numlangs) != B_OK) || (typ != B_STRING_TYPE)) { + SDL_SetError("BLocaleRoster message was wrong"); + return; + } + + for (int32 i = 0; i < numlangs; i++) { + const char *str = NULL; + if (msg.FindString(key, i, &str) != B_OK) { + continue; + } + + const size_t len = SDL_strlen(str); + if (buflen <= len) { + break; // can't fit it, we're done. + } + + SDL_strlcpy(buf, str, buflen); + buf += len; + buflen -= len; + + if (i < (numlangs - 1)) { + if (buflen <= 1) { + break; // out of room, stop looking. + } + buf[0] = ','; // add a comma between entries. + buf[1] = '\0'; + buf++; + buflen--; + } + } +} + +/* vi: set ts=4 sw=4 expandtab: */ + diff --git a/src/locale/macosx/SDL_syslocale.m b/src/locale/macosx/SDL_syslocale.m new file mode 100644 --- /dev/null +++ b/src/locale/macosx/SDL_syslocale.m @@ -0,0 +1,76 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_config.h" +#include "../SDL_syslocale.h" + +#import + +void +SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) +{ @autoreleasepool { + NSArray *languages = NSLocale.preferredLanguages; + size_t numlangs = 0; + size_t i; + + numlangs = (size_t) [languages count]; + + for (i = 0; i < numlangs; i++) { + NSString *nsstr = [languages objectAtIndex:i]; + size_t len; + char *ptr; + + if (nsstr == nil) { + break; + } + + [nsstr getCString:buf maxLength:buflen encoding:NSASCIIStringEncoding]; + len = SDL_strlen(buf); + + // convert '-' to '_'... + // These are always full lang-COUNTRY, so we search from the back, + // so things like zh-Hant-CN find the right '-' to convert. + if ((ptr = SDL_strrchr(buf, '-')) != NULL) { + *ptr = '_'; + } + + if (buflen <= len) { + *buf = '\0'; // drop this one and stop, we can't fit anymore. + break; + } + + buf += len; + buflen -= len; + + if (i < (numlangs - 1)) { + if (buflen <= 1) { + break; // out of room, stop looking. + } + buf[0] = ','; // add a comma between entries. + buf[1] = '\0'; + buf++; + buflen--; + } + } +}} + +/* vi: set ts=4 sw=4 expandtab: */ + diff --git a/src/locale/unix/SDL_syslocale.c b/src/locale/unix/SDL_syslocale.c new file mode 100644 --- /dev/null +++ b/src/locale/unix/SDL_syslocale.c @@ -0,0 +1,51 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_config.h" +#include "../SDL_syslocale.h" + +void +SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) +{ + /* !!! FIXME: should we be using setlocale()? Or some D-Bus thing? */ + /* !!! FIXME: fallback locales are in LANGUAGE, check there. */ + const char *envr = SDL_getenv("LANG"); + if (envr == NULL) { + SDL_SetError("LANG environment variable isn't set"); + } else { + char *ptr; + + SDL_strlcpy(buf, envr, buflen); + + ptr = SDL_strchr(buf, '.'); /* chop off encoding if specified. */ + if (ptr != NULL) { + *ptr = '\0'; + } + + ptr = SDL_strchr(buf, '@'); /* chop off extra bits if specified. */ + if (ptr != NULL) { + *ptr = '\0'; + } + } +} + +/* vi: set ts=4 sw=4 expandtab: */ + diff --git a/src/locale/windows/SDL_syslocale.c b/src/locale/windows/SDL_syslocale.c new file mode 100644 --- /dev/null +++ b/src/locale/windows/SDL_syslocale.c @@ -0,0 +1,54 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_config.h" +#include "../SDL_syslocale.h" + +void +SDL_SYS_GetPreferredLocales(char *buf, size_t buflen) +{ + /* !!! FIXME: there's a newer API in Windows Vista and later, try that first. */ + const char **retval = NULL; + char lang[16]; + char country[16]; + + const int langrc = GetLocaleInfoA(LOCALE_USER_DEFAULT, + LOCALE_SISO639LANGNAME, + lang, sizeof (lang)); + + const int ctryrc = GetLocaleInfoA(LOCALE_USER_DEFAULT, + LOCALE_SISO3166CTRYNAME, + country, sizeof (country)); + + /* Win95 systems will fail, because they don't have LOCALE_SISO*NAME ... */ + if (langrc == 0) { + SDL_SetError("Couldn't obtain language info"); + } else { + if (ctryrc != 0) { + SDL_snprintf(buf, buflen, "%s_%s", lang, country); + } else { + SDL_strlcpy(buf, buflen, lang); + } + } +} + +/* vi: set ts=4 sw=4 expandtab: */ + diff --git a/src/locale/winrt/SDL_syslocale.c b/src/locale/winrt/SDL_syslocale.c new file mode 100644 --- /dev/null +++ b/src/locale/winrt/SDL_syslocale.c @@ -0,0 +1,56 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. +*/ + +#include "SDL_config.h" +#include "../SDL_syslocale.h" + +using namespace Windows::Graphics::Display; +#include + +const char ** +SDL_SYS_GetLocales(void) +{ + WCHAR wbuffer[128] = ""; + int ret = 0; + + /* !!! FIXME: do we not have GetUserPreferredUILanguages on WinPhone or UWP? */ +# if WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP + ret = GetLocaleInfoEx(LOCALE_NAME_SYSTEM_DEFAULT, LOCALE_SNAME, wbuffer, SDL_arraylen(wbuffer)); +# else + ret = GetSystemDefaultLocaleName(wbuffer, SDL_arraylen(wbuffer)); +# endif + + if (ret > 0) + { + /* Need to convert LPWSTR to LPSTR, that is wide char to char. */ + int i; + + if (ret >= (buflen - 1) ) { + ret = buflen - 1; + } + for (i = 0; i < ret; i++) { + buf[i] = (char) wbuffer[i]; /* assume this was ASCII anyhow. */ + } + } +} + +/* vi: set ts=4 sw=4 expandtab: */ + diff --git a/src/video/cocoa/SDL_cocoaevents.m b/src/video/cocoa/SDL_cocoaevents.m --- a/src/video/cocoa/SDL_cocoaevents.m +++ b/src/video/cocoa/SDL_cocoaevents.m @@ -117,6 +117,7 @@ } - (id)init; +- (void)localeDidChange:(NSNotification *)notification; @end @implementation SDLAppDelegate : NSObject @@ -137,6 +138,11 @@ selector:@selector(focusSomeWindow:) name:NSApplicationDidBecomeActiveNotification object:nil]; + + [center addObserver:self + selector:@selector(localeDidChange:) + name:NSCurrentLocaleDidChangeNotification + object:nil]; } return self; @@ -148,6 +154,7 @@ [center removeObserver:self name:NSWindowWillCloseNotification object:nil]; [center removeObserver:self name:NSApplicationDidBecomeActiveNotification object:nil]; + [center removeObserver:self name:NSCurrentLocaleDidChangeNotification object:nil]; [super dealloc]; } @@ -226,6 +233,11 @@ } } +- (void)localeDidChange:(NSNotification *)notification; +{ + SDL_SendLocaleChangedEvent(); +} + - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { return (BOOL)SDL_SendDropFile(NULL, [filename UTF8String]) && SDL_SendDropComplete(NULL); diff --git a/test/Makefile.in b/test/Makefile.in --- a/test/Makefile.in +++ b/test/Makefile.in @@ -37,6 +37,7 @@ testjoystick$(EXE) \ testkeys$(EXE) \ testloadso$(EXE) \ + testlocale$(EXE) \ testlock$(EXE) \ testmessage$(EXE) \ testmultiaudio$(EXE) \ @@ -303,6 +304,10 @@ testvulkan$(EXE): $(srcdir)/testvulkan.c $(CC) -o $@ $^ $(CFLAGS) $(LIBS) +testlocale$(EXE): $(srcdir)/testlocale.c + $(CC) -o $@ $? $(CFLAGS) $(LIBS) + + clean: rm -f $(TARGETS) diff --git a/test/README b/test/README --- a/test/README +++ b/test/README @@ -12,6 +12,7 @@ testjoystick List joysticks and watch joystick events testkeys List the available keyboard keys testloadso Tests the loadable library layer + testlocale Test Locale API testlock Hacked up test of multi-threading and locking testmultiaudio Tests using several audio devices testoverlay2 Tests the overlay flickering/scaling during playback. diff --git a/test/configure b/test/configure --- a/test/configure +++ b/test/configure @@ -640,6 +640,7 @@ docdir oldincludedir includedir +runstatedir localstatedir sharedstatedir sysconfdir @@ -720,6 +721,7 @@ sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE}' @@ -972,6 +974,15 @@ | -silent | --silent | --silen | --sile | --sil) silent=yes ;; + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1109,7 +1120,7 @@ for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir + libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -1262,6 +1273,7 @@ --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] diff --git a/test/testlocale.c b/test/testlocale.c new file mode 100644 --- /dev/null +++ b/test/testlocale.c @@ -0,0 +1,65 @@ +/* + Copyright (C) 1997-2020 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely. +*/ +#include +#include "SDL.h" + +/* !!! FIXME: move this to the test framework */ + +static void log_locales(void) +{ + SDL_Locale *locales = SDL_GetPreferredLocales(); + if (locales == NULL) { + SDL_Log("Couldn't determine locales."); + } else { + SDL_Locale *l; + SDL_Log("Locales, in order of preference:"); + for (l = locales; l->language; l++) { + const char *c = l->country; + SDL_Log(" - %s%s%s", l->language, c ? "_" : "", c ? c : ""); + } + SDL_Log(""); + SDL_free(locales); + } +} + +int main(int argc, char **argv) +{ + /* Enable standard application logging */ + SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); + + /* Print locales and languages */ + if (SDL_Init(SDL_INIT_VIDEO) != -1) { + log_locales(); + + if ((argc == 2) && (SDL_strcmp(argv[1], "--listen") == 0)) { + SDL_bool keep_going = SDL_TRUE; + while (keep_going) { + SDL_Event e; + while (SDL_PollEvent(&e)) { + if (e.type == SDL_QUIT) { + keep_going = SDL_FALSE; + } else if (e.type == SDL_LOCALECHANGED) { + SDL_Log("Saw SDL_LOCALECHANGED event!"); + log_locales(); + } + } + SDL_Delay(10); + } + } + + SDL_Quit(); + } + + return 0; +} + +/* vi: set ts=4 sw=4 expandtab: */