diff --git a/FL/Fl_Preferences.H b/FL/Fl_Preferences.H index 9378ec408..21e331cfc 100644 --- a/FL/Fl_Preferences.H +++ b/FL/Fl_Preferences.H @@ -1,7 +1,7 @@ // // Preferences implementation for the Fast Light Tool Kit (FLTK). // -// Copyright 2002-2010 by Matthias Melcher. +// Copyright 2002-2022 by Matthias Melcher. // // This library is free software. Distribution and use rights are outlined in // the file "COPYING" which should have been included with this file. If this @@ -24,31 +24,74 @@ # include "Fl_Export.H" /** - \brief Fl_Preferences provides methods to store user - settings between application starts. - - It is similar to the - Registry on Windows and Preferences on MacOS, and provides a - simple configuration mechanism for UNIX. - - Fl_Preferences uses a hierarchy to store data. It - bundles similar data into groups and manages entries in these - groups as name/value pairs. - - Preferences are stored in text files that can be edited - manually. The file format is easy to read and relatively - forgiving. Preferences files are the same on all platforms. User - comments in preference files are preserved. Filenames are unique - for each application by using a vendor/application naming - scheme. The user must provide default values for all entries to - ensure proper operation should preferences be corrupted or not - yet exist. - - Entries can be of any length. However, the size of each - preferences file should be kept small for performance - reasons. One application can have multiple preferences files. - Extensive binary data however should be stored in separate - files: see \a Fl_Preferences::getUserdataPath() . + \brief Fl_Preferences store user settings between application starts. + + Fl_Preferences are similar to the Registry on Windows and Preferences on MacOS, + providing a simple method to store customisable user settings between app + launches, i.e. the previous window position or a history of previously + used documents. + + Preferences are organized in a hierarchy of groups. Every group can contain + more groups and any number of kay/value pairs. Keys can be text strings + containing ASCII letters, digits, periods, and underscores. Forward slashes + in a key name are treated as subgroups, i.e the key 'window/width' would + actually refere to the key 'width' inside the group 'window'. + + Keys have usually a unique name within their group. Duplicate kays are + possible though and can beaccessed using the index based functions. + + A value should be an ASCII string. Control characters and utf8 sequences are + stores as octal values. Long strings will wrap at the line ending and will be + reassembled when reading the file back. + + Many shortcuts exist to set and get numerical values and binary data. + + Preferences are stored in text files that can be edited manually if needed. + The file format is easy to read and relatively forgiving. Preferences files + are the same on all platforms. User comments in preference files are preserved. + Filenames are unique for each application by using a vendor/application naming + scheme. The user must provide default values for all entries to ensure proper + operation should preferences be corrupted or not yet exist. + + FLTK preferences are not meant to replace a fully features database. No merging + of data takes place. If several instances of an app access the same database at + the same time, only the most recent changes will persist. + + Preferences should no be used to store document data. The .prefs file should + be kept small for performance reasons. One application can have multiple + preferences files. Extensive binary data however should be stored in separate + files: see \a Fl_Preferences::getUserdataPath() . + + Fl_Preferences are not thread-safe. They can temprorarily change the locale + on some platforms during read an write access, which is alse observable in + other threads of the same app. + + Typically a preferences database is read at startup and close, and then writte + again at app shutdown: + ```.cpp + int appWindowWidth, appWindowHeight; + void launch() { + Fl_Preferences app(Fl_Preferences::USER_L, "matthiasm.com", "hello"); + // 'app' constructor will be called, reading data from .prefs file + Fl_Preferences window(app, "window"); + window.get("width", appWindowWidth, 800); + window.get("height", appWindowHeight, 600); + // 'app' destructor will be called, writing data to .prefs file + } + void quit() { + Fl_Preferences app(Fl_Preferences::USER_L, "matthiasm.com", "hello"); + Fl_Preferences window(app, "window"); + window.set("width", appWindowWidth); + window.set("height", appWindowHeight); + } + ``` + + \see Fl_Preferences::Fl_Preferences( Root root, const char *vendor, const char *application ) + + As a special case, Fl_Preferences can be memeory mapped and not be associated + with a file on disk. + + \see Fl_Preferences::Fl_Preferences( Fl_Preferences *parent, const char *group ) \note Starting with FLTK 1.3, preference databases are expected to be in UTF-8 encoding. Previous databases were stored in the @@ -59,6 +102,14 @@ the preferences files has changed slightly. Please see Fl_Preferences::Fl_Preferences(Root, const char*, const char*) for details. + + \note Starting with FLTK 1.4, preference files should be created with + `SYSTEM_L` or `USER_L` to be interchangeable between computers with + differing loacale settings. The legacy modes, `LOCAL` and `SYSTEM`, will + read and write floating point values using the decimal point of the + current locale. As a result, a fp-value would be writte '3,1415' on a + German machine, and would be read back as '3.0' on a US machine because + the comma would not be recoginized as an alternative decimal point. */ class FL_EXPORT Fl_Preferences { @@ -67,12 +118,17 @@ public: Define the scope of the preferences. */ enum Root { - SYSTEM = 0, ///< Preferences are used system-wide - USER, ///< Preferences apply only to the current user + UNKNOWN_ROOT_TYPE = -1, ///< Returned if storage could not be determined. + SYSTEM = 0, ///< Preferences are used system-wide, deprecated, see SYSTEM_L + USER, ///< Preferences apply only to the current user, deprecated, see USER_L + MEMORY, ///< Returned if querying runtime prefs ROOT_MASK = 0xFF, ///< masks for the values above CORE = 0x100, ///< OR'd by FLTK to read and write core library preferences and options CORE_SYSTEM = CORE|SYSTEM, - CORE_USER = CORE|USER + CORE_USER = CORE|USER, + C_LOCALE = 0x1000, ///< this flag should always be set, it makes sure that floating point values wre writte correctly independently of the current locale + SYSTEM_L = SYSTEM|C_LOCALE, ///< Preferences are used system-wide + USER_L = USER|C_LOCALE, ///< Preferences apply only to the current user }; /** @@ -120,6 +176,7 @@ public: static void file_access(unsigned int flags); static unsigned int file_access(); + static Root filename( char *buffer, size_t buffer_size, Root root, const char *vendor, const char *application ); Fl_Preferences( Root root, const char *vendor, const char *application ); Fl_Preferences( const char *path, const char *vendor, const char *application ); @@ -131,6 +188,8 @@ public: Fl_Preferences( ID id ); virtual ~Fl_Preferences(); + Root filename( char *buffer, size_t buffer_size); + /** Return an ID that can later be reused to open more references to this dataset. */ ID id() { return (ID)node; } @@ -176,12 +235,15 @@ public: char get( const char *entry, char *value, const char *defaultValue, int maxSize ); char get( const char *entry, void *&value, const void *defaultValue, int defaultSize ); char get( const char *entry, void *value, const void *defaultValue, int defaultSize, int maxSize ); + char get( const char *entry, void *value, const void *defaultValue, int defaultSize, int *size ); int size( const char *entry ); char getUserdataPath( char *path, int pathlen ); - void flush(); + int flush(); + + int dirty(); // char export( const char *filename, Type fileFormat ); // char import( const char *filename ); @@ -233,10 +295,10 @@ public: // older Sun compilers need this (public definition of the following cl class FL_EXPORT Node { // a node contains a list to all its entries // and all means to manage the tree structure - Node *child_, *next_; + Node *first_child_, *next_; union { // these two are mutually exclusive Node *parent_; // top_ bit clear - RootNode *root_; // top_ bit set + RootNode *root_node_; // top_ bit set }; char *path_; Entry *entry_; @@ -265,7 +327,7 @@ public: // older Sun compilers need this (public definition of the following cl Node *addChild( const char *path ); void setParent( Node *parent ); Node *parent() { return top_?0L:parent_; } - void setRoot(RootNode *r) { root_ = r; top_ = 1; } + void setRoot(RootNode *r) { root_node_ = r; top_ = 1; } RootNode *findRoot(); char remove(); char dirty(); @@ -290,7 +352,7 @@ public: // older Sun compilers need this (public definition of the following cl Fl_Preferences *prefs_; char *filename_; char *vendor_, *application_; - Root root_; + Root root_type_; public: RootNode( Fl_Preferences *, Root root, const char *vendor, const char *application ); RootNode( Fl_Preferences *, const char *path, const char *vendor, const char *application ); @@ -299,6 +361,8 @@ public: // older Sun compilers need this (public definition of the following cl int read(); int write(); char getPath( char *path, int pathlen ); + char *filename() { return filename_; } + Root root() { return root_type_; } }; friend class RootNode; diff --git a/fluid/Fl_Function_Type.cxx b/fluid/Fl_Function_Type.cxx index 3c728863e..f51570d92 100644 --- a/fluid/Fl_Function_Type.cxx +++ b/fluid/Fl_Function_Type.cxx @@ -1530,7 +1530,7 @@ static void load_comments_preset(Fl_Preferences &menu) { "FLTK/Header" }; int i; menu.set("n", 5); - Fl_Preferences db(Fl_Preferences::USER, "fltk.org", "fluid_comments"); + Fl_Preferences db(Fl_Preferences::USER_L, "fltk.org", "fluid_comments"); for (i=0; i<5; i++) { menu.set(Fl_Preferences::Name(i), predefined_comment[i]); db.set(predefined_comment[i], comment_text[i]); @@ -1545,7 +1545,7 @@ void Fl_Comment_Type::open() { const char *text = name(); { int i=0, n=0; - Fl_Preferences menu(Fl_Preferences::USER, "fltk.org", "fluid_comments_menu"); + Fl_Preferences menu(Fl_Preferences::USER_L, "fltk.org", "fluid_comments_menu"); comment_predefined->clear(); comment_predefined->add("_Edit/Add current comment..."); comment_predefined->add("_Edit/Remove last selection..."); @@ -1583,9 +1583,9 @@ void Fl_Comment_Type::open() { char *name = fl_strdup(xname); for (char*s=name;*s;s++) if (*s==':') *s = ';'; int n; - Fl_Preferences db(Fl_Preferences::USER, "fltk.org", "fluid_comments"); + Fl_Preferences db(Fl_Preferences::USER_L, "fltk.org", "fluid_comments"); db.set(name, comment_input->buffer()->text()); - Fl_Preferences menu(Fl_Preferences::USER, "fltk.org", "fluid_comments_menu"); + Fl_Preferences menu(Fl_Preferences::USER_L, "fltk.org", "fluid_comments_menu"); menu.get("n", n, 0); menu.set(Fl_Preferences::Name(n), name); menu.set("n", ++n); @@ -1599,10 +1599,10 @@ void Fl_Comment_Type::open() { } else if (fl_choice("Are you sure that you want to delete the entry\n" "\"%s\"\nfrom the database?", "Cancel", "Delete", NULL, itempath)) { - Fl_Preferences db(Fl_Preferences::USER, "fltk.org", "fluid_comments"); + Fl_Preferences db(Fl_Preferences::USER_L, "fltk.org", "fluid_comments"); db.deleteEntry(itempath); comment_predefined->remove(last_selected_item); - Fl_Preferences menu(Fl_Preferences::USER, "fltk.org", "fluid_comments_menu"); + Fl_Preferences menu(Fl_Preferences::USER_L, "fltk.org", "fluid_comments_menu"); int i, n; for (i=4, n=0; isize(); i++) { const Fl_Menu_Item *mi = comment_predefined->menu()+i; @@ -1617,7 +1617,7 @@ void Fl_Comment_Type::open() { // load the selected comment from the database if (comment_predefined->item_pathname(itempath, 255)==0) { if (itempath[0]=='/') memmove(itempath, itempath+1, 255); - Fl_Preferences db(Fl_Preferences::USER, "fltk.org", "fluid_comments"); + Fl_Preferences db(Fl_Preferences::USER_L, "fltk.org", "fluid_comments"); char *text; db.get(itempath, text, "(no text found in data base)"); comment_input->buffer()->text(text); diff --git a/fluid/alignment_panel.fl b/fluid/alignment_panel.fl index 716ebcca3..af64ecb73 100644 --- a/fluid/alignment_panel.fl +++ b/fluid/alignment_panel.fl @@ -1,5 +1,10 @@ # data file for the Fltk User Interface Designer (fluid) version 1.0400 +i18n_type 1 +i18n_include {} +i18n_conditional FLTK_GETTEXT_FOUND +i18n_function gettext +i18n_static_function gettext_noop header_name {.h} code_name {.cxx} comment {// @@ -35,7 +40,7 @@ decl {\#include } {public local decl {\#include } {public local } -decl {\#include } {selected public local +decl {\#include } {public local } decl {\#include } {private global @@ -323,7 +328,7 @@ Function {make_shell_window()} {open tooltip {save the design to the .fl file before running the command} xywh {82 39 136 19} down_box DOWN_BOX labelsize 12 } Fl_Check_Button shell_writecode_button { - label {save source code} + label {save source code} selected tooltip {generate the source code and header file before running the command} xywh {82 59 120 19} down_box DOWN_BOX labelsize 12 } Fl_Check_Button shell_writemsgs_button { @@ -524,11 +529,11 @@ wShowZoomFactor->value(opt[Fl::OPTION_SHOW_SCALING][mode]);} {} } Function {readPrefs()} { - comment {read all preferences and refresh the GUI} private return_type void + comment {read all preferences and refresh the GUI} open private return_type void } { code {// read all preferences and refresh the GUI { - Fl_Preferences prefs(Fl_Preferences::SYSTEM, "fltk.org", "fltk"); + Fl_Preferences prefs(Fl_Preferences::SYSTEM_L, "fltk.org", "fltk"); Fl_Preferences opt_prefs(prefs, "options"); opt_prefs.get("ArrowFocus", opt[Fl::OPTION_ARROW_FOCUS][1], 2); opt_prefs.get("VisibleFocus", opt[Fl::OPTION_VISIBLE_FOCUS][1], 2); @@ -539,7 +544,7 @@ Function {readPrefs()} { opt_prefs.get("ShowZoomFactor", opt[Fl::OPTION_SHOW_SCALING ][1], 2); } { - Fl_Preferences prefs(Fl_Preferences::USER, "fltk.org", "fltk"); + Fl_Preferences prefs(Fl_Preferences::USER_L, "fltk.org", "fltk"); Fl_Preferences opt_prefs(prefs, "options"); opt_prefs.get("ArrowFocus", opt[Fl::OPTION_ARROW_FOCUS][0], 2); opt_prefs.get("VisibleFocus", opt[Fl::OPTION_VISIBLE_FOCUS][0], 2); @@ -553,11 +558,11 @@ refreshUI();} {} } Function {writePrefs()} { - comment {write all preferences using the array} private return_type void + comment {write all preferences using the array} open private return_type void } { code {// write all preferences using the array { - Fl_Preferences prefs(Fl_Preferences::SYSTEM, "fltk.org", "fltk"); + Fl_Preferences prefs(Fl_Preferences::SYSTEM_L, "fltk.org", "fltk"); Fl_Preferences opt_prefs(prefs, "options"); if (opt[Fl::OPTION_ARROW_FOCUS][1]==2) opt_prefs.deleteEntry("ArrowFocus"); else opt_prefs.set("ArrowFocus", opt[Fl::OPTION_ARROW_FOCUS][1]); @@ -575,7 +580,7 @@ Function {writePrefs()} { else opt_prefs.set("ShowZoomFactor", opt[Fl::OPTION_SHOW_SCALING][1]); } { - Fl_Preferences prefs(Fl_Preferences::USER, "fltk.org", "fltk"); + Fl_Preferences prefs(Fl_Preferences::USER_L, "fltk.org", "fltk"); Fl_Preferences opt_prefs(prefs, "options"); if (opt[Fl::OPTION_ARROW_FOCUS][0]==2) opt_prefs.deleteEntry("ArrowFocus"); else opt_prefs.set("ArrowFocus", opt[Fl::OPTION_ARROW_FOCUS][0]); @@ -594,7 +599,7 @@ Function {writePrefs()} { }} {} } -Function {show_global_settings_window()} {return_type void +Function {show_global_settings_window()} {open return_type void } { code {if (!global_settings_window) make_global_settings_window(); diff --git a/fluid/fluid.cxx b/fluid/fluid.cxx index 1ed1ad93b..aa0b2ee19 100644 --- a/fluid/fluid.cxx +++ b/fluid/fluid.cxx @@ -72,7 +72,7 @@ Fl_Menu_Bar *main_menubar = NULL; Fl_Window *main_window; /// Fluid application preferences, allways accessible, will be flushed when app closes. -Fl_Preferences fluid_prefs(Fl_Preferences::USER, "fltk.org", "fluid"); +Fl_Preferences fluid_prefs(Fl_Preferences::USER_L, "fltk.org", "fluid"); /// Align widget position and size when designing, saved in app preferences and project file. int gridx = 5; diff --git a/src/Fl_Preferences.cxx b/src/Fl_Preferences.cxx index 0ac1c17ea..83fa3da99 100644 --- a/src/Fl_Preferences.cxx +++ b/src/Fl_Preferences.cxx @@ -1,7 +1,7 @@ // // Preferences methods for the Fast Light Tool Kit (FLTK). // -// Copyright 2011-2020 by Bill Spitzak and others. +// Copyright 2011-2022 by Bill Spitzak and others. // Copyright 2002-2010 by Matthias Melcher. // // This library is free software. Distribution and use rights are outlined in @@ -34,6 +34,24 @@ char Fl_Preferences::uuidBuffer[40]; Fl_Preferences *Fl_Preferences::runtimePrefs = 0; unsigned int Fl_Preferences::fileAccess_ = Fl_Preferences::ALL; +static int clocale_snprintf(char *buffer, size_t buffer_size, const char *format, ...) +{ + va_list args; + va_start(args, format); + int retval = Fl::system_driver()->clocale_snprintf(buffer, buffer_size, format, args); + va_end(args); + return retval; +} + +static int clocale_sscanf(const char *input, const char *format, ...) +{ + va_list args; + va_start(args, format); + int retval = Fl::system_driver()->clocale_sscanf(input, format, args); + va_end(args); + return retval; +} + /** Returns a UUID as generated by the system. @@ -100,69 +118,109 @@ unsigned int Fl_Preferences::file_access() return fileAccess_; } +/** + Determine the file name and path to preferences that would be openend with + these parameters. + + Find the possible location of a preference file on disk without touching any + of the pathname componennts. This can be used to check if a preferneces file + already exists. + + \param[out] buffer write the reulting path into this buffer + \param[in] buffer_size size of the `buffer` in bytes + \param[in] root can be \c USER_L or \c SYSTEM_L for user specific or system + wide preferences + \param[in] vendor unique text describing the company or author of this file, + must be a valid filepath segment + \param[in] application unique text describing the application, must be a + valid filepath segment + \return the input root value, or Fl_Preferences::UNKNOWN_ROOT_TYPE if the path + could not be determined. + \see Fl_Preferences( Root root, const char *vendor, const char *application ) + */ +Fl_Preferences::Root Fl_Preferences::filename( char *buffer, size_t buffer_size, Root root, const char *vendor, const char *application ) +{ + Root ret = UNKNOWN_ROOT_TYPE; + if (buffer && buffer_size>0) { + char *fn = Fl::system_driver()->preference_rootnode(NULL, root, vendor, application); + if (fn) { + fl_strlcpy(buffer, fn, buffer_size); + // FLTK always returns forward slashes in paths + { char *s; for ( s = buffer; *s; s++ ) if ( *s == '\\' ) *s = '/'; } + ret = root; + } else { + buffer[0] = 0; + } + } + return ret; +} + + /** The constructor creates a group that manages name/value pairs and child groups. Groups are ready for reading and writing at any time. - The root argument is either Fl_Preferences::USER - or Fl_Preferences::SYSTEM. - - This constructor creates the base instance for all - following entries and reads existing databases into memory. The - vendor argument is a unique text string identifying the - development team or vendor of an application. A domain name or - an EMail address are great unique names, e.g. - "research.matthiasm.com" or "fluid.fltk.org". The - application argument can be the working title or final - name of your application. Both vendor and - application must be valid UNIX path segments and - may contain forward slashes to create deeper file structures. - - A set of Preferences marked "run-time" exists exactly once per application and - only as long as the application runs. It can be used as a database for - volatile information. FLTK uses it to register plugins at run-time. - - \note On \b Windows, the directory is constructed by querying the Common AppData - or AppData key of the Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders - registry entry. The filename and path is then constructed as \$(query)/\$(vendor)/\$(application).prefs . - If the query call fails, data will be stored in RAM only and be lost when the app exits. - - \par In FLTK versions before 1.4.0, if querying the registry failed, preferences would be written to + The root argument is either `Fl_Preferences::USER_L` + or `Fl_Preferences::SYSTEM_L`. + + This constructor creates the base instance for all following entries + and reads the database from disk into memory if it exists. + The vendor argument is a unique text string identifying the development team + or vendor of an application. A domain name or an EMail address (replacing + the '@' with a '.') are great unique names, e.g. "research.matthiasm.com" or + "fluid.fltk.org". + The application argument can be the working title or final name of your + application. + Both vendor and application must be valid UNIX path segments as they become + parts of the preferences file path and may contain forward slashes to create + deeper file structures. + + \note On \b Windows, the directory is constructed by querying the + Common AppData or AppData key of the + Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders + registry entry. + The filename and path is then constructed as + \$(query)/\$(vendor)/\$(application).prefs . + If the query call fails, data will be stored in RAM only. + It will be lost when the app exits. + + \par In FLTK versions before 1.4.0, if querying the registry failed, + preferences would be written to C:\\FLTK\\\$(vendor)\\\$(application).prefs . - \note On \b Linux, the \c USER directory is constructed by reading \c $HOME . If \c $HOME is not set - or not pointing to an existing directory, we are checking the path member of the passwd struct returned by - \c getpwuid(getuid()) . If all attempts fail, data will be stored in RAM only and be lost when the app exits. - The filename and path is then constructed as \$(directory)/.fltk/\$(vendor)/\$(application).prefs . - The \c SYSTEM directory is hardcoded as /etc/fltk/\$(vendor)/\$(application).prefs . - - \par In FLTK versions before 1.4.0, if \c $HOME was not set, the \c USER path would be empty, - generating \$(vendor)/\$(application).prefs, which was used relative to the current working directory. - - \note On \b macOS, the \c USER directory is constructed by reading \c $HOME . If \c $HOME is not set - or not pointing to an existing directory, we check the path returned by \c NSHomeDirectory() , and - finally checking the path member of the passwd struct returned by \c getpwuid(getuid()) . + \note On \b Linux, the \c USER directory is constructed by reading \c $HOME . + If \c $HOME is not set or not pointing to an existing directory, FLTK will + check the path member of the passwd struct returned by \c getpwuid(getuid()) . + If all attempts fail, data will be stored in RAM only and be lost when the + app exits. + The filename and path is then constructed as + \$(directory)/.fltk/\$(vendor)/\$(application).prefs . + The \c SYSTEM directory is hardcoded as + /etc/fltk/\$(vendor)/\$(application).prefs . + + \par In FLTK versions before 1.4.0, if \c $HOME was not set, the \c USER path + would be empty, generating \$(vendor)/\$(application).prefs, which + was used relative to the current working directory. + + \note On \b macOS, the \c USER directory is constructed by reading \c $HOME . + If \c $HOME is not set or not pointing to an existing directory, we check the + path returned by \c NSHomeDirectory() , and finally checking the path member + of the passwd struct returned by \c getpwuid(getuid()) . If all attempts fail, data will be stored in RAM only and be lost when the app exits. - The filename and path is then constructed as \$(directory)/Library/Preferences/\$(vendor)/\$(application).prefs . - The \c SYSTEM directory is hardcoded as /Library/Preferences/\$(vendor)/\$(application).prefs . + The filename and path is then constructed as + \$(directory)/Library/Preferences/\$(vendor)/\$(application).prefs . + The \c SYSTEM directory is hardcoded as + /Library/Preferences/\$(vendor)/\$(application).prefs . - \par In FLTK versions before 1.4.0, if \c $HOME was not set, the \c USER path would be \c NULL , - generating \/Library/Preferences/\$(vendor)/\$(application).prefs, which would silently fail to - create a preferences file. + \par In FLTK versions before 1.4.0, if \c $HOME was not set, the \c USER path + would be \c NULL , generating + \/Library/Preferences/\$(vendor)/\$(application).prefs, + which would silently fail to create a preferences file. - \param[in] root can be \c USER or \c SYSTEM for user specific or system wide preferences + \param[in] root can be \c USER_L or \c SYSTEM_L for user specific or system wide preferences \param[in] vendor unique text describing the company or author of this file, must be a valid filepath segment \param[in] application unique text describing the application, must be a valid filepath segment - \todo (Matt) Before the release of 1.4.0, I want to make a further attempt to write a preferences file smarter. I - plan to use a subgroup of the "runtime" preferences to store data and stay accessible until the application - exits. Data would be stored under ./\$(vendor)/\$(application).prefs in RAM, but not on disk. - - \todo (Matt) I want a way to access the type of the root preferences (SYSTEM, USER, MEMORY), and the state of - the file access (OK, FILE_SYSTEM_FAIL, PERMISSION_FAIL, etc.), and probably the dirty() flag as well. - - \todo (Matt) Also, I need to explain runtime preferences. - - \todo (Matt) Lastly, I think I have to put short sample code in the Doxygen docs. The test app ist just not enough. + \see Fl_Preferences(Fl_Preferences *parent, const char *group) with parent set to NULL */ Fl_Preferences::Fl_Preferences( Root root, const char *vendor, const char *application ) { node = new Node( "." ); @@ -203,22 +261,49 @@ Fl_Preferences::Fl_Preferences( Fl_Preferences &parent, const char *group ) { } /** - \brief Create or access a group of preferences using a name. + \brief Create or access a group of preferences using a name. + + Parent should point to a previously created parent preferences group to + create a preferences hierarchy. + + If `parent` is set to `NULL`, an unnamed database will be accessed that exists + only in local memory and is not associated with a file on disk. The root type + of this databse is set to `Fl_Preferences::MEMORY`. + + * the memory database is \em not shared among multiple instances of the same app + * memory databses are \em not thread safe + * all data will be lost when the app quits + + ```{.cpp} + void some_function() { + Fl_Preferences guide( NULL, "Guide" ); + guide.set("answer", 42); + } + void other_function() { + int x; + Fl_Preferences guide( NULL, "Guide" ); + guide.get("answer", x, -1); + } + ``` + + FLTK uses the memory database to manage plugins. See `Fl_Plugin`. + \param[in] parent the parameter parent is a pointer to the parent group. - \p Parent may be \p NULL. It then refers to an application internal - database which exists only once, and remains in RAM only until the - application quits. This database is used to manage plugins and other - data indexes by strings. + If \p Parent is \p NULL, the new Preferences item refers to an + application internal database ("runtime prefs") which exists only + once, and remains in RAM only until the application quits. + This database is used to manage plugins and other data indexes + by strings. Runtime Prefs are \em not thread-safe. \param[in] group a group name that is used as a key into the database \see Fl_Preferences( Fl_Preferences&, const char *group ) */ Fl_Preferences::Fl_Preferences( Fl_Preferences *parent, const char *group ) { - if (parent==0) { + if (parent==NULL) { if (!runtimePrefs) { runtimePrefs = new Fl_Preferences(); runtimePrefs->node = new Node( "." ); runtimePrefs->rootNode = new RootNode( runtimePrefs ); - runtimePrefs->node->setRoot(rootNode); + runtimePrefs->node->setRoot(runtimePrefs->rootNode); } parent = runtimePrefs; } @@ -313,6 +398,35 @@ Fl_Preferences::~Fl_Preferences() { rootNode = 0L; } +/** + Return the file name and path to the Preferences file. + + If the preferences have not changed or have not been flushed, the file + or directory may not have been created yet. + + \param[out] buffer write the reulting path into this buffer + \param[in] buffer_size size of the `buffer` in bytes + \return the root type at creation type, or MEMORY for runtime prefs, it does + not return CORE or LOCALE flags. + */ +Fl_Preferences::Root Fl_Preferences::filename( char *buffer, size_t buffer_size) +{ + if (!buffer || buffer_size==0) + return UNKNOWN_ROOT_TYPE; + RootNode *rn = rootNode; + if (!rn) + return UNKNOWN_ROOT_TYPE; + if (rn->root()==MEMORY) + return MEMORY; + char *fn = rn->filename(); + if (!fn) + return UNKNOWN_ROOT_TYPE; + fl_strlcpy(buffer, fn, buffer_size); + if (buffer[0]==0) + return UNKNOWN_ROOT_TYPE; + return (Root)(rn->root() & ROOT_MASK); +} + /** Returns the number of groups that are contained within a group. @@ -474,8 +588,16 @@ char Fl_Preferences::set( const char *key, int value ) { */ char Fl_Preferences::get( const char *key, float &value, float defaultValue ) { const char *v = node->get( key ); - value = v ? (float)atof( v ) : defaultValue; - return ( v != 0 ); + if (v) { + if (rootNode->root() & C_LOCALE) { + clocale_sscanf(v, "%g", &value); + } else { + value = (float)atof(v); + } + } else { + value = defaultValue; + } + return ( v != NULL ); } /** @@ -489,7 +611,11 @@ char Fl_Preferences::get( const char *key, float &value, float defaultValue ) { \return 0 if setting the value failed */ char Fl_Preferences::set( const char *key, float value ) { - sprintf( nameBuffer, "%g", value ); + if (rootNode->root() & C_LOCALE) { + clocale_snprintf( nameBuffer, sizeof(nameBuffer), "%g", value ); + } else { + snprintf( nameBuffer, sizeof(nameBuffer), "%g", value ); + } node->set( key, nameBuffer ); return 1; } @@ -506,7 +632,11 @@ char Fl_Preferences::set( const char *key, float value ) { \return 0 if setting the value failed */ char Fl_Preferences::set( const char *key, float value, int precision ) { - sprintf( nameBuffer, "%.*g", precision, value ); + if (rootNode->root() & C_LOCALE) { + clocale_snprintf( nameBuffer, sizeof(nameBuffer), "%.*g", precision, value ); + } else { + snprintf( nameBuffer, sizeof(nameBuffer), "%.*g", precision, value ); + } node->set( key, nameBuffer ); return 1; } @@ -523,8 +653,16 @@ char Fl_Preferences::set( const char *key, float value, int precision ) { */ char Fl_Preferences::get( const char *key, double &value, double defaultValue ) { const char *v = node->get( key ); - value = v ? atof( v ) : defaultValue; - return ( v != 0 ); + if (v) { + if (rootNode->root() & C_LOCALE) { + clocale_sscanf(v, "%lg", &value); + } else { + value = atof(v); + } + } else { + value = defaultValue; + } + return ( v != NULL ); } /** @@ -538,7 +676,11 @@ char Fl_Preferences::get( const char *key, double &value, double defaultValue ) \return 0 if setting the value failed */ char Fl_Preferences::set( const char *key, double value ) { - sprintf( nameBuffer, "%g", value ); + if (rootNode->root() & C_LOCALE) { + clocale_snprintf( nameBuffer, sizeof(nameBuffer), "%lg", value ); + } else { + snprintf( nameBuffer, sizeof(nameBuffer), "%lg", value ); + } node->set( key, nameBuffer ); return 1; } @@ -555,7 +697,11 @@ char Fl_Preferences::set( const char *key, double value ) { \return 0 if setting the value failed */ char Fl_Preferences::set( const char *key, double value, int precision ) { - sprintf( nameBuffer, "%.*g", precision, value ); + if (rootNode->root() & C_LOCALE) { + clocale_snprintf( nameBuffer, sizeof(nameBuffer), "%.*lg", precision, value ); + } else { + snprintf( nameBuffer, sizeof(nameBuffer), "%.*lg", precision, value ); + } node->set( key, nameBuffer ); return 1; } @@ -695,19 +841,17 @@ static void *decodeHex( const char *src, int &size ) { } /** - Reads an entry from the group. A default value must be - supplied. The return value indicates if the value was available - (non-zero) or the default was used (0). - 'maxSize' is the maximum length of text that will be read. + Reads a binary entry from the group, ancoded in hexadecimal blocks. \param[in] key name of entry \param[out] data value returned from preferences or default value if none was set - \param[in] defaultValue default value to be used if no preference was set + \param[in] defaultValue default value \param[in] defaultSize size of default value array - \param[in] maxSize maximum length of value + \param[in] maxSize maximum length of value, to receive the number of bytes + read, use the function below instead. \return 0 if the default value was used - \todo maxSize should receive the number of bytes that were read. + \see Fl_Preferences::get( const char *key, void *data, const void *defaultValue, int defaultSize, int *maxSize ) */ char Fl_Preferences::get( const char *key, void *data, const void *defaultValue, int defaultSize, int maxSize ) { const char *v = node->get( key ); @@ -723,6 +867,45 @@ char Fl_Preferences::get( const char *key, void *data, const void *defaultValue, return 0; } +/** + Reads a binary entry from the group, ancoded in hexadecimal blocks. + A binary (not hex) default value can be supplied. + The return value indicates if the value was available (non-zero) or the + default was used (0). + `maxSize` is the maximum length of text that will be read and returns the + actual number of bytes read. + + \param[in] key name of entry + \param[out] data value returned from preferences or default value if none was set + \param[in] defaultValue default value to be used if no preference was set + \param[in] defaultSize size of default value array + \param[inout] maxSize maximum length of value and actual number of bytes set + \return 0 if the default value was used + */ +char Fl_Preferences::get( const char *key, void *data, const void *defaultValue, int defaultSize, int *maxSize ) { + if (!maxSize || !data) + return -1; + int capacity = *maxSize; + const char *v = node->get( key ); + if ( v ) { + int nFound; + void *w = decodeHex( v, nFound ); + int nWrite = (nFound>capacity) ? capacity : nFound; + memmove( data, w, nWrite); + free( w ); + *maxSize = nWrite; + return 1; + } + if ( defaultValue ) { + int nWrite = (defaultSize>capacity) ? capacity : defaultSize; + memmove( data, defaultValue, nWrite ); + *maxSize = nWrite; + } else { + *maxSize = 0; + } + return 0; +} + /** Reads an entry from the group. A default value must be supplied. The return value indicates if the value was available @@ -836,13 +1019,40 @@ char Fl_Preferences::getUserdataPath( char *path, int pathlen ) { } /** - Writes all preferences to disk. This function works only with - the base preferences group. This function is rarely used as - deleting the base preferences flushes automatically. + Writes preferences to disk if they were modified. + + This method can be used to verify that writing a preferences file went well. + Deleting the base preferences object will also write the contents of the + database to disk. + + \return -1 if anything went wrong, i.e. file could not be opened, permissions + blocked writing, etc. + \return 0 if the file was written to disk. This does not check if the disk ran + out of space and the file is truncated. + \return 1 if there no data written to the database and no write attempt + to disk was made. */ -void Fl_Preferences::flush() { - if ( rootNode && node->dirty() ) - rootNode->write(); +int Fl_Preferences::flush() { + int ret = dirty(); + if (ret!=1) + return ret; + return rootNode->write(); +} + +/** + Check if there were changes to the database that need to be written to disk. + + \return 1 if the database will be written to disk by `flush` or destructor. + \return 0 if the databse is unchanged since the last write operation. + \return -1 f there is an internal database error. + */ +int Fl_Preferences::dirty() { + Node *n = node; + while (n && n->parent()) + n = n->parent(); + if (!n) + return -1; + return n->dirty(); } //----------------------------------------------------------------------------- @@ -909,7 +1119,7 @@ Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, Root root, const char filename_(0L), vendor_(0L), application_(0L), - root_(root) + root_type_(root) { char *filename = Fl::system_driver()->preference_rootnode(prefs, root, vendor, application); filename_ = filename ? fl_strdup(filename) : 0L; @@ -925,7 +1135,7 @@ Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs, const char *path, con filename_(0L), vendor_(0L), application_(0L), - root_(Fl_Preferences::USER) + root_type_(Fl_Preferences::USER) { if (!vendor) @@ -950,7 +1160,7 @@ Fl_Preferences::RootNode::RootNode( Fl_Preferences *prefs ) filename_(0L), vendor_(0L), application_(0L), - root_(Fl_Preferences::USER) + root_type_(Fl_Preferences::MEMORY) { } @@ -978,15 +1188,15 @@ Fl_Preferences::RootNode::~RootNode() { int Fl_Preferences::RootNode::read() { if (!filename_) // RUNTIME preferences, or filename could not be created return -1; - if ( (root_ & Fl_Preferences::CORE) && !(fileAccess_ & Fl_Preferences::CORE_READ_OK) ) { + if ( (root_type_ & Fl_Preferences::CORE) && !(fileAccess_ & Fl_Preferences::CORE_READ_OK) ) { prefs_->node->clearDirtyFlags(); return -1; } - if ( ((root_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::USER) && !(fileAccess_ & Fl_Preferences::USER_READ_OK) ) { + if ( ((root_type_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::USER) && !(fileAccess_ & Fl_Preferences::USER_READ_OK) ) { prefs_->node->clearDirtyFlags(); return -1; } - if ( ((root_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::SYSTEM) && !(fileAccess_ & Fl_Preferences::SYSTEM_READ_OK) ) { + if ( ((root_type_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::SYSTEM) && !(fileAccess_ & Fl_Preferences::SYSTEM_READ_OK) ) { prefs_->node->clearDirtyFlags(); return -1; } @@ -1027,11 +1237,11 @@ int Fl_Preferences::RootNode::read() { int Fl_Preferences::RootNode::write() { if (!filename_) // RUNTIME preferences, or filename could not be created return -1; - if ( (root_ & Fl_Preferences::CORE) && !(fileAccess_ & Fl_Preferences::CORE_WRITE_OK) ) + if ( (root_type_ & Fl_Preferences::CORE) && !(fileAccess_ & Fl_Preferences::CORE_WRITE_OK) ) return -1; - if ( ((root_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::USER) && !(fileAccess_ & Fl_Preferences::USER_WRITE_OK) ) + if ( ((root_type_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::USER) && !(fileAccess_ & Fl_Preferences::USER_WRITE_OK) ) return -1; - if ( ((root_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::SYSTEM) && !(fileAccess_ & Fl_Preferences::SYSTEM_WRITE_OK) ) + if ( ((root_type_&Fl_Preferences::ROOT_MASK)==Fl_Preferences::SYSTEM) && !(fileAccess_ & Fl_Preferences::SYSTEM_WRITE_OK) ) return -1; fl_make_path_for_file(filename_); FILE *f = fl_fopen( filename_, "wb" ); @@ -1111,10 +1321,10 @@ char Fl_Preferences::RootNode::getPath( char *path, int pathlen ) { } // create a node that represents a group -// - path must be a single word, prferable alnum(), dot and underscore only. Space is ok. +// - path must be a single word, preferable alnum(), dot and underscore only. Space is ok. Fl_Preferences::Node::Node( const char *path ) { if ( path ) path_ = fl_strdup( path ); else path_ = 0; - child_ = 0; next_ = 0; parent_ = 0; + first_child_ = 0; next_ = 0; parent_ = 0; entry_ = 0; nEntry_ = NEntry_ = 0; dirty_ = 0; @@ -1125,12 +1335,12 @@ Fl_Preferences::Node::Node( const char *path ) { } void Fl_Preferences::Node::deleteAllChildren() { - Node *nx; - for ( Node *nd = child_; nd; nd = nx ) { - nx = nd->next_; - delete nd; + Node *next_node = NULL; + for ( Node *current_node = first_child_; current_node; current_node = next_node ) { + next_node = current_node->next_; + delete current_node; } - child_ = 0L; + first_child_ = NULL; dirty_ = 1; updateIndex(); } @@ -1139,16 +1349,16 @@ void Fl_Preferences::Node::deleteAllEntries() { if ( entry_ ) { for ( int i = 0; i < nEntry_; i++ ) { if ( entry_[i].name ) { - free( entry_[i].name ); - entry_[i].name = 0L; + ::free( entry_[i].name ); + entry_[i].name = NULL; } if ( entry_[i].value ) { - free( entry_[i].value ); - entry_[i].value = 0L; + ::free( entry_[i].value ); + entry_[i].value = NULL; } } free( entry_ ); - entry_ = 0L; + entry_ = NULL; nEntry_ = 0; NEntry_ = 0; } @@ -1157,22 +1367,22 @@ void Fl_Preferences::Node::deleteAllEntries() { // delete this and all depending nodes Fl_Preferences::Node::~Node() { + next_ = NULL; + parent_ = NULL; deleteAllChildren(); deleteAllEntries(); deleteIndex(); if ( path_ ) { - free( path_ ); - path_ = 0L; + ::free( path_ ); + path_ = NULL; } - next_ = 0L; - parent_ = 0L; } // recursively check if any entry is dirty (was changed after loading a fresh prefs file) char Fl_Preferences::Node::dirty() { if ( dirty_ ) return 1; if ( next_ && next_->dirty() ) return 1; - if ( child_ && child_->dirty() ) return 1; + if ( first_child_ && first_child_->dirty() ) return 1; return 0; } @@ -1181,7 +1391,7 @@ void Fl_Preferences::Node::clearDirtyFlags() { Fl_Preferences::Node *nd = this; while (nd) { nd->dirty_ = 0; - if ( nd->child_ ) nd->child_->clearDirtyFlags(); + if ( nd->first_child_ ) nd->first_child_->clearDirtyFlags(); nd = nd->next_; } } @@ -1214,7 +1424,7 @@ int Fl_Preferences::Node::write( FILE *f ) { else fprintf( f, "%s\n", entry_[i].name ); } - if ( child_ ) child_->write( f ); + if ( first_child_ ) first_child_->write( f ); dirty_ = 0; return 0; } @@ -1222,8 +1432,8 @@ int Fl_Preferences::Node::write( FILE *f ) { // set the parent node and create the full path void Fl_Preferences::Node::setParent( Node *pn ) { parent_ = pn; - next_ = pn->child_; - pn->child_ = this; + next_ = pn->first_child_; + pn->first_child_ = this; sprintf( nameBuffer, "%s/%s", pn->path_, path_ ); free( path_ ); path_ = fl_strdup( nameBuffer ); @@ -1234,7 +1444,7 @@ Fl_Preferences::RootNode *Fl_Preferences::Node::findRoot() { Node *n = this; do { if (n->top_) - return n->root_; + return n->root_node_; n = n->parent(); } while (n); return 0L; @@ -1346,7 +1556,7 @@ Fl_Preferences::Node *Fl_Preferences::Node::find( const char *path ) { return this; if ( path[ len ] == '/' ) { Node *nd; - for ( nd = child_; nd; nd = nd->next_ ) { + for ( nd = first_child_; nd; nd = nd->next_ ) { Node *nn = nd->find( path ); if ( nn ) return nn; } @@ -1392,7 +1602,7 @@ Fl_Preferences::Node *Fl_Preferences::Node::search( const char *path, int offset if ( len > 0 && path[ len ] == 0 ) return this; if ( len <= 0 || path[ len ] == '/' ) { - for ( Node *nd = child_; nd; nd = nd->next_ ) { + for ( Node *nd = first_child_; nd; nd = nd->next_ ) { Node *nn = nd->search( path, offset ); if ( nn ) return nn; } @@ -1408,7 +1618,7 @@ int Fl_Preferences::Node::nChildren() { return nIndex_; } else { int cnt = 0; - for ( Node *nd = child_; nd; nd = nd->next_ ) + for ( Node *nd = first_child_; nd; nd = nd->next_ ) cnt++; return cnt; } @@ -1444,7 +1654,7 @@ Fl_Preferences::Node *Fl_Preferences::Node::childNode( int ix ) { int n = nChildren(); ix = n - ix -1; Node *nd; - for ( nd = child_; nd; nd = nd->next_ ) { + for ( nd = first_child_; nd; nd = nd->next_ ) { if ( !ix-- ) break; if ( !nd ) break; } @@ -1454,23 +1664,25 @@ Fl_Preferences::Node *Fl_Preferences::Node::childNode( int ix ) { // remove myself from the list and delete me (and all children) char Fl_Preferences::Node::remove() { - Node *nd = 0, *np; - if ( parent() ) { - nd = parent()->child_; np = 0L; + Node *nd = NULL, *np = NULL; + Node *parent_node = parent(); + if ( parent_node ) { + nd = parent_node->first_child_; np = NULL; for ( ; nd; np = nd, nd = nd->next_ ) { if ( nd == this ) { if ( np ) - np->next_ = nd->next_; + np->next_ = next_; else - parent()->child_ = nd->next_; + parent_node->first_child_ = next_; + next_ = NULL; break; } } - parent()->dirty_ = 1; - parent()->updateIndex(); + parent_node->dirty_ = 1; + parent_node->updateIndex(); } delete this; - return ( nd != 0 ); + return ( nd != NULL ); } void Fl_Preferences::Node::createIndex() { @@ -1482,7 +1694,7 @@ void Fl_Preferences::Node::createIndex() { } Node *nd; int i = 0; - for (nd = child_; nd; nd = nd->next_, i++) { + for (nd = first_child_; nd; nd = nd->next_, i++) { index_[n-i-1] = nd; } nIndex_ = n; @@ -1494,9 +1706,10 @@ void Fl_Preferences::Node::updateIndex() { } void Fl_Preferences::Node::deleteIndex() { - if (index_) free(index_); + if (index_) + ::free(index_); + index_ = NULL; NIndex_ = nIndex_ = 0; - index_ = 0; indexed_ = 0; } diff --git a/src/Fl_System_Driver.H b/src/Fl_System_Driver.H index 0ba196d6a..37b5486fa 100644 --- a/src/Fl_System_Driver.H +++ b/src/Fl_System_Driver.H @@ -128,6 +128,8 @@ public: virtual unsigned utf8from_mb(char* dst, unsigned dstlen, const char* src, unsigned srclen); // implement to shield fprintf() from locale changes in decimal point virtual int clocale_printf(FILE *output, const char *format, va_list args); + virtual int clocale_snprintf(char *output, size_t output_size, const char *format, va_list args); + virtual int clocale_sscanf(const char *input, const char *format, va_list args); // implement functions telling whether a key is pressed virtual int event_key(int) {return 0;} virtual int get_key(int) {return 0;} diff --git a/src/Fl_System_Driver.cxx b/src/Fl_System_Driver.cxx index 71a60c034..75bbf3ef9 100644 --- a/src/Fl_System_Driver.cxx +++ b/src/Fl_System_Driver.cxx @@ -404,6 +404,14 @@ int Fl_System_Driver::clocale_printf(FILE *output, const char *format, va_list a return vfprintf(output, format, args); } +int Fl_System_Driver::clocale_snprintf(char *output, size_t output_size, const char *format, va_list args) { + return vsnprintf(output, output_size, format, args); +} + +int Fl_System_Driver::clocale_sscanf(const char *input, const char *format, va_list args) { + return vsscanf(input, format, args); +} + int Fl_System_Driver::filename_expand(char *to,int tolen, const char *from) { char *temp = new char[tolen]; strlcpy(temp,from, tolen); diff --git a/src/Fl_cocoa.mm b/src/Fl_cocoa.mm index 0b8fc3417..52680a84f 100644 --- a/src/Fl_cocoa.mm +++ b/src/Fl_cocoa.mm @@ -4528,7 +4528,10 @@ int Fl_Darwin_System_Driver::calc_mac_os_version() { return fl_mac_os_version; } -char *Fl_Darwin_System_Driver::preference_rootnode(Fl_Preferences *prefs, Fl_Preferences::Root root, +/* + Note: `prefs` can be NULL! + */ +char *Fl_Darwin_System_Driver::preference_rootnode(Fl_Preferences * /*prefs*/, Fl_Preferences::Root root, const char *vendor, const char *application) { static char *filename = 0L; diff --git a/src/drivers/Android/Fl_Android_System_Driver.H b/src/drivers/Android/Fl_Android_System_Driver.H index 585535d43..865b62aa1 100644 --- a/src/drivers/Android/Fl_Android_System_Driver.H +++ b/src/drivers/Android/Fl_Android_System_Driver.H @@ -69,6 +69,8 @@ public: virtual unsigned utf8to_mb(const char *src, unsigned srclen, char *dst, unsigned dstlen); virtual unsigned utf8from_mb(char *dst, unsigned dstlen, const char *src, unsigned srclen); virtual int clocale_printf(FILE *output, const char *format, va_list args); + virtual int clocale_snprintf(char *output, size_t output_size, const char *format, va_list args); + virtual int clocale_sscanf(const char *input, const char *format, va_list args); // these 2 are in Fl_get_key_win32.cxx virtual int event_key(int k); virtual int get_key(int k); diff --git a/src/drivers/Android/Fl_Android_System_Driver.cxx b/src/drivers/Android/Fl_Android_System_Driver.cxx index f164a7c14..2e9439699 100644 --- a/src/drivers/Android/Fl_Android_System_Driver.cxx +++ b/src/drivers/Android/Fl_Android_System_Driver.cxx @@ -487,6 +487,14 @@ int Fl_WinAPI_System_Driver::clocale_printf(FILE *output, const char *format, va return retval; } +int Fl_WinAPI_System_Driver::clocale_snprintf(char *output, size_t output_size, const char *format, va_list args) { + //... write me +} + +int Fl_WinAPI_System_Driver::clocale_sscanf(const char *input, const char *format, va_list args) { + //... write me +} + int Fl_WinAPI_System_Driver::filename_list(const char *d, dirent ***list, int (*sort)(struct dirent **, struct dirent **), char *errmsg, int errmsg_sz ) { @@ -834,6 +842,9 @@ void Fl_WinAPI_System_Driver::newUUID(char *uuidBuffer) } } +/* + Note: `prefs` can be NULL! + */ char *Fl_WinAPI_System_Driver::preference_rootnode(Fl_Preferences *prefs, Fl_Preferences::Root root, const char *vendor, const char *application) { diff --git a/src/drivers/Darwin/Fl_Darwin_System_Driver.H b/src/drivers/Darwin/Fl_Darwin_System_Driver.H index 7d076a9ce..8a2e3b7f6 100644 --- a/src/drivers/Darwin/Fl_Darwin_System_Driver.H +++ b/src/drivers/Darwin/Fl_Darwin_System_Driver.H @@ -45,6 +45,8 @@ public: virtual int single_arg(const char *arg); virtual int arg_and_value(const char *name, const char *value); virtual int clocale_printf(FILE *output, const char *format, va_list args); + virtual int clocale_snprintf(char *output, size_t output_size, const char *format, va_list args); + virtual int clocale_sscanf(const char *input, const char *format, va_list args); static void *get_carbon_function(const char *name); static int calc_mac_os_version(); // computes the fl_mac_os_version global variable static unsigned short *compute_macKeyLookUp(); diff --git a/src/drivers/Darwin/Fl_Darwin_System_Driver.cxx b/src/drivers/Darwin/Fl_Darwin_System_Driver.cxx index 511b3b048..dad46dfc7 100644 --- a/src/drivers/Darwin/Fl_Darwin_System_Driver.cxx +++ b/src/drivers/Darwin/Fl_Darwin_System_Driver.cxx @@ -116,10 +116,15 @@ int Fl_Darwin_System_Driver::arg_and_value(const char *name, const char *value) return strcmp(name, "NSDocumentRevisionsDebugMode") == 0; } +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 +static locale_t postscript_locale = NULL; +#endif + int Fl_Darwin_System_Driver::clocale_printf(FILE *output, const char *format, va_list args) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 if (fl_mac_os_version >= 100400) { - static locale_t postscript_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0); + if (!postscript_locale) + postscript_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0); return vfprintf_l(output, postscript_locale, format, args); } #endif @@ -130,6 +135,37 @@ int Fl_Darwin_System_Driver::clocale_printf(FILE *output, const char *format, va return retval; } +int Fl_Darwin_System_Driver::clocale_snprintf(char *output, size_t output_size, const char *format, va_list args) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 + if (fl_mac_os_version >= 100400) { + if (!postscript_locale) + postscript_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0); + return vsnprintf_l(output, output_size, postscript_locale, format, args); + } +#endif + char *saved_locale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + int retval = vsnprintf(output, output_size, format, args); + setlocale(LC_NUMERIC, saved_locale); + return retval; +} + +int Fl_Darwin_System_Driver::clocale_sscanf(const char *input, const char *format, va_list args) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 + if (fl_mac_os_version >= 100400) { + if (!postscript_locale) + postscript_locale = newlocale(LC_NUMERIC_MASK, "C", (locale_t)0); + return vsscanf_l(input, postscript_locale, format, args); + } +#endif + char *saved_locale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + int retval = vsscanf(input, format, args); + setlocale(LC_NUMERIC, saved_locale); + return retval; +} + + /* Returns the address of a Carbon function after dynamically loading the Carbon library if needed. Supports old Mac OS X versions that may use a couple of Carbon calls: GetKeys used by OS X 10.3 or before (in Fl::get_key()) diff --git a/src/drivers/WinAPI/Fl_WinAPI_System_Driver.H b/src/drivers/WinAPI/Fl_WinAPI_System_Driver.H index a31d5248b..60dca9d17 100644 --- a/src/drivers/WinAPI/Fl_WinAPI_System_Driver.H +++ b/src/drivers/WinAPI/Fl_WinAPI_System_Driver.H @@ -67,6 +67,8 @@ public: virtual unsigned utf8to_mb(const char *src, unsigned srclen, char *dst, unsigned dstlen); virtual unsigned utf8from_mb(char *dst, unsigned dstlen, const char *src, unsigned srclen); virtual int clocale_printf(FILE *output, const char *format, va_list args); + virtual int clocale_snprintf(char *output, size_t output_size, const char *format, va_list args); + virtual int clocale_sscanf(const char *input, const char *format, va_list args); // these 2 are in Fl_get_key_win32.cxx virtual int event_key(int k); virtual int get_key(int k); diff --git a/src/drivers/WinAPI/Fl_WinAPI_System_Driver.cxx b/src/drivers/WinAPI/Fl_WinAPI_System_Driver.cxx index a869b8ca0..e88ac8e9b 100644 --- a/src/drivers/WinAPI/Fl_WinAPI_System_Driver.cxx +++ b/src/drivers/WinAPI/Fl_WinAPI_System_Driver.cxx @@ -455,9 +455,14 @@ unsigned Fl_WinAPI_System_Driver::utf8from_mb(char *dst, unsigned dstlen, const return ret; } +#if defined(_MSC_VER) && (_MSC_VER >= 1400 /*Visual Studio 2005*/) +static _locale_t c_locale = NULL; +#endif + int Fl_WinAPI_System_Driver::clocale_printf(FILE *output, const char *format, va_list args) { #if defined(_MSC_VER) && (_MSC_VER >= 1400 /*Visual Studio 2005*/) - static _locale_t c_locale = _create_locale(LC_NUMERIC, "C"); + if (!c_locale) + c_locale = _create_locale(LC_NUMERIC, "C"); int retval = _vfprintf_l(output, format, c_locale, args); #else char *saved_locale = setlocale(LC_NUMERIC, NULL); @@ -468,6 +473,35 @@ int Fl_WinAPI_System_Driver::clocale_printf(FILE *output, const char *format, va return retval; } +int Fl_WinAPI_System_Driver::clocale_snprintf(char *output, size_t output_size, const char *format, va_list args) { +#if defined(_MSC_VER) && (_MSC_VER >= 1400 /*Visual Studio 2005*/) + if (!c_locale) + c_locale = _create_locale(LC_NUMERIC, "C"); + int retval = _vsnprintf_l(output, output_size, format, c_locale, args); +#else + char *saved_locale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + int retval = vsnprintf(output, output_size, format, args); + setlocale(LC_NUMERIC, saved_locale); +#endif + return retval; +} + +int Fl_WinAPI_System_Driver::clocale_sscanf(const char *input, const char *format, va_list args) { +#if defined(_MSC_VER) && (_MSC_VER >= 1400 /*Visual Studio 2005*/) + if (!c_locale) + c_locale = _create_locale(LC_NUMERIC, "C"); + int retval = _vsscanf_l(input, format, c_locale, args); +#else + char *saved_locale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + int retval = vsscanf(input, format, args); + setlocale(LC_NUMERIC, saved_locale); +#endif + return retval; +} + + int Fl_WinAPI_System_Driver::filename_list(const char *d, dirent ***list, int (*sort)(struct dirent **, struct dirent **), char *errmsg, int errmsg_sz) { @@ -814,7 +848,10 @@ void Fl_WinAPI_System_Driver::newUUID(char *uuidBuffer) } } -char *Fl_WinAPI_System_Driver::preference_rootnode(Fl_Preferences *prefs, Fl_Preferences::Root root, const char *vendor, +/* + Note: `prefs` can be NULL! + */ +char *Fl_WinAPI_System_Driver::preference_rootnode(Fl_Preferences * /*prefs*/, Fl_Preferences::Root root, const char *vendor, const char *application) { # define FLPREFS_RESOURCE "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders" diff --git a/src/drivers/X11/Fl_X11_System_Driver.H b/src/drivers/X11/Fl_X11_System_Driver.H index 146a48e83..891c14d16 100644 --- a/src/drivers/X11/Fl_X11_System_Driver.H +++ b/src/drivers/X11/Fl_X11_System_Driver.H @@ -31,6 +31,8 @@ public: virtual void display_arg(const char *arg); virtual int XParseGeometry(const char*, int*, int*, unsigned int*, unsigned int*); virtual int clocale_printf(FILE *output, const char *format, va_list args); + virtual int clocale_snprintf(char *output, size_t output_size, const char *format, va_list args); + virtual int clocale_sscanf(const char *input, const char *format, va_list args); // these 2 are in Fl_get_key.cxx virtual int event_key(int k); virtual int get_key(int k); diff --git a/src/drivers/X11/Fl_X11_System_Driver.cxx b/src/drivers/X11/Fl_X11_System_Driver.cxx index fcd1d2d5e..2f3656a47 100644 --- a/src/drivers/X11/Fl_X11_System_Driver.cxx +++ b/src/drivers/X11/Fl_X11_System_Driver.cxx @@ -79,10 +79,15 @@ Fl_System_Driver *Fl_System_Driver::newSystemDriver() return new Fl_X11_System_Driver(); } +#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 +static locale_t c_locale = NULL; +#endif + int Fl_X11_System_Driver::clocale_printf(FILE *output, const char *format, va_list args) { #if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 - static locale_t c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE)); + if (!c_locale) + c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE)); locale_t previous_locale = uselocale(c_locale); int retval = vfprintf(output, format, args); uselocale(previous_locale); @@ -95,6 +100,35 @@ int Fl_X11_System_Driver::clocale_printf(FILE *output, const char *format, va_li return retval; } +int Fl_X11_System_Driver::clocale_snprintf(char *output, size_t output_size, const char *format, va_list args) { +#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 + if (!c_locale) + c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE)); + locale_t previous_locale = uselocale(c_locale); + int retval = vsnprintf(output, output_size, format, args); + uselocale(previous_locale); +#else + char *saved_locale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + int retval = vsnprintf(output, output_size, format, args); + setlocale(LC_NUMERIC, saved_locale); +#endif +} + +int Fl_X11_System_Driver::clocale_sscanf(const char *input, const char *format, va_list args) { +#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 + if (!c_locale) + c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE)); + locale_t previous_locale = uselocale(c_locale); + int retval = vsscanf(input, format, args); + uselocale(previous_locale); +#else + char *saved_locale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + int retval = vsscanf(input, format, args); + setlocale(LC_NUMERIC, saved_locale); +#endif +} // Find a program in the path... static char *path_find(const char *program, char *filename, int filesize) { @@ -415,7 +449,10 @@ void Fl_X11_System_Driver::newUUID(char *uuidBuffer) b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); } -char *Fl_X11_System_Driver::preference_rootnode(Fl_Preferences *prefs, Fl_Preferences::Root root, const char *vendor, +/* + Note: `prefs` can be NULL! + */ +char *Fl_X11_System_Driver::preference_rootnode(Fl_Preferences * /*prefs*/, Fl_Preferences::Root root, const char *vendor, const char *application) { static char *filename = 0L; diff --git a/test/blocks.cxx b/test/blocks.cxx index 2128d8a08..a08e6ed40 100644 --- a/test/blocks.cxx +++ b/test/blocks.cxx @@ -497,7 +497,7 @@ class BlockWindow : public Fl_Double_Window { }; -Fl_Preferences BlockWindow::prefs_(Fl_Preferences::USER, "fltk.org", "blocks"); +Fl_Preferences BlockWindow::prefs_(Fl_Preferences::USER_L, "fltk.org", "blocks"); int main(int argc, char *argv[]) { diff --git a/test/preferences.fl b/test/preferences.fl index e7d5b1756..ef2cc5d1a 100644 --- a/test/preferences.fl +++ b/test/preferences.fl @@ -45,7 +45,7 @@ Function {saveAndCloseWindowCB( Fl_Widget*, void* )} {open private return_type v Fl::delete_widget(myWindow);} {} } -Function {} {open return_type int +Function {} {open selected return_type int } { Fl_Window myWindow { label {My Preferences} @@ -78,7 +78,7 @@ Function {} {open return_type int xywh {0 0 100 20} } MenuItem {} { - label {p.m.} selected + label {p.m.} xywh {0 0 100 20} } } @@ -206,10 +206,32 @@ int intValue; char buffer[80]; double doubleValue; -Fl_Preferences app( Fl_Preferences::USER, project, application ); +char path[ FL_PATH_MAX ]; +Fl_Preferences::Root root = + Fl_Preferences::filename(path, FL_PATH_MAX, Fl_Preferences::USER_L, project, application); +if (root == Fl_Preferences::UNKNOWN_ROOT_TYPE) { + printf("Location of future Preferences file not found.\\n"); +} else { + printf("Preferences file will be located at:\\n%s\\n", path); +} + +Fl_Preferences app( Fl_Preferences::USER_L, project, application ); - char path[ FL_PATH_MAX ]; - app.getUserdataPath( path, sizeof(path) ); + root = app.filename(path, FL_PATH_MAX); + if (root == Fl_Preferences::UNKNOWN_ROOT_TYPE) { + printf("Location of app Preferences file not found.\\n"); + } else if (root == Fl_Preferences::MEMORY) { + printf("App Preferences are memory mapped.\\n"); + } else { + printf("App Preferences file is actually located at:\\n%s\\n", path); + } + + app.getUserdataPath( path, sizeof(path) ); + if (path[0]) { + printf("Preferences user data directory is located at:\\n%s\\n", path); + } else { + printf("Location of Preferences user data directory not found.\\n"); + } Fl_Preferences bed( app, "Bed" ); bed.get( "alarm", buffer, "8:00", 79 ); @@ -278,7 +300,7 @@ Fl_Preferences app( Fl_Preferences::USER, project, application ); Function {writePrefs()} {open return_type void } { - code {Fl_Preferences app( Fl_Preferences::USER, project, application ); + code {Fl_Preferences app( Fl_Preferences::USER_L, project, application ); Fl_Preferences bed( app, "Bed" ); diff --git a/test/sudoku.cxx b/test/sudoku.cxx index 28437fbd8..9884c117d 100644 --- a/test/sudoku.cxx +++ b/test/sudoku.cxx @@ -625,7 +625,7 @@ SudokuCell::handle(int event) { // Sudoku class globals... Fl_Help_Dialog *Sudoku::help_dialog_ = (Fl_Help_Dialog *)0; -Fl_Preferences Sudoku::prefs_(Fl_Preferences::USER, "fltk.org", "sudoku"); +Fl_Preferences Sudoku::prefs_(Fl_Preferences::USER_L, "fltk.org", "sudoku"); // Create a Sudoku game window...