Save/Load user defined layouts in new user.aseprite-layouts file

And now we store the TiXmlElement for each Layout, instead of
converting from/to text back and forth.
This commit is contained in:
David Capello 2022-08-25 17:54:59 -03:00
parent dc51ca25e0
commit 6b0a1028fb
7 changed files with 162 additions and 37 deletions

View File

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

View File

@ -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. <timeline />,
@ -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<Layout>();
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>();
layout->m_name = name;
TiXmlDeclaration declaration("1.0", "utf-8", "");
doc->InsertEndChild(declaration);
doc->InsertEndChild(layoutsElem);
layout->m_elem = std::make_unique<TiXmlElement>("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();

View File

@ -11,25 +11,29 @@
#include <memory>
#include <string>
class TiXmlElement;
namespace app {
class Dock;
class Layout {
class Layout;
using LayoutPtr = std::shared_ptr<Layout>;
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<TiXmlElement> m_elem;
};
using LayoutPtr = std::shared_ptr<Layout>;
} // namespace app
#endif

View File

@ -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<Layout>(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);
}

View File

@ -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<LayoutPtr> m_layouts;
Layouts m_layouts;
};
} // namespace app

79
src/app/ui/layouts.cpp Normal file
View File

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

44
src/app/ui/layouts.h Normal file
View File

@ -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 <string>
#include <vector>
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<LayoutPtr>;
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