-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Add a new library responsible for opening theme packages. This library is still very much a work in progress as of now. - Add jsoncpp and libzip as dependencies. - Add a new option to load theme packages in Preferences > Theme. On success, this shows a dialog box explaining everythinhg succeeded. Signed-off-by: Avery King <[email protected]>
Avery King
committed
Nov 23, 2023
1 parent
a4e851d
commit 818a794
Showing
17 changed files
with
957 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ set( LIBRARIES | |
lib-registries | ||
lib-strings | ||
lib-track | ||
lib-theme | ||
lib-utility | ||
lib-xml | ||
lib-audio-devices | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
set(SOURCES | ||
ThemePackage.cpp | ||
ThemePackage.h | ||
ThemeResources.cpp | ||
ThemeResources.h | ||
ThemeResourceList.cpp | ||
ThemeResourceList.h | ||
|
||
# Exceptions | ||
exceptions/ArchiveError.cpp | ||
exceptions/ArchiveError.h | ||
exceptions/IncompatibleTheme.cpp | ||
exceptions/IncompatibleTheme.h | ||
) | ||
|
||
set(LIBRARIES | ||
PRIVATE | ||
jsoncpp | ||
libzip::zip | ||
) | ||
|
||
tenacity_library(lib-theme "${SOURCES}" "${LIBRARIES}" "" "") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Tenacity Theme Package Documentation | ||
|
||
Tenacity theme packages come as ZIP files. Their structure looks something | ||
like this: | ||
|
||
``` | ||
TenacityDarkTheme.zip: | ||
| | ||
|--- theme.json | ||
|--- colors.json | ||
|--- bitmaps | ||
|--- bmpPause.png | ||
|--- bmpPlay.png | ||
|--- ...etc | ||
``` | ||
|
||
## Package Precedence | ||
|
||
When an archive is first opened, Tenacity first reads `theme.json`. If this | ||
file is not found, this file is invalid, or contains invalid values of required | ||
properties, Tenacity considers the entire theme package invalid. | ||
|
||
After reading `theme.json`, Tenacity reads `colors.json` next and loads any | ||
color resources. Tenacity will always continue parsing the rest of the package | ||
even if `colors.json` is invalid or missing, although it will use the system's | ||
colors, which might not look good with the icon set. | ||
|
||
Finally, after | ||
|
||
## `theme.json` | ||
|
||
At the root of each package is a `theme.json` file. This file contains basic | ||
information about the theme package, such as the name, minimum required app | ||
version, and other information. | ||
|
||
### Properties | ||
|
||
- `name` (string): The name of the theme package. Required. | ||
- `minAppVersion` (int array): The minimum required app version (e.g., | ||
`[1, 4, 0]`). Optional, but strongly recommended. If not specified, a value | ||
of `[1, 4, 0]` is specified as the deafult. | ||
- `themeType` (string): Either "dark" for dark themes, "light" for light | ||
themes, or "neutral" for neither. | ||
|
||
## `colors.json` | ||
|
||
This is where colors are specified. | ||
|
||
### Properties | ||
|
||
TODO. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,240 @@ | ||
/********************************************************************** | ||
Tenacity | ||
ThemePackage.cpp | ||
Avery King | ||
License: GPL v2 or later | ||
**********************************************************************/ | ||
|
||
#include "ThemePackage.h" | ||
|
||
#include <stdexcept> | ||
|
||
#include <jsoncpp/json/value.h> | ||
#include <jsoncpp/json/reader.h> | ||
|
||
#include "exceptions/ArchiveError.h" | ||
#include "exceptions/IncompatibleTheme.h" | ||
|
||
using namespace ThemeExceptions; | ||
|
||
#define THROW_NOT_IMPLEMENTED throw std::runtime_error("Not implemented") | ||
|
||
ThemePackage::ThemePackage() : mPackageArchive{nullptr} | ||
{ | ||
} | ||
|
||
ThemePackage::~ThemePackage() | ||
{ | ||
ClosePackage(); | ||
} | ||
|
||
void ThemePackage::OpenPackage(const std::string& path) | ||
{ | ||
// Read the archive | ||
int error = 0; | ||
mPackageArchive = zip_open(path.c_str(), ZIP_RDONLY, &error); | ||
if (!mPackageArchive) | ||
{ | ||
switch (error) | ||
{ | ||
case ZIP_ER_INCONS: | ||
case ZIP_ER_READ: | ||
case ZIP_ER_SEEK: | ||
// TODO: better error handling | ||
throw ArchiveError(ArchiveError::Type::OperationalError); | ||
break; | ||
case ZIP_ER_NOZIP: | ||
case ZIP_ER_INVAL: | ||
throw ArchiveError(ArchiveError::Type::InvalidArchive); | ||
break; | ||
case ZIP_ER_MEMORY: | ||
throw std::bad_alloc(); | ||
break; | ||
} | ||
} | ||
|
||
error = 0; | ||
|
||
// Extract the JSON data from the archive | ||
std::unique_ptr<char> jsonData; | ||
zip_stat_t jsonInfo; | ||
|
||
error = zip_stat(mPackageArchive, "theme.json", ZIP_STAT_SIZE, &jsonInfo); | ||
if (error != 0) | ||
{ | ||
// TODO: Better error handling | ||
throw ArchiveError(ArchiveError::Type::OperationalError); | ||
} | ||
|
||
// Read the entire theme.json into memory | ||
jsonData.reset(new char[jsonInfo.size]); | ||
zip_file_t* themeFile = zip_fopen(mPackageArchive, "theme.json", 0); | ||
zip_int64_t bytesRead = zip_fread(themeFile, jsonData.get(), jsonInfo.size); | ||
if (bytesRead != jsonInfo.size) | ||
{ | ||
zip_fclose(themeFile); | ||
// TODO: Better error handling | ||
throw ArchiveError(ArchiveError::Type::OperationalError); | ||
} | ||
|
||
std::string jsonString = jsonData.get(); | ||
mJsonStream = std::istringstream(jsonString); | ||
} | ||
|
||
/** @brief Parses a version string. | ||
* | ||
* This function works | ||
* | ||
* @param versionString The version string to parse | ||
* @return Returns a std::vector<int> containing the version string values. | ||
* | ||
*/ | ||
std::vector<int> ParseVersionString(const std::string& versionString) | ||
{ | ||
std::vector<int> version; | ||
std::string tempString; | ||
int tempVersion; | ||
std::size_t previousPeriod = 0; | ||
std::size_t period; | ||
|
||
// This is a very simple version string parsing algorithm that merely | ||
// creates substrings and converts them to an integer. | ||
do | ||
{ | ||
period = versionString.find('.', previousPeriod); | ||
tempString = versionString.substr(previousPeriod, period - previousPeriod); | ||
|
||
try | ||
{ | ||
tempVersion = std::stoi(tempString); | ||
} catch(...) | ||
{ | ||
tempVersion = 0; | ||
} | ||
|
||
version.push_back(tempVersion); | ||
previousPeriod = period + 1; | ||
} while (period != std::string::npos); | ||
|
||
return version; | ||
} | ||
|
||
void ThemePackage::ParsePackage() | ||
{ | ||
if (mJsonStream.str().empty()) | ||
{ | ||
return; | ||
} | ||
|
||
Json::Value packageRoot; | ||
{ | ||
Json::CharReaderBuilder builder; | ||
std::string parserErrors; | ||
bool ok = Json::parseFromStream(builder, mJsonStream, &packageRoot, &parserErrors); | ||
if (!ok) | ||
{ | ||
throw ArchiveError(ArchiveError::Type::OperationalError); | ||
} | ||
} | ||
|
||
// Check if the theme package is a multi-theme package. If so, parse those separately | ||
const Json::Value subthemes = packageRoot["subthemes"]; | ||
if (subthemes) | ||
{ | ||
// TODO: handles subthemes | ||
// throw std::runtime_error("Not implemented yet!"); | ||
return; | ||
} | ||
|
||
const Json::Value themeName = packageRoot["name"]; | ||
Json::Value minAppVersionString = packageRoot.get("minAppVersion", "0.0.0"); | ||
std::vector<int> minAppVersion = ParseVersionString(minAppVersionString.asString()); | ||
int minVersionMajor = TENACITY_VERSION; | ||
int minVersionRelease = TENACITY_RELEASE; | ||
int minVersionRevision = TENACITY_REVISION; | ||
|
||
try | ||
{ | ||
minVersionMajor = minAppVersion.at(0); | ||
minVersionRelease = minAppVersion.at(1); | ||
if (minAppVersion.size() >= 3) minVersionRevision = minAppVersion[2]; | ||
} catch (...) | ||
{ | ||
// Something happened when parsing the version number. Assume '0.0.0' by default | ||
minVersionMajor = minVersionRelease = minVersionRevision = 0; | ||
} | ||
|
||
// Handle minimum version compatibility | ||
if (minVersionMajor < TENACITY_VERSION || minVersionRelease < TENACITY_RELEASE) | ||
{ | ||
// TODO: Better exception handling | ||
throw IncompatibleTheme(minVersionMajor, minVersionRelease, minVersionRevision); | ||
} | ||
|
||
// FIXME: Should the revision number really matter between theme packages? | ||
// I don't think it should, but I'm leaving this in until we decide on that | ||
// behavior... | ||
// else if (minVersionRevision < TENACITY_REVISION) | ||
// { | ||
// // TODO: Better exception handling | ||
// throw std::runtime_error("Incompatible theme"); | ||
// } | ||
|
||
// Handle theme name | ||
if (themeName.asString().empty()) | ||
{ | ||
// TODO: Better exception handling | ||
throw std::runtime_error("Theme package does not have a name!"); | ||
} | ||
|
||
// TODO: handle other properities. | ||
} | ||
|
||
void ThemePackage::ClosePackage() | ||
{ | ||
if (mPackageArchive) | ||
{ | ||
zip_close(mPackageArchive); | ||
} | ||
} | ||
|
||
ThemePackage::ResourceMap& ThemePackage::GetResourceMap() | ||
{ | ||
return mThemeResources; | ||
} | ||
|
||
void ThemePackage::LoadAllResources() | ||
{ | ||
THROW_NOT_IMPLEMENTED; | ||
} | ||
|
||
void ThemePackage::LoadResource(const std::string name) | ||
{ | ||
THROW_NOT_IMPLEMENTED; | ||
} | ||
|
||
void ThemePackage::LoadResources(const ThemePackage::ResourceList& names) | ||
{ | ||
THROW_NOT_IMPLEMENTED; | ||
} | ||
|
||
std::any& ThemePackage::GetResource(std::string name) | ||
{ | ||
return mThemeResources.at(name); | ||
} | ||
|
||
std::string ThemePackage::GetName() const | ||
{ | ||
return mPackageName; | ||
} | ||
|
||
bool ThemePackage::IsMultiThemePackage() const | ||
{ | ||
// FIXME: Unimplemented. | ||
return false; | ||
} |
Oops, something went wrong.