doc: update copyright line for 2021
[adg.git] / src / adg / adg-utils.c
blob87d6fc0dcc2a26fb13bff2182b45b38fbe295ad9
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2021 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 /**
22 * SECTION:adg-utils
23 * @Section_Id:utilities
24 * @title: Utilities
25 * @short_description: Assorted macros and functions
27 * Collection of macros and functions that do not fit inside any other topic.
29 * Since: 1.0
30 **/
32 /**
33 * ADG_DIR_RIGHT:
35 * Symbolic constant for the right direction (in radians).
37 * Since: 1.0
38 **/
40 /**
41 * ADG_DIR_DOWN:
43 * Symbolic constant for the down direction (in radians).
45 * Since: 1.0
46 **/
48 /**
49 * ADG_DIR_LEFT:
51 * Symbolic constant for the left direction (in radians).
53 * Since: 1.0
54 **/
56 /**
57 * ADG_DIR_UP:
59 * Symbolic constant for the up direction (in radians).
61 * Since: 1.0
62 **/
64 /**
65 * ADG_UTF8_DIAMETER:
67 * String constant that embeds a UTF-8 encoded diameter (U+2300).
68 * It can be used to prefix diameter quotes, such as:
70 * <informalexample><programlisting language="C">
71 * adg_dim_set_value(dim, ADG_UTF8_DIAMETER "<>");
72 * </programlisting></informalexample>
74 * Since: 1.0
75 **/
77 /**
78 * ADG_UTF8_DEGREE:
80 * String constant that embeds a UTF-8 encoded degree symbol (U+00B0).
81 * It is used to suffix by the default implementation of #AdgADim to
82 * suffix the set value, but can be also used manually:
84 * <informalexample><programlisting language="C">
85 * adg_dim_set_value(dim, "<>" ADG_UTF8_DEGREE);
86 * </programlisting></informalexample>
88 * Since: 1.0
89 **/
92 #include "adg-internal.h"
93 #include <string.h>
94 #include <limits.h>
95 #include <libintl.h>
96 #include <locale.h>
97 #include <math.h>
100 #if GLIB_CHECK_VERSION(2, 54, 0)
101 #else
104 * g_object_new_with_properties: (skip)
105 * @object_type: the object type to instantiate
106 * @n_properties: the number of properties
107 * @names: (array length=n_properties): the names of each property to be set
108 * @values: (array length=n_properties): the values of each property to be set
110 * Creates a new instance of a #GObject subtype and sets its properties using
111 * the provided arrays. Both arrays must have exactly @n_properties elements,
112 * and the names and values correspond by index.
114 * Construction parameters (see %G_PARAM_CONSTRUCT, %G_PARAM_CONSTRUCT_ONLY)
115 * which are not explicitly specified are set to their default values.
117 * Returns: (type GObject.Object) (transfer full): a new instance of
118 * @object_type
120 * Since: 1.0
122 GObject *
123 g_object_new_with_properties(GType object_type, guint n_properties,
124 const char *names[], const GValue values[])
126 GParameter *params = g_newa(GParameter, n_properties);
127 guint n;
129 for (n = 0; n < n_properties; ++n) {
130 params[n].name = names[n];
131 memcpy(&params[n].value, &values[n], sizeof(GValue));
134 return g_object_newv(object_type, n_properties, params);
137 #endif
140 * adg_is_string_empty:
141 * @str: the subject string
143 * Checks if @str is an empty string, that is if is
144 * <constant>NULL</constant> or if its first character
145 * is <constant>'\0'</constant>.
147 * Returns: <constant>TRUE</constant> if @str is an empty string, <constant>FALSE</constant> otherwise.
149 * Since: 1.0
151 gboolean
152 adg_is_string_empty(const gchar *str)
154 return str == NULL || str[0] == '\0';
158 * adg_is_enum_value:
159 * @value: the enum value to check
160 * @enum_type: a #GEnum based type
162 * Checks if @value is a valid @enum_type value.
164 * Returns: <constant>TRUE</constant> if @value is a valid @enum_type, <constant>FALSE</constant> otherwise.
166 * Since: 1.0
168 gboolean
169 adg_is_enum_value(int value, GType enum_type)
171 GEnumClass *enum_class;
172 gboolean found;
174 enum_class = g_type_class_ref(enum_type);
175 g_return_val_if_fail(enum_class != NULL, FALSE);
177 found = FALSE;
179 if (value >= enum_class->minimum && value <= enum_class->maximum) {
180 GEnumValue *enum_value;
181 guint n;
183 for (n = 0; !found && n < enum_class->n_values; ++n) {
184 enum_value = enum_class->values + n;
185 found = value == enum_value->value;
189 g_type_class_unref(enum_class);
191 return found;
195 * adg_is_boolean_value:
196 * @value: the gboolean value to check
198 * Checks if @value is a valid #gboolean value, that is if it is
199 * <constant>TRUE</constant> or <constant>FALSE</constant>.
200 * No other values are accepted.
202 * Returns: <constant>TRUE</constant> if @value is a valid #gboolean, <constant>FALSE</constant> otherwise.
204 * Since: 1.0
206 gboolean
207 adg_is_boolean_value(gboolean value)
209 return value == TRUE || value == FALSE;
213 * adg_string_replace:
214 * @str: the original string
215 * @from: the substring to replace
216 * @to: the replacement string
218 * Replaces @from with @to inside @str and returns the result as a
219 * newly allocated string.
221 * @str and @from must be non-null valid C strings while @to can be
222 * <constant>NULL</constant>, in which case an empty string
223 * (<constant>""</constant>) will be implied.
225 * Returns: a newly allocated string to be freed with g_free() or <constant>NULL</constant> on errors.
227 * Since: 1.0
229 gchar *
230 adg_string_replace(const gchar *str, const gchar *from, const gchar *to)
232 gchar *result;
233 int from_len;
234 gchar *ptr, *old_result;
236 g_return_val_if_fail(str != NULL, NULL);
237 g_return_val_if_fail(from != NULL, NULL);
239 from_len = strlen(from);
241 g_return_val_if_fail(from_len > 0, NULL);
243 if (to == NULL)
244 to = "";
246 result = g_strdup(str);
248 while ((ptr = strstr(result, from)) != NULL) {
249 *ptr = '\0';
250 old_result = result;
251 result = g_strconcat(old_result, to, ptr + from_len, NULL);
252 g_free(old_result);
255 return result;
259 * _adg_dgettext:
260 * @domain: the translation domain to use, or
261 * <constant>NULL</constant> to use the domain set
262 * with <function>textdomain</function>
263 * @msgid: message to translate
265 * A variant of dgettext() (or of g_dgettext(), if available) that
266 * initialize the ADG localization infrastructure.
268 * Returns: The translated string
270 * Since: 1.0
272 const gchar *
273 _adg_dgettext(const gchar *domain, const gchar *msgid)
275 static gboolean initialized = FALSE;
277 if (G_UNLIKELY(!initialized)) {
278 #ifdef G_OS_UNIX
279 bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
280 #else
281 /* On windows, LOCALEDIR is relative to the installation path */
282 gchar *path = g_build_filename(g_win32_get_package_installation_directory_of_module(NULL),
283 LOCALEDIR, NULL);
284 bindtextdomain(GETTEXT_PACKAGE, path);
285 g_free(path);
286 #endif
287 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
288 initialized = TRUE;
291 return g_dgettext(domain, msgid);
295 * _adg_dpgettext:
296 * @domain: the translation domain to use, or
297 * <constant>NULL</constant> to use the domain set with
298 * <function>textdomain</function>
299 * @msgctxtid: a combined message context and message id, separated
300 * by a \004 character
301 * @msgidoffset: the offset of the message id in @msgctxid
303 * This function is basically a duplicate of g_dpgettext() but using
304 * _adg_dgettext() internally instead of g_dgettext().
306 * Returns: The translated string
308 * Since: 1.0
310 const gchar *
311 _adg_dpgettext(const gchar *domain, const gchar *msgctxtid, gsize msgidoffset)
313 const gchar *translation;
314 gchar *sep;
316 translation = _adg_dgettext(domain, msgctxtid);
318 if (translation == msgctxtid) {
319 if (msgidoffset > 0)
320 return msgctxtid + msgidoffset;
322 sep = strchr(msgctxtid, '|');
324 if (sep) {
325 /* try with '\004' instead of '|', in case
326 * xgettext -kQ_:1g was used
328 gchar *tmp = g_alloca(strlen(msgctxtid) + 1);
329 strcpy(tmp, msgctxtid);
330 tmp[sep - msgctxtid] = '\004';
332 translation = _adg_dgettext(domain, tmp);
334 if (translation == tmp)
335 return sep + 1;
339 return translation;
343 * adg_find_file:
344 * @file: the file to search
345 * @...: a NULL terminated list of paths where to look for
346 * file existence.
348 * Searches @file in the provided paths and returns the full
349 * path to the first existing match. The check is performed
350 * using g_file_test() with the G_FILE_TEST_EXISTS test.
352 * The result should be freed with g_free() when no longer needed.
354 * Returns: a newly allocated string containing the path or <constant>NULL</constant> if not found or on errors.
356 * Since: 1.0
358 gchar *
359 adg_find_file(const gchar *file, ...)
361 va_list var_args;
362 gchar *path;
363 const gchar *base;
365 g_return_val_if_fail(file != NULL, NULL);
367 va_start(var_args, file);
369 while ((base = va_arg(var_args, const gchar *)) != NULL) {
370 path = g_build_filename(base, file, NULL);
371 if (g_file_test(path, G_FILE_TEST_EXISTS))
372 return path;
373 g_free(path);
376 return NULL;
380 * adg_scale_factor:
381 * @scale: a string identifying the scale
383 * Converts a scale in the form x:y (where x and y are respectively
384 * two positive integers representing the numerator and denominator
385 * of a fraction) into its approximate double representation. Any
386 * garbage following x or y will be silently ignored, meaning that
387 * x+garbage:y+garbage is equivalent to x:y. Furthermore, the postfix
388 * :y can be omitted, in which case (double) x will be returned.
390 * x and y are converted by using atof(), so refer to your C library
391 * documentation for details on the algorithm used.
393 * Returns: the (possibly approximated) double conversion of @scale or 0 on errors.
395 * Since: 1.0
397 gdouble
398 adg_scale_factor(const gchar *scale)
400 gdouble numerator, denominator;
401 const gchar *ptr;
402 gchar *orig;
404 g_return_val_if_fail(scale != NULL, 0);
406 orig = setlocale(LC_NUMERIC, NULL);
407 setlocale(LC_NUMERIC, "C");
409 numerator = atof(scale);
411 ptr = strchr(scale, ':');
412 denominator = ptr == NULL ? 1 : atof(ptr + 1);
414 setlocale(LC_NUMERIC, orig);
416 if (denominator == 0)
417 return 0;
419 return numerator / denominator;
423 * adg_type_from_filename:
424 * @file: the full path to the file
426 * Gets the surface type from @file. The algorithm simply looks to the
427 * file name extension and tries to guess the correct surface type. If the
428 * guess fails, e.g. the extension does not exist or it is not usual, the
429 * function returns <constant>CAIRO_SURFACE_TYPE_XLIB</constant>. This is
430 * the value conventionally used to signal unrecognized file names.
432 * Returns: (type gint): the surface type of @file
433 * or <constant>CAIRO_SURFACE_TYPE_XLIB</constant>.
435 * Since: 1.0
437 cairo_surface_type_t
438 adg_type_from_filename(const gchar *file)
440 const gchar *p_suffix;
441 gchar *suffix;
442 cairo_surface_type_t type;
444 g_return_val_if_fail(file != NULL, CAIRO_SURFACE_TYPE_XLIB);
446 p_suffix = strrchr(file, '.');
447 if (p_suffix == NULL)
448 return CAIRO_SURFACE_TYPE_XLIB;
450 /* Put in suffix the lowercase extension without the leading dot */
451 suffix = g_ascii_strdown(p_suffix + 1, -1);
453 if (strcmp(suffix, "png") == 0) {
454 type = CAIRO_SURFACE_TYPE_IMAGE;
455 } else if (strcmp(suffix, "svg") == 0) {
456 type = CAIRO_SURFACE_TYPE_SVG;
457 } else if (strcmp(suffix, "pdf") == 0) {
458 type = CAIRO_SURFACE_TYPE_PDF;
459 } else if (strcmp(suffix, "ps") == 0) {
460 type = CAIRO_SURFACE_TYPE_PS;
461 } else {
462 type = CAIRO_SURFACE_TYPE_XLIB;
465 g_free(suffix);
466 return type;
470 * adg_object_clone:
471 * @src: (transfer none): the source #GObject to clone
473 * A helper method that clones a generic #GObject instance. The implementation
474 * leverages the g_object_get_property() method on @src to get all the
475 * properties and uses g_object_newv() to create the destination clone.
477 * The code is not as sophisticated as one might expect, so apart from what
478 * described there is no other magic involved. It is internally used by ADG to
479 * clone #AdgStyle instances in adg_style_clone().
481 * Returns: (transfer full): the clone of @src.
483 * Since: 1.0
485 GObject *
486 adg_object_clone(GObject *src)
488 GObject *dst;
489 GParamSpec **specs;
490 const char **names;
491 GValue *values;
492 const gchar *name;
493 GValue *value;
494 guint n, n_specs, n_properties;
496 g_return_val_if_fail(G_IS_OBJECT(src), NULL);
497 specs = g_object_class_list_properties(G_OBJECT_GET_CLASS(src), &n_specs);
498 names = g_new0(const char *, n_specs);
499 values = g_new0(GValue, n_specs);
500 n_properties = 0;
502 for (n = 0; n < n_specs; ++n) {
503 if ((specs[n]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE) {
504 name = g_intern_string(specs[n]->name);
505 names[n_properties] = name;
506 value = &values[n_properties];
507 g_value_init(value, specs[n]->value_type);
508 g_object_get_property(src, name, value);
509 ++ n_properties;
513 dst = g_object_new_with_properties(G_TYPE_FROM_INSTANCE(src),
514 n_properties, names, values);
515 g_free(specs);
516 g_free(names);
517 g_free(values);
519 return dst;
523 * adg_nop:
525 * A function that does nothing. It can be used as
526 * <constant>/dev/null</constant> when callback are required, e.g. with
527 * g_log_set_default_handler().
529 * Since: 1.0
531 void
532 adg_nop(void)
537 * adg_round:
538 * @value: the value to round
539 * @decimals: the number of significant decimals to consider
541 * Rounds the @value floating number to a specific number of digits. Be aware
542 * a binary floating point is unable to represent all decimal numbers, i.e.
543 * (WARNING: pure theoretical example ahead) rounding 3.3333 to the second
544 * decimal can return in 3.32999999.
546 * Returns: (type gdouble): the rounded number.
548 * Since: 1.0
550 gdouble
551 adg_round(gdouble value, gint decimals)
553 return decimals > 0 ? adg_round(value*10, decimals-1) / 10 : round(value);
557 * adg_unescaped_strchr:
558 * @string: a string
559 * @ch: a character to find
561 * Similar to the standard strchr(), this function returns a pointer to the to
562 * the matched character that *is not* preceded by a backslash.
564 * Returns: (transfer none): a pointer to the matched @ch inside @string or
565 * %NULL if not found.
567 * Since: 1.0
569 const gchar *
570 adg_unescaped_strchr(const gchar *string, gint ch)
572 const gchar *ptr;
574 g_return_val_if_fail(string != NULL, NULL);
576 for (ptr = string; (ptr = strchr(ptr, ch)) != NULL; ++ ptr) {
577 if (ptr == string || *(ptr-1) != '\\') {
578 break;
582 return ptr;