gplugin/gplugin

Implement widgets for settings that use basic types

16 months ago, Elliott Sales de Andrade
7e519d51af72
Implement widgets for settings that use basic types

This implements enums, integral types, double, and string. I have not yet implemented flags, and I'm not sure if we need those. The numeric types use spin buttons, though I suppose they could be sliders. The rates/digits/increments are somewhat arbitrary and could probably be adjusted, but seem good enough for now.

I _think_ this should be sufficient for all libpurple plugins. The GTK plugins may or may not be possible. For example, `spellchk` has a full table of replacements. This may work as a `dict[str, str]` in a table widget, but this is so far not implemented here. We should probably think about whether that makes sense to handle `dict` semi-generically or whether to expose some kind of GTK extension point as in the old API.

Also note that there are no `GSettings` types for things like directories or files, so those don't get any special treatment other than a string input. If we do want special handling, we may have to come up with some setting conventions (e.g., a setting name ending in `-dir` gets a directory chooser widget instead of an entry automatically, or similar).

Testing Done:
Ran `gplugin-gtk4-viewer -p gplugin/native-settings-plugin` and checked that all the settings had widgets with expected limits, and that setting them then going to another plugin and back preserved the settings. (Note that it does warn once for the unimplemented `flags` type.)

Also checked that plugins set up in /r/2131 had some settings.

Reviewed at https://reviews.imfreedom.org/r/2150/
/*
* Copyright (C) 2011-2020 Gary Kramlich <grim@reaperworld.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, see <https://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <glib/gi18n-lib.h>
#include <gplugin/gplugin-core.h>
#include <gplugin/gplugin-version.h>
/******************************************************************************
* Globals
*****************************************************************************/
GRegex *regex = NULL;
static const gchar *version_pattern = "^(?P<major>\\d+)(\\."
"(?P<minor>\\d+)"
"(\\.(?P<micro>\\d+)(?P<extra>.*))?)?$";
/******************************************************************************
* Helpers
*****************************************************************************/
static void
gplugin_version_lazy_init(void)
{
static gsize init = 0;
if(g_once_init_enter(&init)) {
GError *error = NULL;
regex =
g_regex_new(version_pattern, G_REGEX_NO_AUTO_CAPTURE, 0, &error);
if(error) {
g_warning(
_("Failed to initialize the version regex: %s"),
(error->message) ? error->message : _("unknown"));
g_error_free(error);
}
g_once_init_leave(&init, 1);
}
}
/*< private >
* gplugin_version_parser:
* @version: The string version to parse.
* @major: (out) (nullable): A return gint pointer for the major version.
* @minor: (out) (nullable): A return gint pointer for the minor version.
* @micro: (out) (nullable): A return gint pointer for the micro version.
* @extra: (out) (nullable): A return gchar * pointer for any extra version
* info.
*
* Attempts to parse a version string into its @major, @minor, @micro, and
* @extra parts. If @version doesn't match a semantic version string, @major
* will be set to %-1, @minor will be set to %0, and @micro will be set to %0.
*/
static void
gplugin_version_parser(
const gchar *version,
gint *major,
gint *minor,
gint *micro,
gchar **extra)
{
GMatchInfo *info = NULL;
gboolean matches = FALSE;
gchar *temp = NULL;
/* initialize everything to our failed state */
if(major != NULL) {
*major = -1;
}
if(minor != NULL) {
*minor = 0;
}
if(micro != NULL) {
*micro = 0;
}
if(version == NULL) {
/* if a version was not provided, return our failed values */
return;
}
matches = g_regex_match(regex, version, 0, &info);
if(!matches) {
/* If we failed to match the regex, free info and return our failed
* values.
*/
g_match_info_unref(info);
return;
}
/* grab the major version */
if(major) {
temp = g_match_info_fetch_named(info, "major");
*major = (temp) ? atoi(temp) : 0;
g_free(temp);
}
/* grab the minor version */
if(minor) {
temp = g_match_info_fetch_named(info, "minor");
*minor = (temp) ? atoi(temp) : 0;
g_free(temp);
}
/* grab the micro version */
if(micro) {
temp = g_match_info_fetch_named(info, "micro");
*micro = (temp) ? atoi(temp) : 0;
g_free(temp);
}
/* grab the extra version */
if(extra) {
*extra = g_match_info_fetch_named(info, "extra");
}
g_match_info_unref(info);
}
/******************************************************************************
* GPluginVersion API
*****************************************************************************/
/**
* GPLUGIN_MAJOR_VERSION:
*
* This is the major version number of GPlugin that was compiled against.
*/
/**
* GPLUGIN_MINOR_VERSION:
*
* This is the minor version number of GPlugin that was compiled against.
*/
/**
* GPLUGIN_MICRO_VERSION:
*
* This is the micro version number of GPlugin that was compiled against.
*/
/**
* GPLUGIN_EXTRA_VERSION:
*
* This is the extra version string of GPlugin that was compiled against.
*/
/**
* GPLUGIN_VERSION:
*
* This is the string version number of GPlugin that was compiled against.
*/
/**
* GPLUGIN_VERSION_CHECK:
* @major: The major version to compare for.
* @minor: The minor version to compare for.
* @micro: The micro version to compare for.
*
* Checks the version of the GPlugin library that is being compiled
* against.
*
* Returns: %TRUE if the version of the GPlugin header files
* is the same as or newer than the passed-in version.
*/
/**
* gplugin_version_check:
* @major: The required major version.
* @minor: The required minor version.
* @micro: The required micro version.
*
* Checks that the GPlugin library in use is compatible with the given version.
*
* Generally you would pass in the constants [const@GPlugin.MAJOR_VERSION],
* [const@GPlugin.MINOR_VERSION], [const@GPlugin.MICRO_VERSION] as the three
* arguments to this function; that produces a check that the library in use is
* compatible with the version of GPlugin the application or module was
* compiled against.
*
* Compatibility is defined by two things: first the version of the running
* library is newer than the version @major.@minor.@micro. Second the running
* library must be binary compatible with the version @major.@minor.@micro
* (same major version).
*
* Returns: %NULL if the GPlugin library is compatible with the given version,
* or a string describing the version mismatch. The returned string
* is owned by GPlugin and must not be modified or freed.
*/
const gchar *
gplugin_version_check(guint major, guint minor, guint micro)
{
if(major > GPLUGIN_MAJOR_VERSION) {
return "gplugin version too old (major mismatch)";
}
/* This will warn until 1.0.0 is released. */
if(major < GPLUGIN_MAJOR_VERSION) {
return "gplugin version too new (major mismatch)";
}
if(minor > GPLUGIN_MINOR_VERSION) {
return "gplugin version too old (minor mismatch)";
}
if(minor == GPLUGIN_MINOR_VERSION && micro > GPLUGIN_MICRO_VERSION) {
return "gplugin version too old (micro mismatch)";
}
return NULL;
}
/**
* gplugin_version_compare:
* @v1: The first version to compare.
* @v2: The second version to compare.
*
* A semantic version checker which ignores any characters after the micro
* version.
*
* Returns: less than 0 if @v1 is less than @v2, 0 if @v1 is equal to @v1, and
* greater than 0 if @v1 is greater than @v2.
*/
gint
gplugin_version_compare(const gchar *v1, const gchar *v2)
{
gint v1_maj = 0, v1_min = 0, v1_mic = 0;
gint v2_maj = 0, v2_min = 0, v2_mic = 0;
gint t = 0;
if(regex == NULL) {
gplugin_version_lazy_init();
}
/* parse v1 */
gplugin_version_parser(v1, &v1_maj, &v1_min, &v1_mic, NULL);
/* parse v2 */
gplugin_version_parser(v2, &v2_maj, &v2_min, &v2_mic, NULL);
/* now figure out if they match */
t = v1_maj - v2_maj;
if(t != 0)
return t;
t = v1_min - v2_min;
if(t != 0)
return t;
return v1_mic - v2_mic;
}