Fix untranslated text
[geeqie.git] / src / misc.cc
1 /*
2  * Copyright (C) 2008 - 2016 The Geeqie Team
3  *
4  * Authors: Vladimir Nadvornik, Laurent Monin
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20
21 #include "misc.h"
22
23 #include <sys/stat.h>
24 #include <unistd.h>
25
26 #include <cerrno>
27 #include <cstdint>
28 #include <cstdio>
29 #include <cstdlib>
30 #include <cstring>
31 #include <memory>
32
33 #include <langinfo.h>
34 #include <pwd.h>
35
36 #include <config.h>
37
38 #include "debug.h"
39 #include "filedata.h"
40 #include "intl.h"
41 #include "main-defines.h"
42 #include "main.h"
43 #include "options.h"
44 #include "ui-fileops.h"
45
46 gdouble get_zoom_increment()
47 {
48         return ((options->image.zoom_increment != 0) ? static_cast<gdouble>(options->image.zoom_increment) / 100.0 : 1.0);
49 }
50
51 gchar *utf8_validate_or_convert(const gchar *text)
52 {
53         gint len;
54
55         if (!text) return nullptr;
56
57         len = strlen(text);
58         if (!g_utf8_validate(text, len, nullptr))
59                 return g_convert(text, len, "UTF-8", "ISO-8859-1", nullptr, nullptr, nullptr);
60
61         return g_strdup(text);
62 }
63
64 gint utf8_compare(const gchar *s1, const gchar *s2, gboolean case_sensitive)
65 {
66         gchar *s1_key;
67         gchar *s2_key;
68         gchar *s1_t;
69         gchar *s2_t;
70         gint ret;
71
72         g_assert(g_utf8_validate(s1, -1, nullptr));
73         g_assert(g_utf8_validate(s2, -1, nullptr));
74
75         if (!case_sensitive)
76                 {
77                 s1_t = g_utf8_casefold(s1, -1);
78                 s2_t = g_utf8_casefold(s2, -1);
79                 }
80         else
81                 {
82                 s1_t = const_cast<gchar *>(s1);
83                 s2_t = const_cast<gchar *>(s2);
84                 }
85
86         s1_key = g_utf8_collate_key(s1_t, -1);
87         s2_key = g_utf8_collate_key(s2_t, -1);
88
89         ret = strcmp(s1_key, s2_key);
90
91         g_free(s1_key);
92         g_free(s2_key);
93
94         if (!case_sensitive)
95                 {
96                 g_free(s1_t);
97                 g_free(s2_t);
98                 }
99
100         return ret;
101 }
102
103 /* Borrowed from gtkfilesystemunix.c */
104 gchar *expand_tilde(const gchar *filename)
105 {
106 #ifndef G_OS_UNIX
107         return g_strdup(filename);
108 #else
109         const gchar *notilde;
110         const gchar *slash;
111         const gchar *home;
112
113         if (filename[0] != '~')
114                 return g_strdup(filename);
115
116         notilde = filename + 1;
117         slash = strchr(notilde, G_DIR_SEPARATOR);
118         if (slash == notilde || !*notilde)
119                 {
120                 home = g_get_home_dir();
121                 if (!home)
122                         return g_strdup(filename);
123                 }
124         else
125                 {
126                 gchar *username;
127                 struct passwd *passwd;
128
129                 if (slash)
130                         username = g_strndup(notilde, slash - notilde);
131                 else
132                         username = g_strdup(notilde);
133
134                 passwd = getpwnam(username);
135                 g_free(username);
136
137                 if (!passwd)
138                         return g_strdup(filename);
139
140                 home = passwd->pw_dir;
141                 }
142
143         if (slash)
144                 return g_build_filename(home, G_DIR_SEPARATOR_S, slash + 1, NULL);
145
146         return g_build_filename(home, G_DIR_SEPARATOR_S, NULL);
147 #endif
148 }
149
150 /* Search for latitude/longitude parameters in a string
151  */
152
153 #define GEOCODE_NAME "geocode-parameters.awk"
154 enum {
155         BUFSIZE = 128
156 };
157
158 gchar *decode_geo_script(const gchar *path_dir, const gchar *input_text)
159 {
160         std::unique_ptr<gchar, decltype(&g_free)> message{nullptr, g_free};
161         gchar *path = g_build_filename(path_dir, GEOCODE_NAME, NULL);
162         gchar *cmd = g_strconcat("echo \'", input_text, "\'  | awk -f ", path, NULL);
163
164         if (g_file_test(path, G_FILE_TEST_EXISTS))
165                 {
166                 gchar buf[BUFSIZE];
167                 FILE *fp;
168
169                 if ((fp = popen(cmd, "r")) == nullptr)
170                         {
171                         message.reset(g_strconcat("Error: opening pipe\n", input_text, NULL));
172                         }
173                 else
174                         {
175                         while (fgets(buf, BUFSIZE, fp))
176                                 {
177                                 DEBUG_1("Output: %s", buf);
178                                 }
179
180                         message.reset(g_strconcat(buf, NULL));
181
182                         if(pclose(fp))
183                                 {
184                                 message.reset(g_strconcat("Error: Command not found or exited with error status\n", input_text, NULL));
185                                 }
186                         }
187                 }
188         else
189                 {
190                 message.reset(g_strconcat(input_text, NULL));
191                 }
192
193         g_free(path);
194         g_free(cmd);
195         return message.release();
196 }
197
198 gchar *decode_geo_parameters(const gchar *input_text)
199 {
200         gchar *message;
201         gchar *dir;
202
203         message = decode_geo_script(gq_bindir, input_text);
204         if (strstr(message, "Error"))
205                 {
206                 g_free(message);
207                 dir = g_build_filename(get_rc_dir(), "applications", NULL);
208                 message = decode_geo_script(dir, input_text);
209                 g_free(dir);
210                 }
211
212         return message;
213 }
214
215 /* Run a command like system() but may output debug messages. */
216 int runcmd(const gchar *cmd)
217 {
218 #if 1
219         return system(cmd);
220         return 0;
221 #else
222         /* For debugging purposes */
223         int retval = -1;
224         FILE *in;
225
226         DEBUG_1("Running command: %s", cmd);
227
228         in = popen(cmd, "r");
229         if (in)
230                 {
231                 int status;
232                 const gchar *msg;
233                 gchar buf[2048];
234
235                 while (fgets(buf, sizeof(buf), in) != NULL )
236                         {
237                         DEBUG_1("Output: %s", buf);
238                         }
239
240                 status = pclose(in);
241
242                 if (WIFEXITED(status))
243                         {
244                         msg = "Command terminated with exit code";
245                         retval = WEXITSTATUS(status);
246                         }
247                 else if (WIFSIGNALED(status))
248                         {
249                         msg = "Command was killed by signal";
250                         retval = WTERMSIG(status);
251                         }
252                 else
253                         {
254                         msg = "pclose() returned";
255                         retval = status;
256                         }
257
258                 DEBUG_1("%s : %d\n", msg, retval);
259         }
260
261         return retval;
262 #endif
263 }
264
265 /**
266  * @brief Returns integer representing first_day_of_week
267  * @returns Integer in range 1 to 7
268  * 
269  * Uses current locale to get first day of week.
270  * If _NL_TIME_FIRST_WEEKDAY is not available, ISO 8601
271  * states first day of week is Monday.
272  * USA, Mexico and Canada (and others) use Sunday as first day of week.
273  * 
274  * Sunday == 1
275  */
276 gint date_get_first_day_of_week()
277 {
278 #if HAVE__NL_TIME_FIRST_WEEKDAY
279         return nl_langinfo(_NL_TIME_FIRST_WEEKDAY)[0];
280 #else
281         gchar *dot;
282         gchar *current_locale;
283
284         current_locale = setlocale(LC_ALL, NULL);
285         dot = strstr(current_locale, ".");
286         if ((strncmp(dot - 2, "US", 2) == 0) || (strncmp(dot - 2, "MX", 2) == 0) || (strncmp(dot - 2, "CA", 2) == 0))
287                 {
288                 return 1;
289                 }
290         else
291                 {
292                 return 2;
293                 }
294 #endif
295 }
296
297 /**
298  * @brief Get an abbreviated day name from locale
299  * @param day Integer in range 1 to 7, representing day of week
300  * @returns String containing abbreviated day name
301  * 
302  *  Uses current locale to get day name
303  * 
304  * Sunday == 1
305  * Result must be freed
306  */
307 gchar *date_get_abbreviated_day_name(gint day)
308 {
309         gchar *abday = nullptr;
310
311         switch (day)
312                 {
313                 case 1:
314                 abday = g_strdup(nl_langinfo(ABDAY_1));
315                 break;
316                 case 2:
317                 abday = g_strdup(nl_langinfo(ABDAY_2));
318                 break;
319                 case 3:
320                 abday = g_strdup(nl_langinfo(ABDAY_3));
321                 break;
322                 case 4:
323                 abday = g_strdup(nl_langinfo(ABDAY_4));
324                 break;
325                 case 5:
326                 abday = g_strdup(nl_langinfo(ABDAY_5));
327                 break;
328                 case 6:
329                 abday = g_strdup(nl_langinfo(ABDAY_6));
330                 break;
331                 case 7:
332                 abday = g_strdup(nl_langinfo(ABDAY_7));
333                 break;
334                 }
335
336         return abday;
337 }
338
339 gchar *convert_rating_to_stars(gint rating)
340 {
341         GString *str = g_string_new(nullptr);
342
343         if (rating == -1)
344                 {
345                 str = g_string_append_unichar(str, options->star_rating.rejected);
346                 return g_string_free(str, FALSE);
347                 }
348
349         if (rating > 0 && rating < 6)
350                 {
351                 for (; rating > 0; --rating)
352                         {
353                         str = g_string_append_unichar(str, options->star_rating.star);
354                         }
355                 return g_string_free(str, FALSE);
356                 }
357
358         return g_strdup("");
359 }
360
361 gchar *get_symbolic_link(const gchar *path_utf8)
362 {
363         gchar *sl;
364         struct stat st;
365         gchar *ret = g_strdup("");
366
367         sl = path_from_utf8(path_utf8);
368
369         if (lstat(sl, &st) == 0 && S_ISLNK(st.st_mode))
370                 {
371                 gchar *buf;
372                 gint l;
373
374                 buf = static_cast<gchar *>(g_malloc(st.st_size + 1));
375                 l = readlink(sl, buf, st.st_size);
376
377                 if (l == st.st_size)
378                         {
379                         buf[l] = '\0';
380
381                         ret = buf;
382                         }
383                 else
384                         {
385                         g_free(buf);
386                         }
387                 }
388
389         g_free(sl);
390
391         return ret;
392 }
393
394 gint get_cpu_cores()
395 {
396     return sysconf(_SC_NPROCESSORS_ONLN);
397 }
398
399 #if HAVE_GTK4
400 void convert_gdkcolor_to_gdkrgba(gpointer data, GdkRGBA *gdk_rgba)
401 {
402 /* @FIXME GTK4 stub */
403 }
404 #else
405 void convert_gdkcolor_to_gdkrgba(gpointer data, GdkRGBA *gdk_rgba)
406 {
407         auto gdk_color = static_cast<GdkColor *>(data);
408
409         gdk_rgba->red = CLAMP((double)gdk_color->red / 65535.0, 0.0, 1.0);
410         gdk_rgba->green = CLAMP((double)gdk_color->green / 65535.0, 0.0, 1.0);
411         gdk_rgba->blue = CLAMP((double)gdk_color->blue / 65535.0, 0.0, 1.0);
412         gdk_rgba->alpha = 1.0;
413 }
414 #endif
415
416 void gq_gtk_entry_set_text(GtkEntry *entry, const gchar *text)
417 {
418         GtkEntryBuffer *buffer;
419
420         buffer = gtk_entry_get_buffer(entry);
421         gtk_entry_buffer_set_text(buffer, text, static_cast<gint>(g_utf8_strlen(text, -1)));
422 }
423
424 const gchar *gq_gtk_entry_get_text(GtkEntry *entry)
425 {
426         GtkEntryBuffer *buffer;
427
428         buffer = gtk_entry_get_buffer(entry);
429         return gtk_entry_buffer_get_text(buffer);
430 }
431
432 void gq_gtk_grid_attach(GtkGrid *grid, GtkWidget *child, guint left_attach, guint right_attach, guint top_attach, guint bottom_attach, GtkAttachOptions, GtkAttachOptions, guint, guint)
433 {
434         gtk_grid_attach(grid, child, left_attach, top_attach, right_attach - left_attach, bottom_attach - top_attach);
435 }
436
437 void gq_gtk_grid_attach_default(GtkGrid *grid, GtkWidget *child, guint left_attach, guint right_attach, guint top_attach, guint bottom_attach )
438 {
439         gtk_grid_attach(grid, child, left_attach, top_attach, right_attach - left_attach, bottom_attach - top_attach);
440 }
441
442 /* Copied from the libarchive .repo. examples */
443
444 #if !HAVE_ARCHIVE
445 gchar *open_archive(FileData *)
446 {
447         log_printf("%s", _("Warning: libarchive not installed"));
448         return NULL;
449 }
450
451 #else
452
453 #include <archive.h>
454 #include <archive_entry.h>
455
456 static void errmsg(const char *);
457 static gboolean extract(const char *filename, int do_extract, int flags);
458 static int copy_data(struct archive *, struct archive *);
459 static void msg(const char *);
460 static int verbose = 0;
461
462 gchar *open_archive(FileData *fd)
463 {
464         int flags;
465         gchar *current_dir;
466         gchar *destination_dir;
467         gboolean success;
468         gint error;
469
470         destination_dir = g_build_filename(g_get_tmp_dir(), GQ_ARCHIVE_DIR, instance_identifier, fd->path, NULL);
471
472         if (!recursive_mkdir_if_not_exists(destination_dir, 0755))
473                 {
474                 log_printf("%s%s%s", _("Open Archive - Cannot create directory: "), destination_dir, "\n");
475                 g_free(destination_dir);
476                 return nullptr;
477                 }
478
479         current_dir = g_get_current_dir();
480         error = chdir(destination_dir);
481         if (error)
482                 {
483                 log_printf("%s%s%s%s%s", _("Open Archive - Cannot change directory to: "), destination_dir, _("\n  Error code: "), strerror(errno), "\n");
484                 g_free(destination_dir);
485                 g_free(current_dir);
486                 return nullptr;
487                 }
488
489         flags = ARCHIVE_EXTRACT_TIME;
490         success = extract(fd->path, 1, flags);
491
492         error = chdir(current_dir);
493         if (error)
494                 {
495                 log_printf("%s%s%s%s%s", _("Open Archive - Cannot change directory to: "), current_dir, _("\n  Error code: "), strerror(errno), "\n");
496                 g_free(destination_dir);
497                 g_free(current_dir);
498                 return nullptr;
499                 }
500         g_free(current_dir);
501
502         if (!success)
503                 {
504                 g_free(destination_dir);
505                 destination_dir = nullptr;
506                 }
507
508         return destination_dir;
509 }
510
511 static gboolean extract(const char *filename, int do_extract, int flags)
512 {
513         struct archive *a;
514         struct archive *ext;
515         struct archive_entry *entry;
516         int r;
517
518         a = archive_read_new();
519         ext = archive_write_disk_new();
520         archive_write_disk_set_options(ext, flags);
521         archive_write_disk_set_standard_lookup(ext);
522         archive_read_support_filter_all(a);
523         archive_read_support_format_all(a);
524
525         if (filename != nullptr && strcmp(filename, "-") == 0)
526                 {
527                 filename = nullptr;
528                 }
529         if ((r = archive_read_open_filename(a, filename, 10240)))
530                 {
531                 errmsg(archive_error_string(a));
532                 errmsg("\n");
533                 return(FALSE);
534                 }
535         for (;;)
536                 {
537                 int needcr = 0;
538
539                 r = archive_read_next_header(a, &entry);
540                 if (r == ARCHIVE_EOF)
541                         {
542                         break;
543                         }
544                 if (r != ARCHIVE_OK)
545                         {
546                         errmsg(archive_error_string(a));
547                         errmsg("\n");
548                         return(FALSE);
549                         }
550                 if (verbose && do_extract)
551                         {
552                         msg("x ");
553                         }
554                 if (verbose || !do_extract)
555                         {
556                         msg(archive_entry_pathname(entry));
557                         msg(" ");
558                         needcr = 1;
559                         }
560                 if (do_extract)
561                         {
562                         r = archive_write_header(ext, entry);
563                         if (r != ARCHIVE_OK)
564                                 {
565                                 errmsg(archive_error_string(a));
566                                 needcr = 1;
567                                 }
568                         else
569                                 {
570                                 r = copy_data(a, ext);
571                                 if (r != ARCHIVE_OK)
572                                         {
573                                         needcr = 1;
574                                         }
575                                 }
576                         }
577                 if (needcr)
578                         {
579                         msg("\n");
580                         }
581                 }
582         archive_read_close(a);
583         archive_read_free(a);
584
585         archive_write_close(ext);
586         archive_write_free(ext);
587         return(TRUE);
588 }
589
590 static int copy_data(struct archive *ar, struct archive *aw)
591 {
592         int r;
593         const void *buff;
594         size_t size;
595         int64_t offset;
596
597         for (;;)
598                 {
599                 r = archive_read_data_block(ar, &buff, &size, &offset);
600                 if (r == ARCHIVE_EOF)
601                         return (ARCHIVE_OK);
602                 if (r != ARCHIVE_OK)
603                         {
604                         errmsg(archive_error_string(ar));
605                         return (r);
606                         }
607                 r = archive_write_data_block(aw, buff, size, offset);
608                 if (r != ARCHIVE_OK)
609                         {
610                         errmsg(archive_error_string(ar));
611                         return (r);
612                         }
613                 }
614 }
615
616 static void msg(const char *m)
617 {
618         log_printf("Open Archive - libarchive error: %s \n", m);
619 }
620
621 static void errmsg(const char *m)
622 {
623         if (m == nullptr)
624                 {
625                 m = "Error: No error description provided.\n";
626                 }
627         log_printf("Open Archive - libarchive error: %s \n", m);
628 }
629 #endif
630 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */