1 #include "util.hpp"
2 #include <fstream>
3 #include <giomm.h>
4 #include <glibmm/miscutils.h>
5 #include <glib/gstdio.h>
6 #include <unistd.h>
7 #ifdef G_OS_WIN32
8 #include <windows.h>
9 #include <iostream>
10 #endif
11 #include <iomanip>
12 #include "nlohmann/json.hpp"
13 #include "alphanum/alphanum.hpp"
14 #include <gdk/gdkkeysyms.h>
15 #include "imp/in_tool_action.hpp"
16 #include "str_util.hpp"
17
18 namespace horizon {
19
make_ifstream(const std::string & filename_utf8,std::ios_base::openmode mode)20 std::ifstream make_ifstream(const std::string &filename_utf8, std::ios_base::openmode mode)
21 {
22 #ifdef G_OS_WIN32
23 auto wfilename = reinterpret_cast<wchar_t *>(g_utf8_to_utf16(filename_utf8.c_str(), -1, NULL, NULL, NULL));
24 std::ifstream ifs(wfilename, mode);
25 g_free(wfilename);
26 #else
27 std::ifstream ifs(filename_utf8, mode);
28 #endif
29 return ifs;
30 }
31
make_ofstream(const std::string & filename_utf8,std::ios_base::openmode mode)32 std::ofstream make_ofstream(const std::string &filename_utf8, std::ios_base::openmode mode)
33 {
34 #ifdef G_OS_WIN32
35 auto wfilename = reinterpret_cast<wchar_t *>(g_utf8_to_utf16(filename_utf8.c_str(), -1, NULL, NULL, NULL));
36 std::ofstream ofs(wfilename, mode);
37 g_free(wfilename);
38 #else
39 std::ofstream ofs(filename_utf8, mode);
40 #endif
41 return ofs;
42 }
43
save_json_to_file(const std::string & filename,const json & j)44 void save_json_to_file(const std::string &filename, const json &j)
45 {
46 auto ofs = make_ofstream(filename);
47 if (!ofs.is_open()) {
48 throw std::runtime_error("can't save json " + filename);
49 return;
50 }
51 ofs << std::setw(4) << j;
52 ofs.close();
53 }
54
load_json_from_file(const std::string & filename)55 json load_json_from_file(const std::string &filename)
56 {
57 json j;
58 auto ifs = make_ifstream(filename);
59 if (!ifs.is_open()) {
60 throw std::runtime_error("file " + filename + " not opened");
61 }
62 ifs >> j;
63 ifs.close();
64 return j;
65 }
66
json_from_resource(const std::string & rsrc)67 json json_from_resource(const std::string &rsrc)
68 {
69 auto json_bytes = Gio::Resource::lookup_data_global(rsrc);
70 gsize size = json_bytes->get_size();
71 return json::parse((const char *)json_bytes->get_data(size));
72 }
73
74 #ifdef G_OS_WIN32
get_exe_dir()75 std::string get_exe_dir()
76 {
77 TCHAR szPath[MAX_PATH];
78 if (!GetModuleFileName(NULL, szPath, MAX_PATH)) {
79 throw std::runtime_error("can't find executable");
80 return "";
81 }
82 else {
83 return Glib::path_get_dirname(szPath);
84 }
85 }
86
87 #else
88 #if defined(__linux__)
get_exe_dir()89 std::string get_exe_dir()
90 {
91
92 char buf[PATH_MAX];
93 ssize_t len;
94 if ((len = readlink("/proc/self/exe", buf, sizeof(buf) - 1)) != -1) {
95 buf[len] = '\0';
96 return Glib::path_get_dirname(buf);
97 }
98 else {
99 throw std::runtime_error("can't find executable");
100 return "";
101 }
102 }
103 #elif defined(__FreeBSD__) || defined(__DragonFly__)
104 #include <sys/sysctl.h>
get_exe_dir()105 std::string get_exe_dir()
106 {
107 char buf[PATH_MAX];
108 size_t len = sizeof(buf);
109 int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
110 int ret;
111 ret = sysctl(mib, 4, buf, &len, NULL, 0);
112 if (ret == 0) {
113 return Glib::path_get_dirname(buf);
114 }
115 else {
116 throw std::runtime_error("can't find executable");
117 return "";
118 }
119 }
120 #elif defined(__APPLE__)
121 #include <mach-o/dyld.h>
get_exe_dir()122 std::string get_exe_dir()
123 {
124 char buf[PATH_MAX];
125 uint32_t size = sizeof(buf);
126 if (_NSGetExecutablePath(buf, &size) == 0) {
127 return Glib::path_get_dirname(buf);
128 }
129 else {
130 throw std::runtime_error("can't find executable");
131 return "";
132 }
133 }
134 #else
135 #error Dont know how to find path to executable on your OS.
136 #endif
137 #endif
138
139
allow_set_foreground_window(int pid)140 void allow_set_foreground_window(int pid)
141 {
142 #ifdef G_OS_WIN32
143 AllowSetForegroundWindow(pid);
144 #endif
145 }
146
endswith(const std::string & haystack,const std::string & needle)147 bool endswith(const std::string &haystack, const std::string &needle)
148 {
149 auto pos = haystack.rfind(needle);
150 if (pos == std::string::npos)
151 return false;
152 else
153 return (haystack.size() - haystack.rfind(needle)) == needle.size();
154 }
155
strcmp_natural(const std::string & a,const std::string & b)156 int strcmp_natural(const std::string &a, const std::string &b)
157 {
158 return doj::alphanum_comp(a, b);
159 }
160
strcmp_natural(const char * a,const char * b)161 int strcmp_natural(const char *a, const char *b)
162 {
163 return doj::alphanum_comp(a, b);
164 }
165
get_config_dir()166 std::string get_config_dir()
167 {
168 return Glib::build_filename(Glib::get_user_config_dir(), "horizon");
169 }
170
create_config_dir()171 void create_config_dir()
172 {
173 auto config_dir = get_config_dir();
174 if (!Glib::file_test(config_dir, Glib::FILE_TEST_EXISTS))
175 Gio::File::create_for_path(config_dir)->make_directory_with_parents();
176 }
177
replace_backslash(std::string & path)178 void replace_backslash(std::string &path)
179 {
180 #ifdef G_OS_WIN32
181 std::replace(path.begin(), path.end(), '\\', '/');
182 #endif
183 }
184
compare_files(const std::string & filename_a,const std::string & filename_b)185 bool compare_files(const std::string &filename_a, const std::string &filename_b)
186 {
187 auto mapped_a = g_mapped_file_new(filename_a.c_str(), false, NULL);
188 if (!mapped_a) {
189 return false;
190 }
191 auto mapped_b = g_mapped_file_new(filename_b.c_str(), false, NULL);
192 if (!mapped_b) {
193 g_mapped_file_unref(mapped_a);
194 return false;
195 }
196 if (g_mapped_file_get_length(mapped_a) != g_mapped_file_get_length(mapped_b)) {
197 g_mapped_file_unref(mapped_a);
198 g_mapped_file_unref(mapped_b);
199 return false;
200 }
201 auto size = g_mapped_file_get_length(mapped_a);
202 auto r = memcmp(g_mapped_file_get_contents(mapped_a), g_mapped_file_get_contents(mapped_b), size);
203 g_mapped_file_unref(mapped_a);
204 g_mapped_file_unref(mapped_b);
205 return r == 0;
206 }
207
find_files_recursive(const std::string & base_path,std::function<void (const std::string &)> cb,const std::string & path)208 void find_files_recursive(const std::string &base_path, std::function<void(const std::string &)> cb,
209 const std::string &path)
210 {
211 auto this_path = Glib::build_filename(base_path, path);
212 Glib::Dir dir(this_path);
213 for (const auto &it : dir) {
214 auto itempath = Glib::build_filename(this_path, it);
215 if (Glib::file_test(itempath, Glib::FILE_TEST_IS_DIR)) {
216 find_files_recursive(base_path, cb, Glib::build_filename(path, it));
217 }
218 else if (Glib::file_test(itempath, Glib::FILE_TEST_IS_REGULAR)) {
219 cb(Glib::build_filename(path, it));
220 }
221 }
222 }
223
color_to_json(const Color & c)224 json color_to_json(const Color &c)
225 {
226 json j;
227 j["r"] = c.r;
228 j["g"] = c.g;
229 j["b"] = c.b;
230 return j;
231 }
232
233
color_from_json(const json & j)234 Color color_from_json(const json &j)
235 {
236 Color c;
237 c.r = j.at("r");
238 c.g = j.at("g");
239 c.b = j.at("b");
240 return c;
241 }
242
colori_to_json(const ColorI & c)243 json colori_to_json(const ColorI &c)
244 {
245 json j;
246 j["r"] = c.r;
247 j["g"] = c.g;
248 j["b"] = c.b;
249 return j;
250 }
251
252
colori_from_json(const json & j)253 ColorI colori_from_json(const json &j)
254 {
255 ColorI c;
256 c.r = j.at("r");
257 c.g = j.at("g");
258 c.b = j.at("b");
259 return c;
260 }
261
262 static std::locale the_locale = std::locale::classic();
263
setup_locale()264 void setup_locale()
265 {
266 std::locale::global(std::locale::classic());
267 char decimal_sep = '.';
268 #ifdef G_OS_WIN32
269 TCHAR szSep[8];
270 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, szSep, 8);
271 if (szSep[0] == ',') {
272 decimal_sep = ',';
273 }
274 #else
275 try {
276 // Try setting the "native locale", in order to retain
277 // things like decimal separator. This might fail in
278 // case the user has no notion of a native locale.
279 auto user_locale = std::locale("");
280 decimal_sep = std::use_facet<std::numpunct<char>>(user_locale).decimal_point();
281 }
282 catch (...) {
283 // just proceed
284 }
285 #endif
286
287 class comma : public std::numpunct<char> {
288 public:
289 comma(char c) : dp(c)
290 {
291 }
292 char do_decimal_point() const override
293 {
294 return dp;
295 } // comma
296
297 private:
298 const char dp;
299 };
300
301 the_locale = std::locale(std::locale::classic(), new comma(decimal_sep));
302 }
303
get_locale()304 const std::locale &get_locale()
305 {
306 return the_locale;
307 }
308
format_m_of_n(unsigned int m,unsigned int n)309 std::string format_m_of_n(unsigned int m, unsigned int n)
310 {
311 auto n_str = std::to_string(n);
312 auto digits_max = n_str.size();
313 auto m_str = std::to_string(m);
314 std::string prefix;
315 for (size_t i = 0; i < (digits_max - (int)m_str.size()); i++) {
316 prefix += " ";
317 }
318 return prefix + m_str + "/" + n_str;
319 }
320
format_digits(unsigned int m,unsigned int digits_max)321 std::string format_digits(unsigned int m, unsigned int digits_max)
322 {
323 auto m_str = std::to_string(m);
324 std::string prefix;
325 if (m_str.size() < digits_max) {
326 for (size_t i = 0; i < (digits_max - (int)m_str.size()); i++) {
327 prefix += " ";
328 }
329 }
330 return prefix + m_str;
331 }
332
parse_si(const std::string & inps)333 double parse_si(const std::string &inps)
334 {
335 static const auto regex = Glib::Regex::create(
336 "([+-]?)(?:(?:(\\d+)[\\.,]?(\\d*))|(?:[\\.,](\\d+)))(?:[eE]([-+]?)([0-9]+)|\\s*([a-zA-Zµ])|)");
337 Glib::ustring inp(inps);
338 Glib::MatchInfo ma;
339 if (regex->match(inp, ma)) {
340 auto sign = ma.fetch(1);
341 auto intv = ma.fetch(2);
342 auto fracv = ma.fetch(3);
343 auto fracv2 = ma.fetch(4);
344 auto exp_sign = ma.fetch(5);
345 auto exp = ma.fetch(6);
346 auto prefix = ma.fetch(7);
347
348 double v = 0;
349 if (intv.size()) {
350 v = std::stoi(intv);
351 if (fracv.size()) {
352 v += std::stoi(fracv) * std::pow(10, (int)fracv.size() * -1);
353 }
354 }
355 else {
356 v = std::stoi(fracv2) * std::pow(10, (int)fracv2.size() * -1);
357 }
358
359 if (exp.size()) {
360 int iexp = std::stoi(exp);
361 if (exp_sign == "-") {
362 iexp *= -1;
363 }
364 v *= std::pow(10, iexp);
365 }
366 else if (prefix.size()) {
367 int prefix_exp = 0;
368 if (prefix == "p")
369 prefix_exp = -12;
370 else if (prefix == "n" || prefix == "N")
371 prefix_exp = -9;
372 else if (prefix == "u" || prefix == "U" || prefix == "µ")
373 prefix_exp = -6;
374 else if (prefix == "m")
375 prefix_exp = -3;
376 else if (prefix == "k" || prefix == "K")
377 prefix_exp = 3;
378 else if (prefix == "M")
379 prefix_exp = 6;
380 else if (prefix == "G" || prefix == "g")
381 prefix_exp = 9;
382 else if (prefix == "T" || prefix == "t")
383 prefix_exp = 12;
384 v *= std::pow(10, prefix_exp);
385 }
386 if (sign == "-")
387 v *= -1;
388
389 return v;
390 }
391
392 return NAN;
393 }
394
rmdir_recursive(const std::string & dir_name)395 void rmdir_recursive(const std::string &dir_name)
396 {
397 Glib::Dir dir(dir_name);
398 std::list<std::string> entries(dir.begin(), dir.end());
399 for (const auto &it : entries) {
400 auto filename = Glib::build_filename(dir_name, it);
401 if (Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) {
402 rmdir_recursive(filename);
403 }
404 else {
405 if (g_unlink(filename.c_str()) != 0)
406 throw std::runtime_error("g_unlink");
407 }
408 }
409 if (g_rmdir(dir_name.c_str()) != 0)
410 throw std::runtime_error("g_rmdir");
411 }
412
isvar(char c)413 static bool isvar(char c)
414 {
415 return std::isalnum(c) || c == '_' || c == ':';
416 }
417
interpolate_text(const std::string & str,std::function<std::optional<std::string> (const std::string & s)> interpolator)418 std::string interpolate_text(const std::string &str,
419 std::function<std::optional<std::string>(const std::string &s)> interpolator)
420 {
421
422 enum class State { NORMAL, DOLLAR, VAR, VAR_BRACED };
423 State state = State::NORMAL;
424 std::string out;
425 std::string var_current;
426 size_t i = 0;
427 while (true) {
428 const char c = str.c_str()[i++];
429 switch (state) {
430 case State::NORMAL:
431 if (c == '$') {
432 state = State::DOLLAR;
433 }
434 else if (c) {
435 out.append(1, c);
436 }
437 break;
438
439 case State::DOLLAR:
440 if (isvar(c)) {
441 var_current.clear();
442 var_current.append(1, c);
443 state = State::VAR;
444 }
445 else if (c == '{') {
446 var_current.clear();
447 state = State::VAR_BRACED;
448 }
449 else {
450 out.append("$");
451 if (c)
452 out.append(1, c);
453 state = State::NORMAL;
454 }
455 break;
456
457 case State::VAR:
458 if (isvar(c)) {
459 var_current.append(1, c);
460 }
461 else {
462 if (auto subst = interpolator(var_current)) {
463 out.append(*subst);
464 }
465 else {
466 out.append("$" + var_current);
467 }
468
469 if (c == '$') {
470 state = State::DOLLAR;
471 }
472 else if (c) {
473 out.append(1, c);
474 state = State::NORMAL;
475 }
476 }
477 break;
478
479 case State::VAR_BRACED:
480 if (c == '}') {
481 if (auto subst = interpolator(var_current)) {
482 out.append(*subst);
483 }
484 else {
485 out.append("${" + var_current + "}");
486 }
487 state = State::NORMAL;
488 }
489 else {
490 var_current.append(1, c);
491 }
492 break;
493 }
494
495 if (!c)
496 break;
497 }
498
499 return out;
500 }
501
dir_from_action(InToolActionID a)502 std::pair<Coordi, bool> dir_from_action(InToolActionID a)
503 {
504 using I = InToolActionID;
505 switch (a) {
506 case I::MOVE_UP_FINE:
507 return {{0, 1}, true};
508 case I::MOVE_UP:
509 return {{0, 1}, false};
510
511 case I::MOVE_DOWN_FINE:
512 return {{0, -1}, true};
513 case I::MOVE_DOWN:
514 return {{0, -1}, false};
515
516 case I::MOVE_LEFT_FINE:
517 return {{-1, 0}, true};
518 case I::MOVE_LEFT:
519 return {{-1, 0}, false};
520
521 case I::MOVE_RIGHT_FINE:
522 return {{1, 0}, true};
523 case I::MOVE_RIGHT:
524 return {{1, 0}, false};
525
526 default:
527 return {{0, 0}, false};
528 }
529 }
530
check_object_type(const json & j,ObjectType type)531 void check_object_type(const json &j, ObjectType type)
532 {
533 if (j.at("type") != object_type_lut.lookup_reverse(type)) {
534 throw std::runtime_error("wrong object type");
535 }
536 }
537
ensure_parent_dir(const std::string & path)538 void ensure_parent_dir(const std::string &path)
539 {
540 auto parent = Glib::path_get_dirname(path);
541 if (!Glib::file_test(parent, Glib::FILE_TEST_IS_DIR)) {
542 Gio::File::create_for_path(parent)->make_directory_with_parents();
543 }
544 }
545
546
append_dot_json(const std::string & s)547 std::string append_dot_json(const std::string &s)
548 {
549 std::string r = s;
550 trim(r);
551 if (!endswith(r, ".json")) {
552 return r + ".json";
553 }
554 return r;
555 }
556
557 } // namespace horizon
558