| 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 |  |