preserve channel list expansion and active channel (#36)

also check getenv in platform
This commit is contained in:
ouwou
2021-11-04 01:39:56 -04:00
parent d629846220
commit 1f445742b4
10 changed files with 173 additions and 29 deletions

View File

@@ -56,7 +56,7 @@ Abaddon::Abaddon()
Abaddon::~Abaddon() {
m_settings.Close();
m_discord.Stop();
StopDiscord();
}
Abaddon &Abaddon::Get() {
@@ -113,9 +113,7 @@ int Abaddon::StartGTK() {
ActionReloadCSS();
m_gtk_app->signal_shutdown().connect([&]() {
StopDiscord();
});
m_gtk_app->signal_shutdown().connect(sigc::mem_fun(*this, &Abaddon::StopDiscord), false);
if (!m_settings.IsValid()) {
Gtk::MessageDialog dlg(*m_main_window, "The settings file could not be created!", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
@@ -154,6 +152,7 @@ void Abaddon::StartDiscord() {
void Abaddon::StopDiscord() {
m_discord.Stop();
SaveState();
}
bool Abaddon::IsDiscordActive() const {
@@ -176,6 +175,7 @@ const DiscordClient &Abaddon::GetDiscordClient() const {
void Abaddon::DiscordOnReady() {
m_main_window->UpdateComponents();
LoadState();
}
void Abaddon::DiscordOnMessageCreate(const Message &message) {
@@ -365,6 +365,40 @@ void Abaddon::SetupUserMenu() {
m_user_menu->show_all();
}
void Abaddon::SaveState() {
if (!m_settings.GetSaveState()) return;
AbaddonApplicationState state;
state.ActiveChannel = m_main_window->GetChatActiveChannel();
state.Expansion = m_main_window->GetChannelList()->GetExpansionState();
const auto path = GetStateCachePath();
if (!util::IsFolder(path)) {
std::error_code ec;
std::filesystem::create_directories(path, ec);
}
auto *fp = std::fopen(GetStateCachePath("/state.json").c_str(), "wb");
if (fp == nullptr) return;
const auto s = nlohmann::json(state).dump(4);
std::fwrite(s.c_str(), 1, s.size(), fp);
std::fclose(fp);
}
void Abaddon::LoadState() {
if (!m_settings.GetSaveState()) return;
const auto data = ReadWholeFile(GetStateCachePath("/state.json"));
if (data.empty()) return;
try {
AbaddonApplicationState state = nlohmann::json::parse(data.begin(), data.end());
m_main_window->GetChannelList()->UseExpansionState(state.Expansion);
ActionChannelOpened(state.ActiveChannel);
} catch (const std::exception &e) {
printf("failed to load application state: %s\n", e.what());
}
}
void Abaddon::ManageHeapWindow(Gtk::Window *window) {
window->signal_hide().connect([this, window]() {
delete window;
@@ -422,6 +456,11 @@ std::string Abaddon::GetResPath() {
return path;
}
std::string Abaddon::GetStateCachePath() {
const static auto path = Platform::FindStateCacheFolder() + "/state";
return path;
}
std::string Abaddon::GetCSSPath(const std::string &path) {
return GetCSSPath() + path;
}
@@ -430,6 +469,10 @@ std::string Abaddon::GetResPath(const std::string &path) {
return GetResPath() + path;
}
std::string Abaddon::GetStateCachePath(const std::string &path) {
return GetStateCachePath() + path;
}
void Abaddon::ActionConnect() {
if (!m_discord.IsStarted())
StartDiscord();
@@ -461,7 +504,7 @@ void Abaddon::ActionJoinGuildDialog() {
}
void Abaddon::ActionChannelOpened(Snowflake id) {
if (id == m_main_window->GetChatActiveChannel()) return;
if (!id.IsValid() || id == m_main_window->GetChatActiveChannel()) return;
m_main_window->GetChatWindow()->SetTopic("");

View File

@@ -84,13 +84,17 @@ public:
static std::string GetCSSPath();
static std::string GetResPath();
static std::string GetStateCachePath();
static std::string GetCSSPath(const std::string &path);
static std::string GetResPath(const std::string &path);
static std::string GetStateCachePath(const std::string &path);
protected:
void ShowGuildVerificationGateDialog(Snowflake guild_id);
void SetupUserMenu();
void SaveState();
void LoadState();
Snowflake m_shown_user_menu_id;
Snowflake m_shown_user_menu_guild_id;

View File

@@ -380,6 +380,56 @@ void ChannelList::SetActiveChannel(Snowflake id) {
}
}
void ChannelList::UseExpansionState(const ExpansionStateRoot &root) {
auto recurse = [this](auto &self, const ExpansionStateRoot &root) -> void {
// and these are only channels
for (const auto &[id, state] : root.Children) {
if (const auto iter = GetIteratorForChannelFromID(id)) {
if (state.IsExpanded)
m_view.expand_row(m_model->get_path(iter), false);
else
m_view.collapse_row(m_model->get_path(iter));
}
self(self, state.Children);
}
};
// top level is guild
for (const auto &[id, state] : root.Children) {
if (const auto iter = GetIteratorForGuildFromID(id)) {
if (state.IsExpanded)
m_view.expand_row(m_model->get_path(iter), false);
else
m_view.collapse_row(m_model->get_path(iter));
}
recurse(recurse, state.Children);
}
}
ExpansionStateRoot ChannelList::GetExpansionState() const {
ExpansionStateRoot r;
auto recurse = [this](auto &self, const Gtk::TreeRow &row) -> ExpansionState {
ExpansionState r;
r.IsExpanded = row[m_columns.m_expanded];
for (const auto &child : row.children())
r.Children.Children[static_cast<Snowflake>(child[m_columns.m_id])] = self(self, child);
return r;
};
for (const auto &child : m_model->children()) {
const auto id = static_cast<Snowflake>(child[m_columns.m_id]);
if (static_cast<uint64_t>(id) == 0ULL) continue; // dont save DM header
r.Children[id] = recurse(recurse, child);
}
return r;
}
Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) {
auto &discord = Abaddon::Get().GetDiscordClient();
auto &img = Abaddon::Get().GetImageManager();

View File

@@ -6,7 +6,8 @@
#include <unordered_set>
#include <unordered_map>
#include <sigc++/sigc++.h>
#include "../discord/discord.hpp"
#include "discord/discord.hpp"
#include "state.hpp"
constexpr static int GuildIconSize = 24;
constexpr static int DMIconSize = 20;
@@ -134,6 +135,10 @@ public:
void UpdateListing();
void SetActiveChannel(Snowflake id);
// channel list should be populated when this is called
void UseExpansionState(const ExpansionStateRoot &state);
ExpansionStateRoot GetExpansionState() const;
protected:
void UpdateNewGuild(const GuildData &guild);
void UpdateRemoveGuild(Snowflake id);

View File

@@ -1,4 +1,5 @@
#include "platform.hpp"
#include "util.hpp"
#include <string>
#include <fstream>
#include <filesystem>
@@ -6,20 +7,6 @@
using namespace std::literals::string_literals;
bool IsFolder(std::string_view path) {
std::error_code ec;
const auto status = std::filesystem::status(path, ec);
if (ec) return false;
return status.type() == std::filesystem::file_type::directory;
}
bool IsFile(std::string_view path) {
std::error_code ec;
const auto status = std::filesystem::status(path, ec);
if (ec) return false;
return status.type() == std::filesystem::file_type::regular;
}
#if defined(_WIN32) && defined(_MSC_VER)
#include <Windows.h>
#include <Shlwapi.h>
@@ -90,19 +77,26 @@ std::string Platform::FindConfigFile() {
return "./abaddon.ini";
}
std::string Platform::FindStateCacheFolder() {
return ".";
}
#elif defined(__linux__)
std::string Platform::FindResourceFolder() {
static std::string found_path;
static bool found = false;
if (found) return found_path;
const static std::string home_path = std::getenv("HOME") + "/.local/share/abaddon"s;
const auto home_env = std::getenv("HOME");
if (home_env != nullptr) {
const static std::string home_path = home_env + "/.local/share/abaddon"s;
for (const auto &path : { "."s, home_path, std::string(ABADDON_DEFAULT_RESOURCE_DIR) }) {
if (IsFolder(path + "/res") && IsFolder(path + "/css")) {
found_path = path;
found = true;
return found_path;
for (const auto &path : { "."s, home_path, std::string(ABADDON_DEFAULT_RESOURCE_DIR) }) {
if (util::IsFolder(path + "/res") && util::IsFolder(path + "/css")) {
found_path = path;
found = true;
return found_path;
}
}
}
@@ -117,13 +111,31 @@ std::string Platform::FindConfigFile() {
if (x != nullptr)
return x;
const auto home_path = std::string(std::getenv("HOME")) + "/.config/abaddon/abaddon.ini";
for (const auto path : { "./abaddon.ini"s, home_path }) {
if (IsFile(path)) return path;
const auto home_env = std::getenv("HOME");
if (home_env != nullptr) {
const auto home_path = home_env + "/.config/abaddon/abaddon.ini"s;
for (auto path : { "./abaddon.ini"s, home_path }) {
if (util::IsFile(path)) return path;
}
}
puts("can't find configuration file!");
return "./abaddon.ini";
}
std::string Platform::FindStateCacheFolder() {
const auto home_env = std::getenv("HOME");
if (home_env != nullptr) {
auto home_path = home_env + "/.cache/abaddon"s;
std::error_code ec;
if (!util::IsFolder(home_path))
std::filesystem::create_directories(home_path, ec);
if (util::IsFolder(home_path))
return home_path;
}
puts("can't find cache folder!");
return ".";
}
#else
std::string Platform::FindResourceFolder() {
puts("unknown OS, trying to load resources from cwd");
@@ -137,4 +149,9 @@ std::string Platform::FindConfigFile() {
puts("unknown OS, trying to load config from cwd");
return "./abaddon.ini";
}
std::string Platform::FindStateCacheFolder() {
puts("unknown OS, setting state cache folder to cwd");
return ".";
}
#endif

View File

@@ -5,4 +5,5 @@ namespace Platform {
bool SetupFonts();
std::string FindResourceFolder();
std::string FindConfigFile();
std::string FindStateCacheFolder();
}

View File

@@ -109,3 +109,7 @@ std::string SettingsManager::GetAPIBaseURL() const {
bool SettingsManager::GetAnimatedGuildHoverOnly() const {
return GetSettingBool("gui", "animated_guild_hover_only", true);
}
bool SettingsManager::GetSaveState() const {
return GetSettingBool("gui", "save_state", true);
}

View File

@@ -23,6 +23,7 @@ public:
std::string GetGatewayURL() const;
std::string GetAPIBaseURL() const;
bool GetAnimatedGuildHoverOnly() const;
bool GetSaveState() const;
// i would like to use Gtk::StyleProperty for this, but it will not work on windows
// #1 it's missing from the project files for the version used by vcpkg

View File

@@ -1,4 +1,5 @@
#include "util.hpp"
#include <filesystem>
Semaphore::Semaphore(int count)
: m_count(count) {}
@@ -200,3 +201,17 @@ void AddPointerCursor(Gtk::Widget &widget) {
window->set_cursor(cursor);
});
}
bool util::IsFolder(std::string_view path) {
std::error_code ec;
const auto status = std::filesystem::status(path, ec);
if (ec) return false;
return status.type() == std::filesystem::file_type::directory;
}
bool util::IsFile(std::string_view path) {
std::error_code ec;
const auto status = std::filesystem::status(path, ec);
if (ec) return false;
return status.type() == std::filesystem::file_type::regular;
}

View File

@@ -21,6 +21,10 @@ struct is_optional : ::std::false_type {};
template<typename T>
struct is_optional<::std::optional<T>> : ::std::true_type {};
bool IsFolder(std::string_view path);
bool IsFile(std::string_view path);
} // namespace util
class Semaphore {