Split up Window Layout loading into a loading and application stage (#1964)

* Split up Window Layout loading into a loading and application stage

Previously, we were creating UI elements at while we were reading the window-layout.json file.
We now read the window-layout.json file fully first, which results in a
WindowLayout struct which is built up of a list of windows with a list
of tabs with a root node which contains containers and splits.
This WindowLayout can then be applied.

This will enable PRs like #1940 to start Chatterino with Window Layouts
that aren't defined in a json file.

This commit has deprecated loading of v1 window layouts (we're now on v2). If a v1 window layout is there, it will just be ignored and Chatterino will boot up as if it did not have a window layout at all, and on save that old window layout will be gone.

* Fix compile error for mac
This commit is contained in:
pajlada 2020-09-19 11:14:10 -04:00 committed by GitHub
parent 7eabba959b
commit 913193f8b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 484 additions and 191 deletions

View file

@ -2,6 +2,7 @@
## Unversioned ## Unversioned
- Minor: Deprecate loading of "v1" window layouts. If you haven't updated Chatterino in more than 2 years, there's a chance you will lose your window layout.
- Minor: Disable checking for updates on unsupported platforms (#1874) - Minor: Disable checking for updates on unsupported platforms (#1874)
- Bugfix: Fix bug preventing users from setting the highlight color of the second entry in the "User" highlights tab (#1898) - Bugfix: Fix bug preventing users from setting the highlight color of the second entry in the "User" highlights tab (#1898)
- Bugfix: Fix bug where the "check user follow state" event could trigger a network request requesting the user to follow or unfollow a user. By itself its quite harmless as it just repeats to Twitch the same follow state we had, so no follows should have been lost by this but it meant there was a rogue network request that was fired that could cause a crash (#1906) - Bugfix: Fix bug where the "check user follow state" event could trigger a network request requesting the user to follow or unfollow a user. By itself its quite harmless as it just repeats to Twitch the same follow state we had, so no follows should have been lost by this but it meant there was a rogue network request that was fired that could cause a crash (#1906)

View file

@ -124,6 +124,7 @@ SOURCES += \
src/common/NetworkResult.cpp \ src/common/NetworkResult.cpp \
src/common/UsernameSet.cpp \ src/common/UsernameSet.cpp \
src/common/Version.cpp \ src/common/Version.cpp \
src/common/WindowDescriptors.cpp \
src/controllers/accounts/Account.cpp \ src/controllers/accounts/Account.cpp \
src/controllers/accounts/AccountController.cpp \ src/controllers/accounts/AccountController.cpp \
src/controllers/accounts/AccountModel.cpp \ src/controllers/accounts/AccountModel.cpp \

View file

@ -0,0 +1,207 @@
#include "common/WindowDescriptors.hpp"
#include "widgets/Window.hpp"
namespace chatterino {
namespace {
QJsonArray loadWindowArray(const QString &settingsPath)
{
QFile file(settingsPath);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
QJsonDocument document = QJsonDocument::fromJson(data);
QJsonArray windows_arr = document.object().value("windows").toArray();
return windows_arr;
}
template <typename T>
T loadNodes(const QJsonObject &obj)
{
static_assert("loadNodes must be called with the SplitNodeDescriptor "
"or ContainerNodeDescriptor type");
}
template <>
SplitNodeDescriptor loadNodes(const QJsonObject &root)
{
SplitNodeDescriptor descriptor;
descriptor.flexH_ = root.value("flexh").toDouble(1.0);
descriptor.flexV_ = root.value("flexv").toDouble(1.0);
auto data = root.value("data").toObject();
SplitDescriptor::loadFromJSON(descriptor, root, data);
return descriptor;
}
template <>
ContainerNodeDescriptor loadNodes(const QJsonObject &root)
{
ContainerNodeDescriptor descriptor;
descriptor.flexH_ = root.value("flexh").toDouble(1.0);
descriptor.flexV_ = root.value("flexv").toDouble(1.0);
descriptor.vertical_ = root.value("type").toString() == "vertical";
for (QJsonValue _val : root.value("items").toArray())
{
auto _obj = _val.toObject();
auto _type = _obj.value("type");
if (_type == "split")
{
descriptor.items_.emplace_back(
loadNodes<SplitNodeDescriptor>(_obj));
}
else
{
descriptor.items_.emplace_back(
loadNodes<ContainerNodeDescriptor>(_obj));
}
}
return descriptor;
}
} // namespace
void SplitDescriptor::loadFromJSON(SplitDescriptor &descriptor,
const QJsonObject &root,
const QJsonObject &data)
{
descriptor.type_ = data.value("type").toString();
descriptor.server_ = data.value("server").toInt(-1);
if (data.contains("channel"))
{
descriptor.channelName_ = data.value("channel").toString();
}
else
{
descriptor.channelName_ = data.value("name").toString();
}
}
WindowLayout WindowLayout::loadFromFile(const QString &path)
{
WindowLayout layout;
bool hasSetAMainWindow = false;
// "deserialize"
for (const QJsonValue &window_val : loadWindowArray(path))
{
QJsonObject window_obj = window_val.toObject();
WindowDescriptor window;
// Load window type
QString type_val = window_obj.value("type").toString();
auto type = type_val == "main" ? WindowType::Main : WindowType::Popup;
if (type == WindowType::Main)
{
if (hasSetAMainWindow)
{
qDebug()
<< "Window Layout file contains more than one Main window "
"- demoting to Popup type";
type = WindowType::Popup;
}
hasSetAMainWindow = true;
}
window.type_ = type;
// Load window state
if (window_obj.value("state") == "minimized")
{
window.state_ = WindowDescriptor::State::Minimized;
}
else if (window_obj.value("state") == "maximized")
{
window.state_ = WindowDescriptor::State::Maximized;
}
// Load window geometry
{
int x = window_obj.value("x").toInt(-1);
int y = window_obj.value("y").toInt(-1);
int width = window_obj.value("width").toInt(-1);
int height = window_obj.value("height").toInt(-1);
window.geometry_ = QRect(x, y, width, height);
}
bool hasSetASelectedTab = false;
// Load window tabs
QJsonArray tabs = window_obj.value("tabs").toArray();
for (QJsonValue tab_val : tabs)
{
TabDescriptor tab;
QJsonObject tab_obj = tab_val.toObject();
// Load tab custom title
QJsonValue title_val = tab_obj.value("title");
if (title_val.isString())
{
tab.customTitle_ = title_val.toString();
}
// Load tab selected state
tab.selected_ = tab_obj.value("selected").toBool(false);
if (tab.selected_)
{
if (hasSetASelectedTab)
{
qDebug() << "Window contains more than one selected tab - "
"demoting to unselected";
tab.selected_ = false;
}
hasSetASelectedTab = true;
}
// Load tab "highlightsEnabled" state
tab.highlightsEnabled_ =
tab_obj.value("highlightsEnabled").toBool(true);
QJsonObject splitRoot = tab_obj.value("splits2").toObject();
// Load tab splits
if (!splitRoot.isEmpty())
{
// root type
auto nodeType = splitRoot.value("type").toString();
if (nodeType == "split")
{
tab.rootNode_ = loadNodes<SplitNodeDescriptor>(splitRoot);
}
else if (nodeType == "horizontal" || nodeType == "vertical")
{
tab.rootNode_ =
loadNodes<ContainerNodeDescriptor>(splitRoot);
}
}
window.tabs_.emplace_back(std::move(tab));
}
// Load emote popup position
QJsonObject emote_popup_obj = window_obj.value("emotePopup").toObject();
layout.emotePopupPos_ = QPoint(emote_popup_obj.value("x").toInt(),
emote_popup_obj.value("y").toInt());
layout.windows_.emplace_back(std::move(window));
}
return layout;
}
} // namespace chatterino

View file

@ -0,0 +1,97 @@
#pragma once
#include <QString>
#include <optional>
#include <variant>
namespace chatterino {
/**
* A WindowLayout contains one or more windows.
* Only one of those windows can be the main window
*
* Each window contains a list of tabs.
* Only one of those tabs can be marked as selected.
*
* Each tab contains a root node.
* The root node is either a:
* - Split Node (for single-split tabs), or
* - Container Node (for multi-split tabs).
* This container node would then contain a list of nodes on its own, which could be split nodes or further container nodes
**/
// from widgets/Window.hpp
enum class WindowType;
struct SplitDescriptor {
// twitch or mentions or watching or whispers or irc
QString type_;
// Twitch Channel name or IRC channel name
QString channelName_;
// IRC server
int server_{-1};
// Whether "Moderation Mode" (the sword icon) is enabled in this split or not
bool moderationMode_{false};
static void loadFromJSON(SplitDescriptor &descriptor,
const QJsonObject &root, const QJsonObject &data);
};
struct SplitNodeDescriptor : SplitDescriptor {
qreal flexH_ = 1;
qreal flexV_ = 1;
};
struct ContainerNodeDescriptor;
using NodeDescriptor =
std::variant<ContainerNodeDescriptor, SplitNodeDescriptor>;
struct ContainerNodeDescriptor {
qreal flexH_ = 1;
qreal flexV_ = 1;
bool vertical_ = false;
std::vector<NodeDescriptor> items_;
};
struct TabDescriptor {
QString customTitle_;
bool selected_{false};
bool highlightsEnabled_{true};
std::optional<NodeDescriptor> rootNode_;
};
struct WindowDescriptor {
enum class State {
None,
Minimized,
Maximized,
};
WindowType type_;
State state_ = State::None;
QRect geometry_;
std::vector<TabDescriptor> tabs_;
};
class WindowLayout
{
public:
static WindowLayout loadFromFile(const QString &path);
// A complete window layout has a single emote popup position that is shared among all windows
QPoint emotePopupPos_;
std::vector<WindowDescriptor> windows_;
};
} // namespace chatterino

View file

@ -23,6 +23,7 @@
#include "singletons/Settings.hpp" #include "singletons/Settings.hpp"
#include "singletons/Theme.hpp" #include "singletons/Theme.hpp"
#include "util/Clamp.hpp" #include "util/Clamp.hpp"
#include "util/CombinePath.hpp"
#include "widgets/AccountSwitchPopup.hpp" #include "widgets/AccountSwitchPopup.hpp"
#include "widgets/Notebook.hpp" #include "widgets/Notebook.hpp"
#include "widgets/Window.hpp" #include "widgets/Window.hpp"
@ -31,25 +32,17 @@
#include "widgets/splits/Split.hpp" #include "widgets/splits/Split.hpp"
#include "widgets/splits/SplitContainer.hpp" #include "widgets/splits/SplitContainer.hpp"
#define SETTINGS_FILENAME "/window-layout.json"
namespace chatterino { namespace chatterino {
namespace { namespace {
QJsonArray loadWindowArray(const QString &settingsPath)
{ const QString WINDOW_LAYOUT_FILENAME(QStringLiteral("window-layout.json"));
QFile file(settingsPath);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
QJsonDocument document = QJsonDocument::fromJson(data);
QJsonArray windows_arr = document.object().value("windows").toArray();
return windows_arr;
}
boost::optional<bool> &shouldMoveOutOfBoundsWindow() boost::optional<bool> &shouldMoveOutOfBoundsWindow()
{ {
static boost::optional<bool> x; static boost::optional<bool> x;
return x; return x;
} }
} // namespace } // namespace
using SplitNode = SplitContainer::Node; using SplitNode = SplitContainer::Node;
@ -86,6 +79,8 @@ void WindowManager::showAccountSelectPopup(QPoint point)
} }
WindowManager::WindowManager() WindowManager::WindowManager()
: windowLayoutFilePath(
combinePath(getPaths()->settingsDirectory, WINDOW_LAYOUT_FILENAME))
{ {
qDebug() << "init WindowManager"; qDebug() << "init WindowManager";
@ -294,141 +289,18 @@ void WindowManager::initialize(Settings &settings, Paths &paths)
assert(!this->initialized_); assert(!this->initialized_);
// load file
QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME;
QJsonArray windows_arr = loadWindowArray(settingsPath);
// "deserialize"
for (QJsonValue window_val : windows_arr)
{ {
QJsonObject window_obj = window_val.toObject(); auto windowLayout = this->loadWindowLayoutFromFile();
// get type this->emotePopupPos_ = windowLayout.emotePopupPos_;
QString type_val = window_obj.value("type").toString();
WindowType type =
type_val == "main" ? WindowType::Main : WindowType::Popup;
if (type == WindowType::Main && mainWindow_ != nullptr) this->applyWindowLayout(windowLayout);
{
type = WindowType::Popup;
}
Window &window = createWindow(type, false);
if (type == WindowType::Main)
{
mainWindow_ = &window;
}
// get geometry
{
int x = window_obj.value("x").toInt(-1);
int y = window_obj.value("y").toInt(-1);
int width = window_obj.value("width").toInt(-1);
int height = window_obj.value("height").toInt(-1);
QRect geometry{x, y, width, height};
// out of bounds windows
auto screens = qApp->screens();
bool outOfBounds = std::none_of(
screens.begin(), screens.end(), [&](QScreen *screen) {
return screen->availableGeometry().intersects(geometry);
});
// ask if move into bounds
auto &&should = shouldMoveOutOfBoundsWindow();
if (outOfBounds && !should)
{
should =
QMessageBox(QMessageBox::Icon::Warning,
"Windows out of bounds",
"Some windows were detected out of bounds. "
"Should they be moved into bounds?",
QMessageBox::Yes | QMessageBox::No)
.exec() == QMessageBox::Yes;
}
if ((!outOfBounds || !should.value()) && x != -1 && y != -1 &&
width != -1 && height != -1)
{
// Have to offset x by one because qt moves the window 1px too
// far to the left:w
window.setInitialBounds({x, y, width, height});
}
}
// load tabs
QJsonArray tabs = window_obj.value("tabs").toArray();
for (QJsonValue tab_val : tabs)
{
SplitContainer *page = window.getNotebook().addPage(false);
QJsonObject tab_obj = tab_val.toObject();
// set custom title
QJsonValue title_val = tab_obj.value("title");
if (title_val.isString())
{
page->getTab()->setCustomTitle(title_val.toString());
}
// selected
if (tab_obj.value("selected").toBool(false))
{
window.getNotebook().select(page);
}
// highlighting on new messages
bool val = tab_obj.value("highlightsEnabled").toBool(true);
page->getTab()->setHighlightsEnabled(val);
// load splits
QJsonObject splitRoot = tab_obj.value("splits2").toObject();
if (!splitRoot.isEmpty())
{
page->decodeFromJson(splitRoot);
continue;
}
// fallback load splits (old)
int colNr = 0;
for (QJsonValue column_val : tab_obj.value("splits").toArray())
{
for (QJsonValue split_val : column_val.toArray())
{
Split *split = new Split(page);
QJsonObject split_obj = split_val.toObject();
split->setChannel(decodeChannel(split_obj));
page->appendSplit(split);
}
colNr++;
}
}
window.show();
QJsonObject emote_popup_obj = window_obj.value("emotePopup").toObject();
this->emotePopupPos_ = QPoint(emote_popup_obj.value("x").toInt(),
emote_popup_obj.value("y").toInt());
if (window_obj.value("state") == "minimized")
{
window.setWindowState(Qt::WindowMinimized);
}
else if (window_obj.value("state") == "maximized")
{
window.setWindowState(Qt::WindowMaximized);
}
} }
// No main window has been created from loading, create an empty one
if (mainWindow_ == nullptr) if (mainWindow_ == nullptr)
{ {
mainWindow_ = &createWindow(WindowType::Main); mainWindow_ = &this->createWindow(WindowType::Main);
mainWindow_->getNotebook().addPage(true); mainWindow_->getNotebook().addPage(true);
} }
@ -545,8 +417,7 @@ void WindowManager::save()
document.setObject(obj); document.setObject(obj);
// save file // save file
QString settingsPath = getPaths()->settingsDirectory + SETTINGS_FILENAME; QSaveFile file(this->windowLayoutFilePath);
QSaveFile file(settingsPath);
file.open(QIODevice::WriteOnly | QIODevice::Truncate); file.open(QIODevice::WriteOnly | QIODevice::Truncate);
QJsonDocument::JsonFormat format = QJsonDocument::JsonFormat format =
@ -650,34 +521,32 @@ void WindowManager::encodeChannel(IndirectChannel channel, QJsonObject &obj)
} }
} }
IndirectChannel WindowManager::decodeChannel(const QJsonObject &obj) IndirectChannel WindowManager::decodeChannel(const SplitDescriptor &descriptor)
{ {
assertInGuiThread(); assertInGuiThread();
auto app = getApp(); auto app = getApp();
QString type = obj.value("type").toString(); if (descriptor.type_ == "twitch")
if (type == "twitch")
{ {
return app->twitch.server->getOrAddChannel( return app->twitch.server->getOrAddChannel(descriptor.channelName_);
obj.value("name").toString());
} }
else if (type == "mentions") else if (descriptor.type_ == "mentions")
{ {
return app->twitch.server->mentionsChannel; return app->twitch.server->mentionsChannel;
} }
else if (type == "watching") else if (descriptor.type_ == "watching")
{ {
return app->twitch.server->watchingChannel; return app->twitch.server->watchingChannel;
} }
else if (type == "whispers") else if (descriptor.type_ == "whispers")
{ {
return app->twitch.server->whispersChannel; return app->twitch.server->whispersChannel;
} }
else if (type == "irc") else if (descriptor.type_ == "irc")
{ {
return Irc::instance().getOrAddChannel(obj.value("server").toInt(-1), return Irc::instance().getOrAddChannel(descriptor.server_,
obj.value("channel").toString()); descriptor.channelName_);
} }
return Channel::getEmpty(); return Channel::getEmpty();
@ -703,4 +572,109 @@ void WindowManager::incGeneration()
this->generation_++; this->generation_++;
} }
WindowLayout WindowManager::loadWindowLayoutFromFile() const
{
return WindowLayout::loadFromFile(this->windowLayoutFilePath);
}
void WindowManager::applyWindowLayout(const WindowLayout &layout)
{
// Set emote popup position
this->emotePopupPos_ = layout.emotePopupPos_;
for (const auto &windowData : layout.windows_)
{
auto type = windowData.type_;
Window &window = this->createWindow(type, false);
if (type == WindowType::Main)
{
assert(this->mainWindow_ == nullptr);
this->mainWindow_ = &window;
}
// get geometry
{
// out of bounds windows
auto screens = qApp->screens();
bool outOfBounds = std::none_of(
screens.begin(), screens.end(), [&](QScreen *screen) {
return screen->availableGeometry().intersects(
windowData.geometry_);
});
// ask if move into bounds
auto &&should = shouldMoveOutOfBoundsWindow();
if (outOfBounds && !should)
{
should =
QMessageBox(QMessageBox::Icon::Warning,
"Windows out of bounds",
"Some windows were detected out of bounds. "
"Should they be moved into bounds?",
QMessageBox::Yes | QMessageBox::No)
.exec() == QMessageBox::Yes;
}
if ((!outOfBounds || !should.value()) &&
windowData.geometry_.x() != -1 &&
windowData.geometry_.y() != -1 &&
windowData.geometry_.width() != -1 &&
windowData.geometry_.height() != -1)
{
// Have to offset x by one because qt moves the window 1px too
// far to the left:w
window.setInitialBounds({windowData.geometry_.x(),
windowData.geometry_.y(),
windowData.geometry_.width(),
windowData.geometry_.height()});
}
}
// open tabs
for (const auto &tab : windowData.tabs_)
{
SplitContainer *page = window.getNotebook().addPage(false);
// set custom title
if (!tab.customTitle_.isEmpty())
{
page->getTab()->setCustomTitle(tab.customTitle_);
}
// selected
if (tab.selected_)
{
window.getNotebook().select(page);
}
// highlighting on new messages
page->getTab()->setHighlightsEnabled(tab.highlightsEnabled_);
if (tab.rootNode_)
{
page->applyFromDescriptor(*tab.rootNode_);
}
}
window.show();
// Set window state
switch (windowData.state_)
{
case WindowDescriptor::State::Minimized: {
window.setWindowState(Qt::WindowMinimized);
}
break;
case WindowDescriptor::State::Maximized: {
window.setWindowState(Qt::WindowMaximized);
}
break;
}
}
}
} // namespace chatterino } // namespace chatterino

View file

@ -3,6 +3,7 @@
#include "common/Channel.hpp" #include "common/Channel.hpp"
#include "common/FlagsEnum.hpp" #include "common/FlagsEnum.hpp"
#include "common/Singleton.hpp" #include "common/Singleton.hpp"
#include "common/WindowDescriptors.hpp"
#include "pajlada/settings/settinglistener.hpp" #include "pajlada/settings/settinglistener.hpp"
#include "widgets/splits/SplitContainer.hpp" #include "widgets/splits/SplitContainer.hpp"
@ -25,7 +26,7 @@ public:
WindowManager(); WindowManager();
static void encodeChannel(IndirectChannel channel, QJsonObject &obj); static void encodeChannel(IndirectChannel channel, QJsonObject &obj);
static IndirectChannel decodeChannel(const QJsonObject &obj); static IndirectChannel decodeChannel(const SplitDescriptor &descriptor);
void showSettingsDialog( void showSettingsDialog(
SettingsDialogPreference preference = SettingsDialogPreference()); SettingsDialogPreference preference = SettingsDialogPreference());
@ -90,6 +91,15 @@ public:
private: private:
void encodeNodeRecusively(SplitContainer::Node *node, QJsonObject &obj); void encodeNodeRecusively(SplitContainer::Node *node, QJsonObject &obj);
// Load window layout from the window-layout.json file
WindowLayout loadWindowLayoutFromFile() const;
// Apply a window layout for this window manager.
void applyWindowLayout(const WindowLayout &layout);
// Contains the full path to the window layout file, e.g. /home/pajlada/.local/share/Chatterino/Settings/window-layout.json
const QString windowLayoutFilePath;
bool initialized_ = false; bool initialized_ = false;
QPoint emotePopupPos_; QPoint emotePopupPos_;

View file

@ -695,55 +695,67 @@ SplitContainer::Node *SplitContainer::getBaseNode()
return &this->baseNode_; return &this->baseNode_;
} }
void SplitContainer::decodeFromJson(QJsonObject &obj) void SplitContainer::applyFromDescriptor(const NodeDescriptor &rootNode)
{ {
assert(this->baseNode_.type_ == Node::EmptyRoot); assert(this->baseNode_.type_ == Node::EmptyRoot);
this->decodeNodeRecusively(obj, &this->baseNode_); this->applyFromDescriptorRecursively(rootNode, &this->baseNode_);
} }
void SplitContainer::decodeNodeRecusively(QJsonObject &obj, Node *node) void SplitContainer::applyFromDescriptorRecursively(
const NodeDescriptor &rootNode, Node *node)
{ {
QString type = obj.value("type").toString(); if (std::holds_alternative<SplitNodeDescriptor>(rootNode))
if (type == "split")
{ {
auto *n = std::get_if<SplitNodeDescriptor>(&rootNode);
if (!n)
{
return;
}
const auto &splitNode = *n;
auto *split = new Split(this); auto *split = new Split(this);
split->setChannel( split->setChannel(WindowManager::decodeChannel(splitNode));
WindowManager::decodeChannel(obj.value("data").toObject())); split->setModerationMode(splitNode.moderationMode_);
split->setModerationMode(obj.value("moderationMode").toBool(false));
this->appendSplit(split); this->appendSplit(split);
} }
else if (type == "horizontal" || type == "vertical") else if (std::holds_alternative<ContainerNodeDescriptor>(rootNode))
{ {
bool vertical = type == "vertical"; auto *n = std::get_if<ContainerNodeDescriptor>(&rootNode);
if (!n)
{
return;
}
const auto &containerNode = *n;
bool vertical = containerNode.vertical_;
Direction direction = vertical ? Direction::Below : Direction::Right; Direction direction = vertical ? Direction::Below : Direction::Right;
node->type_ = node->type_ =
vertical ? Node::VerticalContainer : Node::HorizontalContainer; vertical ? Node::VerticalContainer : Node::HorizontalContainer;
for (QJsonValue _val : obj.value("items").toArray()) for (const auto &item : containerNode.items_)
{ {
auto _obj = _val.toObject(); if (std::holds_alternative<SplitNodeDescriptor>(item))
auto _type = _obj.value("type");
if (_type == "split")
{ {
auto *n = std::get_if<SplitNodeDescriptor>(&item);
if (!n)
{
return;
}
const auto &splitNode = *n;
auto *split = new Split(this); auto *split = new Split(this);
split->setChannel(WindowManager::decodeChannel( split->setChannel(WindowManager::decodeChannel(splitNode));
_obj.value("data").toObject())); split->setModerationMode(splitNode.moderationMode_);
split->setModerationMode(
_obj.value("moderationMode").toBool(false));
Node *_node = new Node(); Node *_node = new Node();
_node->parent_ = node; _node->parent_ = node;
_node->split_ = split; _node->split_ = split;
_node->type_ = Node::_Split; _node->type_ = Node::_Split;
_node->flexH_ = _obj.value("flexh").toDouble(1.0); _node->flexH_ = splitNode.flexH_;
_node->flexV_ = _obj.value("flexv").toDouble(1.0); _node->flexV_ = splitNode.flexV_;
node->children_.emplace_back(_node); node->children_.emplace_back(_node);
this->addSplit(split); this->addSplit(split);
@ -753,19 +765,7 @@ void SplitContainer::decodeNodeRecusively(QJsonObject &obj, Node *node)
Node *_node = new Node(); Node *_node = new Node();
_node->parent_ = node; _node->parent_ = node;
node->children_.emplace_back(_node); node->children_.emplace_back(_node);
this->decodeNodeRecusively(_obj, _node); this->applyFromDescriptorRecursively(item, _node);
}
}
for (int i = 0; i < 2; i++)
{
if (node->getChildren().size() < 2)
{
auto *split = new Split(this);
split->setChannel(
WindowManager::decodeChannel(obj.value("data").toObject()));
this->insertSplit(split, direction, node);
} }
} }
} }

View file

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "common/WindowDescriptors.hpp"
#include "widgets/BaseWidget.hpp" #include "widgets/BaseWidget.hpp"
#include <QDragEnterEvent> #include <QDragEnterEvent>
@ -184,8 +185,6 @@ public:
void selectNextSplit(Direction direction); void selectNextSplit(Direction direction);
void setSelected(Split *selected_); void setSelected(Split *selected_);
void decodeFromJson(QJsonObject &obj);
int getSplitCount(); int getSplitCount();
const std::vector<Split *> getSplits() const; const std::vector<Split *> getSplits() const;
@ -201,6 +200,8 @@ public:
static bool isDraggingSplit; static bool isDraggingSplit;
static Split *draggingSplit; static Split *draggingSplit;
void applyFromDescriptor(const NodeDescriptor &rootNode);
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
@ -214,6 +215,9 @@ protected:
void resizeEvent(QResizeEvent *event) override; void resizeEvent(QResizeEvent *event) override;
private: private:
void applyFromDescriptorRecursively(const NodeDescriptor &rootNode,
Node *node);
void layout(); void layout();
void selectSplitRecursive(Node *node, Direction direction); void selectSplitRecursive(Node *node, Direction direction);
void focusSplitRecursive(Node *node); void focusSplitRecursive(Node *node);
@ -221,7 +225,6 @@ private:
void addSplit(Split *split); void addSplit(Split *split);
void decodeNodeRecusively(QJsonObject &obj, Node *node);
Split *getTopRightSplit(Node &node); Split *getTopRightSplit(Node &node);
void refreshTabTitle(); void refreshTabTitle();