1 #include "preferences.hpp"
2 #include "common/lut.hpp"
3 #include "util/util.hpp"
4 #include <fstream>
5 #include <giomm/file.h>
6 #include <glibmm/fileutils.h>
7 #include <glibmm/miscutils.h>
8 #include "nlohmann/json.hpp"
9 #include "logger/logger.hpp"
10 #include "imp/in_tool_action_catalog.hpp"
11 #include "core/tool_id.hpp"
12 #include "imp/actions.hpp"
13
14 namespace horizon {
15
16 static const LutEnumStr<Appearance::GridStyle> grid_style_lut = {
17 {"cross", Appearance::GridStyle::CROSS},
18 {"dot", Appearance::GridStyle::DOT},
19 {"grid", Appearance::GridStyle::GRID},
20 };
21
22 static const LutEnumStr<Appearance::GridFineModifier> grid_fine_mod_lut = {
23 {"alt", Appearance::GridFineModifier::ALT},
24 {"ctrl", Appearance::GridFineModifier::CTRL},
25 };
26
27 static const LutEnumStr<Appearance::CursorSize> cursor_size_lut = {
28 {"default", Appearance::CursorSize::DEFAULT},
29 {"large", Appearance::CursorSize::LARGE},
30 {"full", Appearance::CursorSize::FULL},
31 };
32
33 static const LutEnumStr<Preferences::StockInfoProviderSel> stock_info_provider_lut = {
34 {"none", Preferences::StockInfoProviderSel::NONE},
35 {"partinfo", Preferences::StockInfoProviderSel::PARTINFO},
36 {"digikey", Preferences::StockInfoProviderSel::DIGIKEY}};
37
38 #ifdef G_OS_WIN32
39 static const bool capture_output_default = true;
40 #else
41 static const bool capture_output_default = false;
42 #endif
43
Preferences()44 Preferences::Preferences()
45 {
46 canvas_non_layer.appearance.layer_colors[0] = {1, 1, 0};
47 capture_output = capture_output_default;
48 }
49
load_default()50 void Preferences::load_default()
51 {
52 canvas_layer = CanvasPreferences();
53 canvas_non_layer = CanvasPreferences();
54 canvas_non_layer.appearance.layer_colors[0] = {1, 1, 0};
55 key_sequences.load_from_json(json_from_resource("/org/horizon-eda/horizon/imp/keys_default.json"));
56 in_tool_key_sequences.load_from_json(json_from_resource("/org/horizon-eda/horizon/imp/in_tool_keys_default.json"));
57 capture_output = capture_output_default;
58 }
59
get_preferences_filename()60 std::string Preferences::get_preferences_filename()
61 {
62 return Glib::build_filename(get_config_dir(), "prefs.json");
63 }
64
65 #define COLORP_LUT_ITEM(x) \
66 { \
67 #x, ColorP::x \
68 }
69
70 static const LutEnumStr<ColorP> colorp_lut = {
71 COLORP_LUT_ITEM(FROM_LAYER),
72 COLORP_LUT_ITEM(JUNCTION),
73 COLORP_LUT_ITEM(FRAG_ORPHAN),
74 COLORP_LUT_ITEM(AIRWIRE_ROUTER),
75 COLORP_LUT_ITEM(TEXT_OVERLAY),
76 COLORP_LUT_ITEM(HOLE),
77 COLORP_LUT_ITEM(DIMENSION),
78 COLORP_LUT_ITEM(ERROR),
79 COLORP_LUT_ITEM(NET),
80 COLORP_LUT_ITEM(BUS),
81 COLORP_LUT_ITEM(FRAME),
82 COLORP_LUT_ITEM(AIRWIRE),
83 COLORP_LUT_ITEM(PIN),
84 COLORP_LUT_ITEM(PIN_HIDDEN),
85 COLORP_LUT_ITEM(DIFFPAIR),
86 COLORP_LUT_ITEM(BACKGROUND),
87 COLORP_LUT_ITEM(GRID),
88 COLORP_LUT_ITEM(CURSOR_NORMAL),
89 COLORP_LUT_ITEM(CURSOR_TARGET),
90 COLORP_LUT_ITEM(ORIGIN),
91 COLORP_LUT_ITEM(MARKER_BORDER),
92 COLORP_LUT_ITEM(SELECTION_BOX),
93 COLORP_LUT_ITEM(SELECTION_LINE),
94 COLORP_LUT_ITEM(SELECTABLE_OUTER),
95 COLORP_LUT_ITEM(SELECTABLE_INNER),
96 COLORP_LUT_ITEM(SELECTABLE_PRELIGHT),
97 COLORP_LUT_ITEM(SELECTABLE_ALWAYS),
98 COLORP_LUT_ITEM(SEARCH),
99 COLORP_LUT_ITEM(SEARCH_CURRENT),
100 COLORP_LUT_ITEM(SHADOW),
101 COLORP_LUT_ITEM(CONNECTION_LINE),
102 COLORP_LUT_ITEM(NOPOPULATE_X),
103 COLORP_LUT_ITEM(PROJECTION),
104 };
105
serialize() const106 json CanvasPreferences::serialize() const
107 {
108 json j = serialize_colors();
109 j["grid_style"] = grid_style_lut.lookup_reverse(appearance.grid_style);
110 j["grid_opacity"] = appearance.grid_opacity;
111 j["highlight_dim"] = appearance.highlight_dim;
112 j["highlight_lighten"] = appearance.highlight_lighten;
113 j["grid_fine_modifier"] = grid_fine_mod_lut.lookup_reverse(appearance.grid_fine_modifier);
114 j["cursor_size"] = cursor_size_lut.lookup_reverse(appearance.cursor_size);
115 j["cursor_size_tool"] = cursor_size_lut.lookup_reverse(appearance.cursor_size_tool);
116 j["msaa"] = appearance.msaa;
117 j["min_line_width"] = appearance.min_line_width;
118 j["min_selectable_size"] = appearance.min_selectable_size;
119 j["snap_radius"] = appearance.snap_radius;
120 return j;
121 }
122
serialize_colors() const123 json CanvasPreferences::serialize_colors() const
124 {
125 json j;
126 json j_layer_colors = json::object();
127 for (const auto &it : appearance.layer_colors) {
128 j_layer_colors[std::to_string(it.first)] = color_to_json(it.second);
129 }
130 j["layer_colors"] = j_layer_colors;
131
132 json j_colors = json::object();
133 for (const auto &it : appearance.colors) {
134 j_colors[colorp_lut.lookup_reverse(it.first)] = color_to_json(it.second);
135 }
136 j["colors"] = j_colors;
137 return j;
138 }
139
load_colors_from_json(const json & j)140 void CanvasPreferences::load_colors_from_json(const json &j)
141 {
142 if (j.count("layer_colors")) {
143 const auto &o = j.at("layer_colors");
144 for (auto it = o.cbegin(); it != o.cend(); ++it) {
145 int l = std::stoi(it.key());
146 appearance.layer_colors[l] = color_from_json(it.value());
147 }
148 }
149 if (j.count("colors")) {
150 const auto &o = j.at("colors");
151 for (auto it = o.cbegin(); it != o.cend(); ++it) {
152 auto c = colorp_lut.lookup(it.key(), ColorP::FROM_LAYER);
153 if (c != ColorP::FROM_LAYER)
154 appearance.colors[c] = color_from_json(it.value());
155 }
156 }
157 }
158
load_from_json(const json & j)159 void CanvasPreferences::load_from_json(const json &j)
160 {
161 appearance.grid_style = grid_style_lut.lookup(j.at("grid_style"));
162 appearance.grid_opacity = j.value("grid_opacity", .4);
163 appearance.highlight_dim = j.value("highlight_dim", .3);
164 appearance.highlight_lighten = j.value("highlight_lighten", .3);
165 appearance.grid_fine_modifier = grid_fine_mod_lut.lookup(j.value("grid_fine_modifier", "alt"));
166 appearance.cursor_size = cursor_size_lut.lookup(j.value("cursor_size", "default"));
167 appearance.cursor_size_tool = cursor_size_lut.lookup(j.value("cursor_size_tool", "default"));
168 appearance.msaa = j.value("msaa", 0);
169 appearance.min_line_width = j.value("min_line_width", 1.0);
170 appearance.min_selectable_size = j.value("min_selectable_size", 20.0);
171 appearance.snap_radius = j.value("snap_radius", 30.0);
172 load_colors_from_json(j);
173 }
174
serialize() const175 json SchematicPreferences::serialize() const
176 {
177 json j;
178 j["show_all_junctions"] = show_all_junctions;
179 j["drag_start_net_line"] = drag_start_net_line;
180 j["bend_non_ortho"] = bend_non_ortho;
181 return j;
182 }
183
load_from_json(const json & j)184 void SchematicPreferences::load_from_json(const json &j)
185 {
186 show_all_junctions = j.value("show_all_junctions", false);
187 drag_start_net_line = j.value("drag_start_net_line", true);
188 bend_non_ortho = j.value("bend_non_ortho", true);
189 }
190
serialize() const191 json BoardPreferences::serialize() const
192 {
193 json j;
194 j["drag_start_track"] = drag_start_track;
195 j["highlight_on_top"] = highlight_on_top;
196 j["show_text_in_tracks"] = show_text_in_tracks;
197 j["show_text_in_vias"] = show_text_in_vias;
198 j["move_using_router"] = move_using_router;
199 return j;
200 }
201
load_from_json(const json & j)202 void BoardPreferences::load_from_json(const json &j)
203 {
204 drag_start_track = j.value("drag_start_track", true);
205 highlight_on_top = j.value("highlight_on_top", true);
206 show_text_in_tracks = j.value("show_text_in_tracks", true);
207 show_text_in_vias = j.value("show_text_in_vias", true);
208 move_using_router = j.value("move_using_router", true);
209 }
210
serialize() const211 json ZoomPreferences::serialize() const
212 {
213 json j;
214 j["smooth_zoom_2d"] = smooth_zoom_2d;
215 j["smooth_zoom_3d"] = smooth_zoom_3d;
216 j["touchpad_pan"] = touchpad_pan;
217 j["zoom_factor"] = zoom_factor;
218 j["keyboard_zoom_to_cursor"] = keyboard_zoom_to_cursor;
219 return j;
220 }
221
load_from_json(const json & j)222 void ZoomPreferences::load_from_json(const json &j)
223 {
224 smooth_zoom_2d = j.value("smooth_zoom_2d", true);
225 smooth_zoom_3d = j.value("smooth_zoom_3d", false);
226 touchpad_pan = j.value("touchpad_pan", false);
227 zoom_factor = j.value("zoom_factor", 50);
228 keyboard_zoom_to_cursor = j.value("keyboard_zoom_to_cursor", false);
229 }
230
serialize() const231 json KeySequencesPreferences::serialize() const
232 {
233 json j;
234 for (const auto &it : keys) {
235 json k;
236 auto a_str = action_lut.lookup_reverse(it.first.first);
237 auto t_str = tool_lut.lookup_reverse(it.first.second);
238 k["action"] = a_str;
239 k["tool"] = t_str;
240 k["keys"] = json::object();
241 for (const auto &it2 : it.second) {
242 k["keys"][std::to_string(static_cast<int>(it2.first))] = json::array();
243 for (const auto &it3 : it2.second) {
244 json seq;
245 for (const auto &it4 : it3) {
246 json o;
247 o["key"] = gdk_keyval_name(it4.first);
248 o["mod"] = static_cast<int>(it4.second);
249 seq.push_back(o);
250 }
251 if (seq.size())
252 k["keys"][std::to_string(static_cast<int>(it2.first))].push_back(seq);
253 }
254 }
255 j.push_back(k);
256 }
257 return j;
258 }
259
load_from_json(const json & j)260 void KeySequencesPreferences::load_from_json(const json &j)
261 {
262 keys.clear();
263 append_from_json(j);
264 }
265
append_from_json(const json & j)266 void KeySequencesPreferences::append_from_json(const json &j)
267 {
268 for (const auto &it : j) {
269 try {
270 auto action = action_lut.lookup(it.at("action"), ActionID::NONE);
271 if (action != ActionID::NONE) {
272 auto tool = tool_lut.lookup(it.at("tool"), ToolID::NONE);
273 if (action != ActionID::TOOL || (action == ActionID::TOOL && tool != ToolID::NONE)) {
274 auto k = std::make_pair(action, tool);
275 if (keys.count(k) == 0) {
276 keys[k];
277 auto &j2 = it.at("keys");
278 for (auto it2 = j2.cbegin(); it2 != j2.cend(); ++it2) {
279 auto av = static_cast<ActionCatalogItem::Availability>(std::stoi(it2.key()));
280 keys[k][av];
281 for (const auto &it3 : it2.value()) {
282 keys[k][av].emplace_back();
283 for (const auto &it4 : it3) {
284 std::string keyname = it4.at("key");
285 auto key = gdk_keyval_from_name(keyname.c_str());
286 auto mod = static_cast<GdkModifierType>(it4.at("mod").get<int>());
287 keys[k][av].back().emplace_back(key, mod);
288 }
289 }
290 }
291 }
292 }
293 }
294 }
295 catch (const std::exception &e) {
296 Logger::log_warning("error loading key sequence", Logger::Domain::UNSPECIFIED, e.what());
297 }
298 catch (...) {
299 Logger::log_warning("error loading key sequence", Logger::Domain::UNSPECIFIED, "unknown error");
300 }
301 }
302 }
serialize() const303 json InToolKeySequencesPreferences::serialize() const
304 {
305 json j;
306 for (const auto &[action, sequences] : keys) {
307 auto a_str = in_tool_action_lut.lookup_reverse(action);
308 for (const auto &it2 : sequences) {
309 json seq;
310 for (const auto &it4 : it2) {
311 json o;
312 o["key"] = gdk_keyval_name(it4.first);
313 o["mod"] = static_cast<int>(it4.second);
314 seq.push_back(o);
315 }
316 if (seq.size())
317 j[a_str].push_back(seq);
318 }
319 }
320 return j;
321 }
322
load_from_json(const json & j)323 void InToolKeySequencesPreferences::load_from_json(const json &j)
324 {
325 keys.clear();
326 append_from_json(j);
327 }
328
append_from_json(const json & j)329 void InToolKeySequencesPreferences::append_from_json(const json &j)
330 {
331 for (const auto &[a_str, keys_seqs] : j.items()) {
332 try {
333 auto action = in_tool_action_lut.lookup(a_str, InToolActionID::NONE);
334 if (action != InToolActionID::NONE) {
335 if (keys.count(action) == 0) {
336 for (const auto &seq : keys_seqs) {
337 keys[action].emplace_back();
338 for (const auto &it4 : seq) {
339 std::string keyname = it4.at("key");
340 auto key = gdk_keyval_from_name(keyname.c_str());
341 auto mod = static_cast<GdkModifierType>(it4.at("mod").get<int>());
342 keys[action].back().emplace_back(key, mod);
343 }
344 }
345 }
346 }
347 }
348 catch (const std::exception &e) {
349 Logger::log_warning("error loading in-tool key sequence", Logger::Domain::UNSPECIFIED, e.what());
350 }
351 catch (...) {
352 Logger::log_warning("error loading int-tool key sequence", Logger::Domain::UNSPECIFIED, "unknown error");
353 }
354 }
355 }
356
load_from_json(const json & j)357 void PartInfoPreferences::load_from_json(const json &j)
358 {
359 url = j.at("url");
360 preferred_distributor = j.at("preferred_distributor");
361 ignore_moq_gt_1 = j.at("ignore_moq_gt_1");
362 max_price_breaks = j.value("max_price_breaks", 3);
363 }
364
serialize() const365 json PartInfoPreferences::serialize() const
366 {
367 json j;
368 j["enable"] = false;
369 j["url"] = url;
370 j["preferred_distributor"] = preferred_distributor;
371 j["ignore_moq_gt_1"] = ignore_moq_gt_1;
372 j["max_price_breaks"] = max_price_breaks;
373 return j;
374 }
375
load_from_json(const json & j)376 void DigiKeyApiPreferences::load_from_json(const json &j)
377 {
378 client_id = j.at("client_id");
379 client_secret = j.at("client_secret");
380 currency = j.value("currency", "EUR");
381 site = j.value("site", "DE");
382 max_price_breaks = j.value("max_price_breaks", 3);
383 }
384
serialize() const385 json DigiKeyApiPreferences::serialize() const
386 {
387 json j;
388 j["enable"] = false;
389 j["client_id"] = client_id;
390 j["client_secret"] = client_secret;
391 j["site"] = site;
392 j["currency"] = currency;
393 j["max_price_breaks"] = max_price_breaks;
394 return j;
395 }
396
serialize() const397 json ActionBarPreferences::serialize() const
398 {
399 json j;
400 j["enable"] = enable;
401 j["remember"] = remember;
402 j["show_in_tool"] = show_in_tool;
403 return j;
404 }
405
load_from_json(const json & j)406 void ActionBarPreferences::load_from_json(const json &j)
407 {
408 enable = j.value("enable", true);
409 remember = j.value("remember", true);
410 show_in_tool = j.value("show_in_tool", true);
411 }
412
serialize() const413 json MousePreferences::serialize() const
414 {
415 json j;
416 j["switch_layers"] = switch_layers;
417 j["switch_sheets"] = switch_sheets;
418 j["drag_polygon_edges"] = drag_polygon_edges;
419 j["drag_to_move"] = drag_to_move;
420 return j;
421 }
422
load_from_json(const json & j)423 void MousePreferences::load_from_json(const json &j)
424 {
425 switch_layers = j.value("switch_layers", true);
426 switch_sheets = j.value("switch_sheets", true);
427 drag_polygon_edges = j.value("drag_polygon_edges", true);
428 drag_to_move = j.value("drag_to_move", true);
429 }
430
serialize() const431 json Preferences::serialize() const
432 {
433 json j;
434 j["canvas_layer"] = canvas_layer.serialize();
435 j["canvas_non_layer"] = canvas_non_layer.serialize();
436 j["schematic"] = schematic.serialize();
437 j["key_sequences"] = key_sequences.serialize();
438 j["in_tool_key_sequences"] = in_tool_key_sequences.serialize();
439 j["board"] = board.serialize();
440 j["zoom"] = zoom.serialize();
441 j["capture_output"] = capture_output;
442 j["stock_info_provider"] = stock_info_provider_lut.lookup_reverse(stock_info_provider);
443 j["partinfo"] = partinfo.serialize();
444 j["digikey_api"] = digikey_api.serialize();
445 j["action_bar"] = action_bar.serialize();
446 j["mouse"] = mouse.serialize();
447 j["show_pull_request_tools"] = show_pull_request_tools;
448 j["hud_debug"] = hud_debug;
449 return j;
450 }
451
save()452 void Preferences::save()
453 {
454 std::string prefs_filename = get_preferences_filename();
455
456 json j = serialize();
457 save_json_to_file(prefs_filename, j);
458 }
459
load_from_json(const json & j)460 void Preferences::load_from_json(const json &j)
461 {
462
463 canvas_layer.load_from_json(j.at("canvas_layer"));
464 canvas_non_layer.load_from_json(j.at("canvas_non_layer"));
465 if (j.count("schematic"))
466 schematic.load_from_json(j.at("schematic"));
467 if (j.count("board"))
468 board.load_from_json(j.at("board"));
469 if (j.count("zoom"))
470 zoom.load_from_json(j.at("zoom"));
471 if (j.count("key_sequences"))
472 key_sequences.load_from_json(j.at("key_sequences"));
473 if (j.count("in_tool_key_sequences"))
474 in_tool_key_sequences.load_from_json(j.at("in_tool_key_sequences"));
475 if (j.count("action_bar"))
476 action_bar.load_from_json(j.at("action_bar"));
477 if (j.count("mouse"))
478 mouse.load_from_json(j.at("mouse"));
479 key_sequences.append_from_json(json_from_resource("/org/horizon-eda/horizon/imp/keys_default.json"));
480 in_tool_key_sequences.append_from_json(
481 json_from_resource("/org/horizon-eda/horizon/imp/in_tool_keys_default.json"));
482 capture_output = j.value("capture_output", capture_output_default);
483 show_pull_request_tools = j.value("show_pull_request_tools", false);
484 hud_debug = j.value("hud_debug", false);
485 stock_info_provider = stock_info_provider_lut.lookup(j.value("stock_info_provider", "none"));
486 if (j.count("partinfo"))
487 partinfo.load_from_json(j.at("partinfo"));
488 if (j.count("digikey_api"))
489 digikey_api.load_from_json(j.at("digikey_api"));
490 }
491
load()492 void Preferences::load()
493 {
494 std::string prefs_filename = get_preferences_filename();
495 std::string prefs_filename_old = Glib::build_filename(get_config_dir(), "imp-prefs.json");
496 if (!Glib::file_test(prefs_filename, Glib::FILE_TEST_IS_REGULAR)) {
497 if (Glib::file_test(prefs_filename_old, Glib::FILE_TEST_IS_REGULAR)) {
498 json j = load_json_from_file(prefs_filename_old);
499 load_from_json(j);
500 }
501 else {
502 load_default();
503 }
504 }
505 else {
506 json j = load_json_from_file(prefs_filename);
507 load_from_json(j);
508 }
509 s_signal_changed.emit();
510 }
511 } // namespace horizon
512