mirror of
https://github.com/aseprite/aseprite.git
synced 2024-12-27 03:16:58 +00:00
Implement Add/Uninstall extension (#1403)
- Added libarchive to uncompress .zip extensions - Moved ListItem painting code to styles because we needed a selected+disabled state.
This commit is contained in:
parent
950955787f
commit
a9e688989f
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -48,3 +48,6 @@
|
||||
[submodule "third_party/json"]
|
||||
path = third_party/json
|
||||
url = https://github.com/aseprite/json.git
|
||||
[submodule "third_party/libarchive"]
|
||||
path = third_party/libarchive
|
||||
url = https://github.com/aseprite/libarchive.git
|
||||
|
@ -890,5 +890,14 @@
|
||||
<border part="simple_color_border" />
|
||||
<border part="simple_color_selected" state="selected" />
|
||||
</style>
|
||||
<style id="list_item" border="1">
|
||||
<background color="listitem_normal_face" />
|
||||
<background color="listitem_selected_face" state="selected" />
|
||||
<background color="face" state="disabled" />
|
||||
<background color="listitem_selected_face" state="selected disabled" />
|
||||
<text color="listitem_normal_text" align="left middle" x="1" />
|
||||
<text color="listitem_selected_text" align="left middle" x="1" state="selected" />
|
||||
<text color="disabled" align="left middle" x="1" state="disabled" />
|
||||
</style>
|
||||
</styles>
|
||||
</theme>
|
||||
|
@ -392,7 +392,7 @@ undo_allow_nonlinear_history = Allow non-linear history
|
||||
available_themes = Available Themes
|
||||
select_theme = &Select
|
||||
open_theme_folder = Open &Folder
|
||||
new_extension = New
|
||||
add_extension = Add Extension
|
||||
disable_extension = Disable
|
||||
uninstall_extension = Uninstall
|
||||
open_extension_folder = Open &Folder
|
||||
|
@ -277,7 +277,7 @@
|
||||
<listbox id="extensions_list" />
|
||||
</view>
|
||||
<hbox>
|
||||
<button id="new_extension" text="@.new_extension" minwidth="60" />
|
||||
<button id="add_extension" text="@.add_extension" minwidth="60" />
|
||||
<boxfiller />
|
||||
<button id="disable_extension" text="@.disable_extension" minwidth="60" />
|
||||
<button id="uninstall_extension" text="@.uninstall_extension" minwidth="60" />
|
||||
|
@ -527,6 +527,70 @@ ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
```
|
||||
|
||||
# [libarchive](http://www.libarchive.org/)
|
||||
|
||||
```
|
||||
The libarchive distribution as a whole is Copyright by Tim Kientzle
|
||||
and is subject to the copyright notice reproduced at the bottom of
|
||||
this file.
|
||||
|
||||
Each individual file in this distribution should have a clear
|
||||
copyright/licensing statement at the beginning of the file. If any do
|
||||
not, please let me know and I will rectify it. The following is
|
||||
intended to summarize the copyright status of the individual files;
|
||||
the actual statements in the files are controlling.
|
||||
|
||||
* Except as listed below, all C sources (including .c and .h files)
|
||||
and documentation files are subject to the copyright notice reproduced
|
||||
at the bottom of this file.
|
||||
|
||||
* The following source files are also subject in whole or in part to
|
||||
a 3-clause UC Regents copyright; please read the individual source
|
||||
files for details:
|
||||
libarchive/archive_entry.c
|
||||
libarchive/archive_read_support_filter_compress.c
|
||||
libarchive/archive_write_add_filter_compress.c
|
||||
libarchive/mtree.5
|
||||
|
||||
* The following source files are in the public domain:
|
||||
libarchive/archive_getdate.c
|
||||
|
||||
* The build files---including Makefiles, configure scripts,
|
||||
and auxiliary scripts used as part of the compile process---have
|
||||
widely varying licensing terms. Please check individual files before
|
||||
distributing them to see if those restrictions apply to you.
|
||||
|
||||
I intend for all new source code to use the license below and hope over
|
||||
time to replace code with other licenses with new implementations that
|
||||
do use the license below. The varying licensing of the build scripts
|
||||
seems to be an unavoidable mess.
|
||||
|
||||
|
||||
Copyright (c) 2003-2009 <author(s)>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer
|
||||
in this position and unchanged.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
```
|
||||
|
||||
# [libjpeg](http://www.ijg.org/)
|
||||
|
||||
```
|
||||
|
@ -541,7 +541,8 @@ target_link_libraries(app-lib
|
||||
${ZLIB_LIBRARIES}
|
||||
${FREETYPE_LIBRARIES}
|
||||
${HARFBUZZ_LIBRARIES}
|
||||
taocpp-json)
|
||||
taocpp-json
|
||||
archive_static)
|
||||
|
||||
if(ENABLE_SCRIPTING)
|
||||
target_link_libraries(app-lib script-lib)
|
||||
|
@ -10,8 +10,10 @@
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/commands/command.h"
|
||||
#include "app/console.h"
|
||||
#include "app/context.h"
|
||||
#include "app/extensions.h"
|
||||
#include "app/file_selector.h"
|
||||
#include "app/ini_file.h"
|
||||
#include "app/launcher.h"
|
||||
#include "app/pref/preferences.h"
|
||||
@ -72,23 +74,45 @@ class OptionsWindow : public app::gen::Options {
|
||||
ExtensionItem(Extension* extension)
|
||||
: ListItem(extension->displayName())
|
||||
, m_extension(extension) {
|
||||
setEnabled(extension->isEnabled());
|
||||
}
|
||||
|
||||
bool isEnabled() const { return m_extension->isEnabled(); }
|
||||
bool isInstalled() const { return m_extension->isInstalled(); }
|
||||
bool canBeDisabled() const { return m_extension->canBeDisabled(); }
|
||||
bool canBeUninstalled() const { return m_extension->canBeUninstalled(); }
|
||||
bool isEnabled() const {
|
||||
ASSERT(m_extension);
|
||||
return m_extension->isEnabled();
|
||||
}
|
||||
|
||||
bool isInstalled() const {
|
||||
ASSERT(m_extension);
|
||||
return m_extension->isInstalled();
|
||||
}
|
||||
|
||||
bool canBeDisabled() const {
|
||||
ASSERT(m_extension);
|
||||
return m_extension->canBeDisabled();
|
||||
}
|
||||
|
||||
bool canBeUninstalled() const {
|
||||
ASSERT(m_extension);
|
||||
return m_extension->canBeUninstalled();
|
||||
}
|
||||
|
||||
void enable(bool state) {
|
||||
ASSERT(m_extension);
|
||||
m_extension->enable(state);
|
||||
|
||||
setEnabled(m_extension->isEnabled());
|
||||
}
|
||||
|
||||
void uninstall() {
|
||||
ASSERT(m_extension);
|
||||
ASSERT(canBeUninstalled());
|
||||
m_extension->uninstall();
|
||||
m_extension = nullptr;
|
||||
}
|
||||
|
||||
void openFolder() const {
|
||||
ASSERT(m_extension);
|
||||
app::launcher::open_folder(m_extension->path());
|
||||
}
|
||||
|
||||
@ -288,7 +312,7 @@ public:
|
||||
|
||||
// Extensions buttons
|
||||
extensionsList()->Change.connect(base::Bind<void>(&OptionsWindow::onExtensionChange, this));
|
||||
newExtension()->Click.connect(base::Bind<void>(&OptionsWindow::onNewExtension, this));
|
||||
addExtension()->Click.connect(base::Bind<void>(&OptionsWindow::onAddExtension, this));
|
||||
disableExtension()->Click.connect(base::Bind<void>(&OptionsWindow::onDisableExtension, this));
|
||||
uninstallExtension()->Click.connect(base::Bind<void>(&OptionsWindow::onUninstallExtension, this));
|
||||
openExtensionFolder()->Click.connect(base::Bind<void>(&OptionsWindow::onOpenExtensionFolder, this));
|
||||
@ -642,8 +666,28 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void onNewExtension() {
|
||||
// TODO open dialog to select a .zip file with the extension and uncompress it in the user folder
|
||||
void onAddExtension() {
|
||||
FileSelectorFiles filename;
|
||||
if (!app::show_file_selector(
|
||||
"Add Extension", "", "zip",
|
||||
FileSelectorType::Open, filename))
|
||||
return;
|
||||
|
||||
ASSERT(!filename.empty());
|
||||
|
||||
try {
|
||||
Extension* extension =
|
||||
App::instance()->extensions().installCompressedExtension(filename.front());
|
||||
|
||||
// Add the new extension in the listbox
|
||||
ExtensionItem* item = new ExtensionItem(extension);
|
||||
extensionsList()->addChild(item);
|
||||
extensionsList()->selectChild(item);
|
||||
extensionsList()->layout();
|
||||
}
|
||||
catch (std::exception& ex) {
|
||||
Console::showException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
void onDisableExtension() {
|
||||
@ -656,9 +700,27 @@ private:
|
||||
|
||||
void onUninstallExtension() {
|
||||
ExtensionItem* item = dynamic_cast<ExtensionItem*>(extensionsList()->getSelectedChild());
|
||||
if (item) {
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
if (ui::Alert::show(
|
||||
"Warning"
|
||||
"<<Do you really want to uninstall '%s' extension?"
|
||||
"||&Yes||&No",
|
||||
item->text().c_str()) != 1)
|
||||
return;
|
||||
|
||||
try {
|
||||
item->uninstall();
|
||||
onExtensionChange();
|
||||
|
||||
// Remove the item from the list
|
||||
extensionsList()->removeChild(item);
|
||||
extensionsList()->layout();
|
||||
|
||||
item->deferDelete();
|
||||
}
|
||||
catch (std::exception& ex) {
|
||||
Console::showException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,14 +10,139 @@
|
||||
|
||||
#include "app/extensions.h"
|
||||
|
||||
#include "app/ini_file.h"
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/resource_finder.h"
|
||||
#include "base/exception.h"
|
||||
#include "base/file_handle.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/unique_ptr.h"
|
||||
|
||||
#include "archive.h"
|
||||
#include "archive_entry.h"
|
||||
#include "tao/json.hpp"
|
||||
|
||||
#include <queue>
|
||||
|
||||
namespace app {
|
||||
|
||||
namespace {
|
||||
|
||||
class ReadArchive {
|
||||
public:
|
||||
ReadArchive(const std::string& filename)
|
||||
: m_arch(nullptr), m_open(false) {
|
||||
m_arch = archive_read_new();
|
||||
archive_read_support_format_zip(m_arch);
|
||||
|
||||
m_file = base::open_file(filename, "rb");
|
||||
if (!m_file)
|
||||
throw base::Exception("Error loading file %s",
|
||||
filename.c_str());
|
||||
|
||||
int err;
|
||||
if ((err = archive_read_open_FILE(m_arch, m_file.get())))
|
||||
throw base::Exception("Error uncompressing extension\n%s (%d)",
|
||||
archive_error_string(m_arch), err);
|
||||
|
||||
m_open = true;
|
||||
}
|
||||
|
||||
~ReadArchive() {
|
||||
if (m_arch) {
|
||||
if (m_open)
|
||||
archive_read_close(m_arch);
|
||||
archive_read_free(m_arch);
|
||||
}
|
||||
}
|
||||
|
||||
archive_entry* readEntry() {
|
||||
archive_entry* entry;
|
||||
int err = archive_read_next_header(m_arch, &entry);
|
||||
|
||||
if (err == ARCHIVE_EOF)
|
||||
return nullptr;
|
||||
|
||||
if (err != ARCHIVE_OK)
|
||||
throw base::Exception("Error uncompressing extension\n%s",
|
||||
archive_error_string(m_arch));
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
int copyDataTo(archive* out) {
|
||||
const void* buf;
|
||||
size_t size;
|
||||
int64_t offset;
|
||||
for (;;) {
|
||||
int err = archive_read_data_block(m_arch, &buf, &size, &offset);
|
||||
if (err == ARCHIVE_EOF)
|
||||
break;
|
||||
if (err != ARCHIVE_OK)
|
||||
return err;
|
||||
|
||||
err = archive_write_data_block(out, buf, size, offset);
|
||||
if (err != ARCHIVE_OK) {
|
||||
throw base::Exception("Error writing data blocks\n%s (%d)",
|
||||
archive_error_string(out), err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
return ARCHIVE_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
base::FileHandle m_file;
|
||||
archive* m_arch;
|
||||
bool m_open;
|
||||
};
|
||||
|
||||
class WriteArchive {
|
||||
public:
|
||||
WriteArchive(const std::string& outputDir)
|
||||
: m_arch(nullptr)
|
||||
, m_open(false)
|
||||
, m_outputDir(outputDir) {
|
||||
m_arch = archive_write_disk_new();
|
||||
m_open = true;
|
||||
}
|
||||
|
||||
~WriteArchive() {
|
||||
if (m_arch) {
|
||||
if (m_open)
|
||||
archive_write_close(m_arch);
|
||||
archive_write_free(m_arch);
|
||||
}
|
||||
}
|
||||
|
||||
void writeEntry(ReadArchive& in, archive_entry* entry) {
|
||||
const std::string origFn = archive_entry_pathname(entry);
|
||||
const std::string fullFn = base::join_path(m_outputDir, origFn);
|
||||
archive_entry_set_pathname(entry, fullFn.c_str());
|
||||
|
||||
LOG("EXT: Uncompressing file <%s> to <%s>\n",
|
||||
origFn.c_str(), fullFn.c_str());
|
||||
|
||||
int err = archive_write_header(m_arch, entry);
|
||||
if (err != ARCHIVE_OK)
|
||||
throw base::Exception("Error writing file into disk\n%s (%d)",
|
||||
archive_error_string(m_arch), err);
|
||||
|
||||
in.copyDataTo(m_arch);
|
||||
err = archive_write_finish_entry(m_arch);
|
||||
if (err != ARCHIVE_OK)
|
||||
throw base::Exception("Error saving the last part of a file entry in disk\n%s (%d)",
|
||||
archive_error_string(m_arch), err);
|
||||
}
|
||||
|
||||
private:
|
||||
archive* m_arch;
|
||||
bool m_open;
|
||||
std::string m_outputDir;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
Extension::Extension(const std::string& path,
|
||||
const std::string& name,
|
||||
const std::string& displayName,
|
||||
@ -32,13 +157,34 @@ Extension::Extension(const std::string& path,
|
||||
{
|
||||
}
|
||||
|
||||
void Extension::addTheme(const std::string& id, const std::string& path)
|
||||
{
|
||||
m_themes[id] = path;
|
||||
}
|
||||
|
||||
void Extension::addPalette(const std::string& id, const std::string& path)
|
||||
{
|
||||
m_palettes[id] = path;
|
||||
}
|
||||
|
||||
bool Extension::canBeDisabled() const
|
||||
{
|
||||
return (m_isEnabled && !isCurrentTheme());
|
||||
}
|
||||
|
||||
bool Extension::canBeUninstalled() const
|
||||
{
|
||||
return (!m_isBuiltinExtension && !isCurrentTheme());
|
||||
}
|
||||
|
||||
void Extension::enable(const bool state)
|
||||
{
|
||||
// Do nothing
|
||||
if (m_isEnabled == state)
|
||||
return;
|
||||
|
||||
// TODO save the enable/disable state on configuration or other place
|
||||
set_config_bool("extensions", m_name.c_str(), state);
|
||||
flush_config_file();
|
||||
|
||||
m_isEnabled = state;
|
||||
Enable(this, state);
|
||||
@ -49,15 +195,57 @@ void Extension::uninstall()
|
||||
if (!m_isInstalled)
|
||||
return;
|
||||
|
||||
// TODO remove files if the extension is not built-in
|
||||
ASSERT(canBeUninstalled());
|
||||
if (!canBeUninstalled())
|
||||
return;
|
||||
|
||||
TRACE("EXT: Uninstall extension '%s' from '%s'...\n",
|
||||
m_name.c_str(), m_path.c_str());
|
||||
|
||||
// Remove all files inside the extension path
|
||||
uninstallFiles(m_path);
|
||||
ASSERT(!base::is_directory(m_path));
|
||||
|
||||
m_isEnabled = false;
|
||||
m_isInstalled = false;
|
||||
Uninstall(this);
|
||||
}
|
||||
|
||||
void Extension::uninstallFiles(const std::string& path)
|
||||
{
|
||||
for (auto& item : base::list_files(path)) {
|
||||
std::string fn = base::join_path(path, item);
|
||||
if (base::is_file(fn)) {
|
||||
TRACE("EXT: Deleting file '%s'\n", fn.c_str());
|
||||
base::delete_file(fn);
|
||||
}
|
||||
else if (base::is_directory(fn)) {
|
||||
uninstallFiles(fn);
|
||||
}
|
||||
}
|
||||
|
||||
TRACE("EXT: Deleting directory '%s'\n", path.c_str());
|
||||
base::remove_directory(path);
|
||||
}
|
||||
|
||||
bool Extension::isCurrentTheme() const
|
||||
{
|
||||
auto it = m_themes.find(Preferences::instance().theme.selected.defaultValue());
|
||||
return (it != m_themes.end());
|
||||
}
|
||||
|
||||
Extensions::Extensions()
|
||||
{
|
||||
// Create and get the user extensions directory
|
||||
{
|
||||
ResourceFinder rf2;
|
||||
rf2.includeUserDir("data/extensions/.");
|
||||
m_userExtensionsPath = rf2.getFirstOrCreateDefault();
|
||||
m_userExtensionsPath = base::normalize_path(m_userExtensionsPath);
|
||||
m_userExtensionsPath = base::get_file_path(m_userExtensionsPath);
|
||||
LOG("EXT: User extensions path '%s'\n", m_userExtensionsPath.c_str());
|
||||
}
|
||||
|
||||
ResourceFinder rf;
|
||||
rf.includeDataDir("extensions");
|
||||
|
||||
@ -68,22 +256,24 @@ Extensions::Extensions()
|
||||
|
||||
if (base::is_directory(extensionsDir)) {
|
||||
for (auto fn : base::list_files(extensionsDir)) {
|
||||
auto dir = base::join_path(extensionsDir, fn);
|
||||
const auto dir = base::join_path(extensionsDir, fn);
|
||||
if (!base::is_directory(dir))
|
||||
continue;
|
||||
|
||||
const bool isBuiltinExtension =
|
||||
(m_userExtensionsPath != base::get_file_path(dir));
|
||||
|
||||
auto fullFn = base::join_path(dir, "package.json");
|
||||
fullFn = base::normalize_path(fullFn);
|
||||
|
||||
LOG("EXT: Loading extension '%s'...\n", fullFn.c_str());
|
||||
if (!base::is_file(fullFn)) {
|
||||
LOG("EXT: File '%s' not found\n", fullFn.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isBuiltinExtension = true; // TODO check if the extension is in Aseprite installation folder or the user folder
|
||||
|
||||
try {
|
||||
Extension* extension = loadExtension(dir, fullFn, isBuiltinExtension);
|
||||
m_extensions.push_back(extension);
|
||||
loadExtension(dir, fullFn, isBuiltinExtension);
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
LOG("EXT: Error loading JSON file: %s\n",
|
||||
@ -102,20 +292,46 @@ Extensions::~Extensions()
|
||||
|
||||
std::string Extensions::themePath(const std::string& themeId)
|
||||
{
|
||||
auto it = m_userThemes.find(themeId);
|
||||
if (it != m_userThemes.end())
|
||||
return it->second;
|
||||
|
||||
it = m_builtinThemes.find(themeId);
|
||||
if (it != m_builtinThemes.end())
|
||||
return it->second;
|
||||
|
||||
for (auto ext : m_extensions) {
|
||||
auto it = ext->themes().find(themeId);
|
||||
if (it != ext->themes().end())
|
||||
return it->second;
|
||||
}
|
||||
return std::string();
|
||||
}
|
||||
|
||||
const std::map<std::string, std::string>& Extensions::palettes() const
|
||||
ExtensionItems Extensions::palettes() const
|
||||
{
|
||||
return m_palettes;
|
||||
ExtensionItems palettes;
|
||||
for (auto ext : m_extensions)
|
||||
for (auto item : ext->palettes())
|
||||
palettes[item.first] = item.second;
|
||||
return palettes;
|
||||
}
|
||||
|
||||
Extension* Extensions::installCompressedExtension(const std::string& zipFn)
|
||||
{
|
||||
std::string dstExtensionPath =
|
||||
base::join_path(m_userExtensionsPath,
|
||||
base::get_file_title(zipFn));
|
||||
|
||||
ReadArchive in(zipFn);
|
||||
WriteArchive out(dstExtensionPath);
|
||||
|
||||
archive_entry* entry;
|
||||
while ((entry = in.readEntry()) != nullptr)
|
||||
out.writeEntry(in, entry);
|
||||
|
||||
Extension* extension = loadExtension(
|
||||
dstExtensionPath,
|
||||
base::join_path(dstExtensionPath, "package.json"),
|
||||
false);
|
||||
if (!extension)
|
||||
throw base::Exception("Error adding the new extension");
|
||||
|
||||
// Generate signal
|
||||
NewExtension(extension);
|
||||
return extension;
|
||||
}
|
||||
|
||||
Extension* Extensions::loadExtension(const std::string& path,
|
||||
@ -132,7 +348,8 @@ Extension* Extensions::loadExtension(const std::string& path,
|
||||
new Extension(path,
|
||||
name,
|
||||
displayName,
|
||||
true, // TODO check if the extension is enabled in the configuration
|
||||
// Extensions are enabled by default
|
||||
get_config_bool("extensions", name.c_str(), true),
|
||||
isBuiltinExtension));
|
||||
|
||||
auto contributes = json["contributes"];
|
||||
@ -151,12 +368,7 @@ Extension* Extensions::loadExtension(const std::string& path,
|
||||
themeId.c_str(),
|
||||
themePath.c_str());
|
||||
|
||||
if (isBuiltinExtension) {
|
||||
m_builtinThemes[themeId] = themePath;
|
||||
}
|
||||
else {
|
||||
m_userThemes[themeId] = themePath;
|
||||
}
|
||||
extension->addTheme(themeId, themePath);
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,11 +386,13 @@ Extension* Extensions::loadExtension(const std::string& path,
|
||||
palId.c_str(),
|
||||
palPath.c_str());
|
||||
|
||||
m_palettes[palId] = palPath;
|
||||
extension->addPalette(palId, palPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (extension)
|
||||
m_extensions.push_back(extension.get());
|
||||
return extension.release();
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,10 @@
|
||||
|
||||
namespace app {
|
||||
|
||||
// Key=theme/palette/etc. id
|
||||
// Value=theme/palette/etc. path
|
||||
typedef std::map<std::string, std::string> ExtensionItems;
|
||||
|
||||
class Extension {
|
||||
public:
|
||||
Extension(const std::string& path,
|
||||
@ -28,10 +32,16 @@ namespace app {
|
||||
const std::string& name() const { return m_name; }
|
||||
const std::string& displayName() const { return m_displayName; }
|
||||
|
||||
const ExtensionItems& themes() const { return m_themes; }
|
||||
const ExtensionItems& palettes() const { return m_palettes; }
|
||||
|
||||
void addTheme(const std::string& id, const std::string& path);
|
||||
void addPalette(const std::string& id, const std::string& path);
|
||||
|
||||
bool isEnabled() const { return m_isEnabled; }
|
||||
bool isInstalled() const { return m_isInstalled; }
|
||||
bool canBeDisabled() const { return m_isEnabled; }
|
||||
bool canBeUninstalled() const { return !m_isBuiltinExtension; }
|
||||
bool canBeDisabled() const;
|
||||
bool canBeUninstalled() const;
|
||||
|
||||
void enable(const bool state);
|
||||
void uninstall();
|
||||
@ -41,6 +51,11 @@ namespace app {
|
||||
obs::signal<void(Extension*)> Uninstall;
|
||||
|
||||
private:
|
||||
void uninstallFiles(const std::string& path);
|
||||
bool isCurrentTheme() const;
|
||||
|
||||
ExtensionItems m_themes;
|
||||
ExtensionItems m_palettes;
|
||||
std::string m_path;
|
||||
std::string m_name;
|
||||
std::string m_displayName;
|
||||
@ -64,10 +79,12 @@ namespace app {
|
||||
void disableExtension(Extension* extension);
|
||||
void uninstallExtension(Extension* extension);
|
||||
|
||||
void installCompressedExtension(const std::string& zipFn);
|
||||
Extension* installCompressedExtension(const std::string& zipFn);
|
||||
|
||||
std::string themePath(const std::string& themeId);
|
||||
const std::map<std::string, std::string>& palettes() const;
|
||||
ExtensionItems palettes() const;
|
||||
|
||||
obs::signal<void(Extension*)> NewExtension;
|
||||
|
||||
private:
|
||||
Extension* loadExtension(const std::string& path,
|
||||
@ -75,12 +92,7 @@ namespace app {
|
||||
const bool isBuiltinExtension);
|
||||
|
||||
List m_extensions;
|
||||
|
||||
// Key=theme id, Value=theme path
|
||||
std::map<std::string, std::string> m_builtinThemes;
|
||||
std::map<std::string, std::string> m_userThemes;
|
||||
|
||||
std::map<std::string, std::string> m_palettes;
|
||||
std::string m_userExtensionsPath;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -748,7 +748,7 @@ void SkinTheme::initWidget(Widget* widget)
|
||||
break;
|
||||
|
||||
case kListItemWidget:
|
||||
BORDER(1 * scale);
|
||||
widget->setStyle(styles.listItem());
|
||||
break;
|
||||
|
||||
case kComboBoxWidget: {
|
||||
@ -1063,34 +1063,6 @@ void SkinTheme::paintListBox(PaintEvent& ev)
|
||||
g->fillRect(colors.background(), g->getClipBounds());
|
||||
}
|
||||
|
||||
void SkinTheme::paintListItem(ui::PaintEvent& ev)
|
||||
{
|
||||
Widget* widget = static_cast<Widget*>(ev.getSource());
|
||||
gfx::Rect bounds = widget->clientBounds();
|
||||
Graphics* g = ev.graphics();
|
||||
gfx::Color fg, bg;
|
||||
|
||||
if (!widget->isEnabled()) {
|
||||
bg = colors.face();
|
||||
fg = colors.disabled();
|
||||
}
|
||||
else if (widget->isSelected()) {
|
||||
fg = colors.listitemSelectedText();
|
||||
bg = colors.listitemSelectedFace();
|
||||
}
|
||||
else {
|
||||
fg = colors.listitemNormalText();
|
||||
bg = colors.listitemNormalFace();
|
||||
}
|
||||
|
||||
g->fillRect(bg, bounds);
|
||||
|
||||
if (widget->hasText()) {
|
||||
bounds.shrink(widget->border());
|
||||
drawText(g, nullptr, fg, bg, widget, bounds, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void SkinTheme::paintMenu(PaintEvent& ev)
|
||||
{
|
||||
Widget* widget = static_cast<Widget*>(ev.getSource());
|
||||
|
@ -59,7 +59,6 @@ namespace app {
|
||||
|
||||
void paintEntry(ui::PaintEvent& ev) override;
|
||||
void paintListBox(ui::PaintEvent& ev) override;
|
||||
void paintListItem(ui::PaintEvent& ev) override;
|
||||
void paintMenu(ui::PaintEvent& ev) override;
|
||||
void paintMenuItem(ui::PaintEvent& ev) override;
|
||||
void paintSlider(ui::PaintEvent& ev) override;
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -43,11 +43,6 @@ bool ListItem::onProcessMessage(Message* msg)
|
||||
return Widget::onProcessMessage(msg);
|
||||
}
|
||||
|
||||
void ListItem::onPaint(PaintEvent& ev)
|
||||
{
|
||||
theme()->paintListItem(ev);
|
||||
}
|
||||
|
||||
void ListItem::onResize(ResizeEvent& ev)
|
||||
{
|
||||
setBoundsQuietly(ev.bounds());
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite UI Library
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
@ -24,7 +24,6 @@ namespace ui {
|
||||
|
||||
protected:
|
||||
bool onProcessMessage(Message* msg) override;
|
||||
void onPaint(PaintEvent& ev) override;
|
||||
void onResize(ResizeEvent& ev) override;
|
||||
void onSizeHint(SizeHintEvent& ev) override;
|
||||
|
||||
|
@ -64,7 +64,6 @@ namespace ui {
|
||||
|
||||
virtual void paintEntry(PaintEvent& ev) = 0;
|
||||
virtual void paintListBox(PaintEvent& ev) = 0;
|
||||
virtual void paintListItem(PaintEvent& ev) = 0;
|
||||
virtual void paintMenu(PaintEvent& ev) = 0;
|
||||
virtual void paintMenuItem(PaintEvent& ev) = 0;
|
||||
virtual void paintSlider(PaintEvent& ev) = 0;
|
||||
|
5
third_party/CMakeLists.txt
vendored
5
third_party/CMakeLists.txt
vendored
@ -111,3 +111,8 @@ endif()
|
||||
# JSON
|
||||
set(TAOCPP_JSON_BUILD_TESTS OFF CACHE BOOL "Build test programs")
|
||||
add_subdirectory(json)
|
||||
|
||||
# libarchive
|
||||
add_subdirectory(libarchive)
|
||||
target_include_directories(archive_static INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/libarchive/libarchive>)
|
||||
|
1
third_party/libarchive
vendored
Submodule
1
third_party/libarchive
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 328453a041e2ead52bf2c64b778a29f99bd17f14
|
Loading…
Reference in New Issue
Block a user