1 | // Aseprite |
2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "app/doc.h" |
13 | #include "app/ini_file.h" |
14 | #include "app/pref/preferences.h" |
15 | #include "app/resource_finder.h" |
16 | #include "app/tools/ink.h" |
17 | #include "app/tools/tool.h" |
18 | #include "base/fs.h" |
19 | #include "doc/sprite.h" |
20 | #include "os/system.h" |
21 | #include "ui/system.h" |
22 | |
23 | namespace app { |
24 | |
25 | static Preferences* singleton = nullptr; |
26 | |
27 | // static |
28 | Preferences& Preferences::instance() |
29 | { |
30 | #ifdef _DEBUG |
31 | // Preferences can be used only from the main UI thread. In other |
32 | // case access to std::map<> could crash the program. |
33 | if (ui::UISystem::instance()) |
34 | ui::assert_ui_thread(); |
35 | #endif |
36 | |
37 | ASSERT(singleton); |
38 | return *singleton; |
39 | } |
40 | |
41 | Preferences::Preferences() |
42 | : app::gen::GlobalPref("" ) |
43 | { |
44 | ASSERT(!singleton); |
45 | singleton = this; |
46 | |
47 | // Main configuration file |
48 | const std::string fn = main_config_filename(); |
49 | ASSERT(!fn.empty()); |
50 | |
51 | // The first time we execute the program, the configuration file |
52 | // doesn't exist. |
53 | const bool firstTime = (!base::is_file(fn)); |
54 | |
55 | load(); |
56 | |
57 | // Create a connection with the default RgbMapAlgorithm preferences |
58 | // to change the default algorithm in the "doc" layer. |
59 | quantization.rgbmapAlgorithm.AfterChange.connect( |
60 | [](const doc::RgbMapAlgorithm& newValue){ |
61 | doc::Sprite::SetDefaultRgbMapAlgorithm(newValue); |
62 | }); |
63 | doc::Sprite::SetDefaultRgbMapAlgorithm(quantization.rgbmapAlgorithm()); |
64 | |
65 | // Create a connection with the default document preferences grid |
66 | // bounds to sync the default grid bounds for new sprites in the |
67 | // "doc" layer. |
68 | auto& defPref = document(nullptr); |
69 | defPref.grid.bounds.AfterChange.connect( |
70 | [](const gfx::Rect& newValue){ |
71 | doc::Sprite::SetDefaultGridBounds(newValue); |
72 | }); |
73 | doc::Sprite::SetDefaultGridBounds(defPref.grid.bounds()); |
74 | |
75 | // Reset confusing defaults for a new instance of the program. |
76 | defPref.grid.snap(false); |
77 | if (selection.mode() != gen::SelectionMode::DEFAULT && |
78 | selection.mode() != gen::SelectionMode::ADD) { |
79 | selection.mode(gen::SelectionMode::DEFAULT); |
80 | } |
81 | |
82 | // Hide the menu bar depending on: |
83 | // 1. this is the first run of the program |
84 | // 2. the native menu bar is available |
85 | if (firstTime && |
86 | os::instance() && |
87 | os::instance()->menus()) { |
88 | general.showMenuBar(false); |
89 | } |
90 | } |
91 | |
92 | Preferences::~Preferences() |
93 | { |
94 | // Don't save preferences, this must be done by the client of the |
95 | // Preferences instance (the App class). |
96 | //save(); |
97 | |
98 | for (auto& pair : m_tools) |
99 | delete pair.second; |
100 | |
101 | for (auto& pair : m_docs) |
102 | delete pair.second; |
103 | |
104 | ASSERT(singleton == this); |
105 | singleton = nullptr; |
106 | } |
107 | |
108 | void Preferences::load() |
109 | { |
110 | app::gen::GlobalPref::load(); |
111 | } |
112 | |
113 | void Preferences::save() |
114 | { |
115 | #ifdef _DEBUG |
116 | if (ui::UISystem::instance()) |
117 | ui::assert_ui_thread(); |
118 | #endif |
119 | app::gen::GlobalPref::save(); |
120 | |
121 | for (auto& pair : m_tools) |
122 | pair.second->save(); |
123 | |
124 | for (auto& pair : m_docs) |
125 | serializeDocPref(pair.first, pair.second, true); |
126 | |
127 | flush_config_file(); |
128 | } |
129 | |
130 | bool Preferences::isSet(OptionBase& opt) const |
131 | { |
132 | return (get_config_string(opt.section(), opt.id(), nullptr) != nullptr); |
133 | } |
134 | |
135 | ToolPreferences& Preferences::tool(tools::Tool* tool) |
136 | { |
137 | ASSERT(tool != NULL); |
138 | |
139 | auto it = m_tools.find(tool->getId()); |
140 | if (it != m_tools.end()) { |
141 | return *it->second; |
142 | } |
143 | else { |
144 | std::string section = std::string("tool." ) + tool->getId(); |
145 | ToolPreferences* toolPref = new ToolPreferences(section); |
146 | |
147 | // Default size for eraser, blur, etc. |
148 | if (tool->getInk(0)->isEraser() || |
149 | tool->getInk(0)->isEffect()) { |
150 | toolPref->brush.size.setDefaultValue(8); |
151 | } |
152 | |
153 | m_tools[tool->getId()] = toolPref; |
154 | toolPref->load(); |
155 | return *toolPref; |
156 | } |
157 | } |
158 | |
159 | DocumentPreferences& Preferences::document(const Doc* doc) |
160 | { |
161 | auto it = m_docs.find(doc); |
162 | if (it != m_docs.end()) { |
163 | return *it->second; |
164 | } |
165 | else { |
166 | // Create a new DocumentPreferences for the given "doc" pointer |
167 | DocumentPreferences* docPref = new DocumentPreferences("" ); |
168 | m_docs[doc] = docPref; |
169 | |
170 | // Setup the preferences of this document with the default ones |
171 | // (these preferences will be overwritten in the next statement |
172 | // loading the preferences from the .ini file of this doc) |
173 | if (doc) { |
174 | // The default preferences for this document are the current |
175 | // defaults for (document=nullptr). |
176 | DocumentPreferences& defPref = this->document(nullptr); |
177 | *docPref = defPref; |
178 | |
179 | // Default values for symmetry |
180 | docPref->symmetry.xAxis.setValueAndDefault(doc->sprite()->width()/2); |
181 | docPref->symmetry.yAxis.setValueAndDefault(doc->sprite()->height()/2); |
182 | } |
183 | |
184 | // Load specific settings of this document |
185 | serializeDocPref(doc, docPref, false); |
186 | |
187 | // Turn off snap to grid setting (it's confusing for most of the |
188 | // users to load this setting). |
189 | docPref->grid.snap.setValueAndDefault(false); |
190 | |
191 | return *docPref; |
192 | } |
193 | } |
194 | |
195 | void Preferences::resetToolPreferences(tools::Tool* tool) |
196 | { |
197 | if (tool->prefAlreadyResetFromScript()) |
198 | return; |
199 | tool->markPrefAlreadyResetFromScript(); |
200 | |
201 | auto it = m_tools.find(tool->getId()); |
202 | if (it != m_tools.end()) |
203 | m_tools.erase(it); |
204 | |
205 | std::string section = std::string("tool." ) + tool->getId(); |
206 | del_config_section(section.c_str()); |
207 | |
208 | // TODO improve this, if we add new sections in pref.xml we have to |
209 | // update this manually :( |
210 | del_config_section((section + ".brush" ).c_str()); |
211 | del_config_section((section + ".spray" ).c_str()); |
212 | del_config_section((section + ".floodfill" ).c_str()); |
213 | } |
214 | |
215 | void Preferences::removeDocument(Doc* doc) |
216 | { |
217 | ASSERT(doc); |
218 | |
219 | auto it = m_docs.find(doc); |
220 | if (it != m_docs.end()) { |
221 | serializeDocPref(it->first, it->second, true); |
222 | delete it->second; |
223 | m_docs.erase(it); |
224 | } |
225 | } |
226 | |
227 | void Preferences::onRemoveDocument(Doc* doc) |
228 | { |
229 | removeDocument(doc); |
230 | } |
231 | |
232 | std::string Preferences::docConfigFileName(const Doc* doc) |
233 | { |
234 | if (!doc) |
235 | return "" ; |
236 | |
237 | ResourceFinder rf; |
238 | std::string fn = doc->filename(); |
239 | for (size_t i=0; i<fn.size(); ++i) { |
240 | if (fn[i] == ' ' || fn[i] == '/' || fn[i] == '\\' || fn[i] == ':' || fn[i] == '.') { |
241 | fn[i] = '-'; |
242 | } |
243 | } |
244 | rf.includeUserDir(("files/" + fn + ".ini" ).c_str()); |
245 | return rf.getFirstOrCreateDefault(); |
246 | } |
247 | |
248 | void Preferences::serializeDocPref(const Doc* doc, app::DocumentPreferences* docPref, bool save) |
249 | { |
250 | bool flush_config = false; |
251 | |
252 | if (doc) { |
253 | // We do nothing if the document isn't associated to a file and we |
254 | // want to save its specific preferences. |
255 | if (save && !doc->isAssociatedToFile()) |
256 | return; |
257 | |
258 | // We always push a new configuration file in the stack to avoid |
259 | // modifying the default preferences when a document in "doc" is |
260 | // specified. |
261 | push_config_state(); |
262 | if (doc->isAssociatedToFile()) { |
263 | set_config_file(docConfigFileName(doc).c_str()); |
264 | flush_config = true; |
265 | } |
266 | } |
267 | |
268 | if (save) { |
269 | docPref->save(); |
270 | } |
271 | else { |
272 | // Load default preferences, or preferences from .ini file. |
273 | docPref->load(); |
274 | } |
275 | |
276 | if (doc) { |
277 | if (save && flush_config) |
278 | flush_config_file(); |
279 | |
280 | pop_config_state(); |
281 | } |
282 | } |
283 | |
284 | } // namespace app |
285 | |