diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index ad4cd4758..c66106a30 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -655,6 +655,7 @@ target_sources(app-lib PRIVATE ui/keyboard_shortcuts.cpp ui/layer_frame_comboboxes.cpp ui/layout.cpp + ui/layouts.cpp ui/layout_selector.cpp ui/main_menu_bar.cpp ui/main_window.cpp diff --git a/src/app/ui/layout.cpp b/src/app/ui/layout.cpp index e0883a46f..cb3541687 100644 --- a/src/app/ui/layout.cpp +++ b/src/app/ui/layout.cpp @@ -28,7 +28,7 @@ namespace app { -static void save_dock_layout(TiXmlElement& elem, const Dock* dock) +static void save_dock_layout(TiXmlElement* elem, const Dock* dock) { for (const auto child : dock->children()) { const int side = dock->whichSideChildIsDocked(child); @@ -52,7 +52,7 @@ static void save_dock_layout(TiXmlElement& elem, const Dock* dock) if (!sideStr.empty()) childElem.SetAttribute("side", sideStr); - save_dock_layout(childElem, subdock); + save_dock_layout(&childElem, subdock); } else { // Set the widget ID as the element name, e.g. , @@ -66,7 +66,7 @@ static void save_dock_layout(TiXmlElement& elem, const Dock* dock) childElem.SetAttribute("height", size.h); } - elem.InsertEndChild(childElem); + elem->InsertEndChild(childElem); } } @@ -136,40 +136,36 @@ static void load_dock_layout(const TiXmlElement* elem, Dock* dock) } } -Layout::Layout(const std::string& name, const Dock* dock) : m_name(name) +// static +LayoutPtr Layout::MakeFromXmlElement(const TiXmlElement* layoutElem) { - XmlDocumentRef doc(new TiXmlDocument()); - TiXmlElement layoutsElem("layouts"); - { - TiXmlElement layoutElem("layout"); - layoutElem.SetAttribute("name", name); + auto layout = std::make_shared(); + if (auto name = layoutElem->Attribute("name")) + layout->m_name = name; - save_dock_layout(layoutElem, dock); + layout->m_elem.reset(layoutElem->Clone()->ToElement()); + return layout; +} - layoutsElem.InsertEndChild(layoutElem); - } +// static +LayoutPtr Layout::MakeFromDock(const std::string& name, const Dock* dock) +{ + auto layout = std::make_shared(); + layout->m_name = name; - TiXmlDeclaration declaration("1.0", "utf-8", ""); - doc->InsertEndChild(declaration); - doc->InsertEndChild(layoutsElem); + layout->m_elem = std::make_unique("layout"); + layout->m_elem->SetAttribute("name", name); + save_dock_layout(layout->m_elem.get(), dock); - std::stringstream s; - s << *doc; - m_data = s.str(); + return layout; } bool Layout::loadLayout(Dock* dock) const { - XmlDocumentRef doc(new TiXmlDocument); - doc->Parse(m_data.c_str(), 0, TIXML_DEFAULT_ENCODING); - TiXmlHandle handle(doc.get()); - - TiXmlElement* layoutElem = handle.FirstChild("layouts").FirstChild("layout").ToElement(); - - if (!layoutElem) + if (!m_elem) return false; - TiXmlElement* elem = layoutElem->FirstChildElement(); + TiXmlElement* elem = m_elem->FirstChildElement(); while (elem) { load_dock_layout(elem, dock); elem = elem->NextSiblingElement(); diff --git a/src/app/ui/layout.h b/src/app/ui/layout.h index 8a53a3dfc..cd3844a8d 100644 --- a/src/app/ui/layout.h +++ b/src/app/ui/layout.h @@ -11,25 +11,29 @@ #include #include +class TiXmlElement; + namespace app { class Dock; -class Layout { +class Layout; +using LayoutPtr = std::shared_ptr; + +class Layout final { public: - Layout(const std::string& name, const Dock* dock); + static LayoutPtr MakeFromXmlElement(const TiXmlElement* layoutElem); + static LayoutPtr MakeFromDock(const std::string& name, const Dock* dock); const std::string& name() const { return m_name; } - const std::string& data() const { return m_data; } + const TiXmlElement* xmlElement() const { return m_elem.get(); } bool loadLayout(Dock* dock) const; private: std::string m_name; - std::string m_data; + std::unique_ptr m_elem; }; -using LayoutPtr = std::shared_ptr; - } // namespace app #endif diff --git a/src/app/ui/layout_selector.cpp b/src/app/ui/layout_selector.cpp index ed5736518..de57abed6 100644 --- a/src/app/ui/layout_selector.cpp +++ b/src/app/ui/layout_selector.cpp @@ -128,13 +128,13 @@ public: case LayoutId::SAVE_LAYOUT: { gen::NewLayout window; window.name()->setText( - fmt::format("{} ({})", window.name()->text(), m_selector->m_layouts.size())); + fmt::format("{} ({})", window.name()->text(), m_selector->m_layouts.size() + 1)); window.openWindowInForeground(); if (window.closer() == window.ok()) { - auto layout = std::make_shared(window.name()->text(), win->customizableDock()); + auto layout = Layout::MakeFromDock(window.name()->text(), win->customizableDock()); - m_selector->addLayout(std::move(layout)); + m_selector->addLayout(layout); } break; } @@ -198,7 +198,7 @@ void LayoutSelector::addLayout(const LayoutPtr& layout) auto item = m_comboBox.addItem( new LayoutItem(this, LayoutItem::USER_DEFINED, layout->name(), layout)); - m_layouts.push_back(layout); + m_layouts.addLayout(layout); m_comboBox.setSelectedItemIndex(item); } diff --git a/src/app/ui/layout_selector.h b/src/app/ui/layout_selector.h index e9fedc980..6d91af9cb 100644 --- a/src/app/ui/layout_selector.h +++ b/src/app/ui/layout_selector.h @@ -11,6 +11,7 @@ #include "app/ui/dockable.h" #include "app/ui/icon_button.h" #include "app/ui/layout.h" +#include "app/ui/layouts.h" #include "ui/animated_widget.h" #include "ui/box.h" #include "ui/combobox.h" @@ -61,7 +62,7 @@ private: IconButton m_button; gfx::Size m_startSize; gfx::Size m_endSize; - std::vector m_layouts; + Layouts m_layouts; }; } // namespace app diff --git a/src/app/ui/layouts.cpp b/src/app/ui/layouts.cpp new file mode 100644 index 000000000..9089608a2 --- /dev/null +++ b/src/app/ui/layouts.cpp @@ -0,0 +1,79 @@ +// Aseprite +// Copyright (c) 2022 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#include "app/ui/layouts.h" + +#include "app/resource_finder.h" +#include "app/xml_document.h" +#include "app/xml_exception.h" +#include "base/fs.h" + +#include + +namespace app { + +Layouts::Layouts() +{ + try { + std::string fn = m_userLayoutsFilename = UserLayoutsFilename(); + if (base::is_file(fn)) + load(fn); + } + catch (const std::exception& ex) { + LOG(ERROR, "LAY: Error loading user layouts: %s\n", ex.what()); + } +} + +Layouts::~Layouts() +{ + if (!m_userLayoutsFilename.empty()) + save(m_userLayoutsFilename); +} + +void Layouts::addLayout(const LayoutPtr& layout) +{ + m_layouts.push_back(layout); +} + +void Layouts::load(const std::string& fn) +{ + XmlDocumentRef doc = app::open_xml(fn); + TiXmlHandle handle(doc.get()); + TiXmlElement* layoutElem = handle.FirstChild("layouts").FirstChild("layout").ToElement(); + + while (layoutElem) { + m_layouts.push_back(Layout::MakeFromXmlElement(layoutElem)); + layoutElem = layoutElem->NextSiblingElement(); + } +} + +void Layouts::save(const std::string& fn) const +{ + XmlDocumentRef doc(new TiXmlDocument()); + TiXmlElement layoutsElem("layouts"); + + for (const auto& layout : m_layouts) + layoutsElem.InsertEndChild(*layout->xmlElement()); + + TiXmlDeclaration declaration("1.0", "utf-8", ""); + doc->InsertEndChild(declaration); + doc->InsertEndChild(layoutsElem); + save_xml(doc, fn); +} + +// static +std::string Layouts::UserLayoutsFilename() +{ + ResourceFinder rf; + rf.includeUserDir("user.aseprite-layouts"); + return rf.getFirstOrCreateDefault(); +} + +} // namespace app diff --git a/src/app/ui/layouts.h b/src/app/ui/layouts.h new file mode 100644 index 000000000..6fd5e8bfb --- /dev/null +++ b/src/app/ui/layouts.h @@ -0,0 +1,44 @@ +// Aseprite +// Copyright (c) 2022 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifndef APP_UI_LAYOUTS_H_INCLUDED +#define APP_UI_LAYOUTS_H_INCLUDED +#pragma once + +#include "app/ui/layout.h" + +#include +#include + +namespace app { + +class Layouts { +public: + Layouts(); + ~Layouts(); + + size_t size() const { return m_layouts.size(); } + + void addLayout(const LayoutPtr& layout); + + // To iterate layouts + using List = std::vector; + using iterator = List::iterator; + iterator begin() { return m_layouts.begin(); } + iterator end() { return m_layouts.end(); } + +private: + void load(const std::string& fn); + void save(const std::string& fn) const; + static std::string UserLayoutsFilename(); + + List m_layouts; + std::string m_userLayoutsFilename; +}; + +} // namespace app + +#endif