Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 40 additions & 40 deletions data/main.glade
Original file line number Diff line number Diff line change
Expand Up @@ -5238,26 +5238,26 @@ Ships empty; place theme folders here, or point this anywhere you keep them.</pr
<property name="tooltip-text" translatable="yes">When this option is enabled, empty directories will be preserved when saving the project.</property>
<property name="spacing">5</property>
<child>
<object class="GtkSwitch" id="SwitchSettingsPreserveEmptyDir">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label">Preserve empty directories</property>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<object class="GtkSwitch" id="SwitchSettingsPreserveEmptyDir">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label">Preserve empty directories</property>
<property name="can-focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
Expand Down Expand Up @@ -5287,26 +5287,26 @@ Ships empty; place theme folders here, or point this anywhere you keep them.</pr
<property name="tooltip-text" translatable="yes">When this option is enabled, components that are empty or fail validation checks will be automatically removed.</property>
<property name="spacing">5</property>
<child>
<object class="GtkSwitch" id="SwitchSettingsRemoveInvalidItems">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label">Remove invalid or empty components</property>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<object class="GtkSwitch" id="SwitchSettingsRemoveInvalidItems">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label">Remove invalid or empty components</property>
<property name="can-focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
Expand All @@ -5324,26 +5324,26 @@ Ships empty; place theme folders here, or point this anywhere you keep them.</pr
<property name="tooltip-text" translatable="yes">When this option is enabled, a backup of the current project files will be created before saving changes.</property>
<property name="spacing">5</property>
<child>
<object class="GtkSwitch" id="SwitchSettingsSaveBackup">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label">Backup project before saving</property>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<object class="GtkSwitch" id="SwitchSettingsSaveBackup">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label">Backup project before saving</property>
<property name="can-focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
Expand Down Expand Up @@ -5373,26 +5373,26 @@ Ships empty; place theme folders here, or point this anywhere you keep them.</pr
<property name="tooltip-text" translatable="yes">When enabled, generated files will be displayed in a dialog instead of being saved, and the project directory will remain unchanged.</property>
<property name="spacing">5</property>
<child>
<object class="GtkSwitch" id="SwitchSettingsDebugFiles">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label">Display files instead of saving</property>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<object class="GtkSwitch" id="SwitchSettingsDebugFiles">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label">Display files instead of saving</property>
<property name="can-focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
Expand All @@ -5410,26 +5410,26 @@ Ships empty; place theme folders here, or point this anywhere you keep them.</pr
<property name="tooltip-text" translatable="yes">When enabled, hardware test actions (lighting and clearing elements, restrictor moves) are shown in the status bar.</property>
<property name="spacing">5</property>
<child>
<object class="GtkSwitch" id="SwitchSettingsDebugHardwareTest">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label">Show hardware test actions</property>
</object>
<packing>
<property name="expand">False</property>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<object class="GtkSwitch" id="SwitchSettingsDebugHardwareTest">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="halign">end</property>
<property name="label">Show hardware test actions</property>
<property name="can-focus">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
Expand Down
1 change: 0 additions & 1 deletion src/Ui/DataDialogs/DialogLinkEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ void DialogLinkEditor::open(Storage::Link* link) noexcept {
return;
}

// Store TODO verify
for (const auto& field : link->getLinkFields()) {
switch (field.widgetType) {
case Storage::Link::LinkField::Widget::COLOR_PICKER:
Expand Down
19 changes: 12 additions & 7 deletions src/Ui/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,16 @@ void MainWindow::prepareDialogs(Glib::RefPtr<Gtk::Builder> const &builder) {
}
const string& newProject {DialogProject::getInstance()->getProjectName()};
if (newProject == Settings::get().getCurrentProject()) {
// TODO add revert option, instead of warning, ask to reload without saving.
Message::displayInfo("Already working on that project", DialogProject::getInstance());
if (not Defaults::isDirty()) {
Message::displayInfo("Already working on that project", DialogProject::getInstance());
DialogProject::getInstance()->hide();
return;
}
if (Message::ask("Discard unsaved changes and reload \"" + newProject + "\" from disk?", DialogProject::getInstance()) != Gtk::ResponseType::RESPONSE_YES) {
DialogProject::getInstance()->hide();
return;
}
openProject(newProject);
DialogProject::getInstance()->hide();
return;
}
Expand All @@ -364,10 +372,10 @@ void MainWindow::setConfiguration(const Values& values) {
comboColors->set_active_id(values.getValue("colors", DEFAULT_COLORS));
comboLogLevel->set_active_id(values.getValue("logLevel", DEFAULT_LOGLEVEL));
listBoxDataSource->sortAndMark(Defaults::explode(values.getValue("dataSource", DEFAULT_DATASOURCE), ','));
DialogColors::getInstance()->wipeColorPicker(boxRandomColors);
auto randomColors(Defaults::explode(values.getValue("randomColors"), ','));
if (not randomColors.empty()) {
if (not randomColors.empty())
DialogColors::getInstance()->populateColorBox(boxRandomColors, randomColors);
}

// DEFAULT_PROFILE
// emitter
Expand Down Expand Up @@ -721,10 +729,7 @@ void MainWindow::readConfigFile(const string& dataFilePath, bool wipe, uint8_t i

// Load inputs, animations and profiles from the project subdirectories.
if (wipe and not Settings::get().getProjectDir().empty()) {
inputNavigator.clear();
animationNavigator.clear();
profileNavigator.setDefaultProfileName(datafile.getRootInfo().getValue("defaultProfile"));
profileNavigator.clear();

inputNavigator.load();
animationNavigator.load();
Expand Down
11 changes: 9 additions & 2 deletions src/XMLHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,20 @@ string XMLHelper::xmlHeader(const string& type, const Values& attrs) noexcept {
string XMLHelper::xmlSection(
const string& tag,
const string& content,
const Values& attrs
const Values& attrs,
bool allowEmpty
) noexcept {

if (content.empty()) return "";
if (content.empty() and not allowEmpty) return "";
string r(Defaults::tab() + "<" + tag);
for (const auto& [k, v] : attrs)
r += " " + k + "=\"" + Defaults::escapeXmlValue(v) + "\"";

if (content.empty()) {
r += "/>\n";
return r;
}

r += ">\n";
Defaults::increaseTab();
// Fixes indentation for multi-line content, adds a tab if the line is not empty.
Expand Down
13 changes: 8 additions & 5 deletions src/XMLHelper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,18 @@ class XMLHelper : protected tinyxml2::XMLDocument {

/**
* Wraps content in a named XML section.
* Skips output entirely if content is empty.
* @param tag Element tag name.
* @param content Inner XML string.
* @param attrs Optional attributes on the opening tag.
* Skips output entirely if content is empty, unless allowEmpty is set, in
* which case a self-closing tag with just the attributes is emitted instead.
* @param tag Element tag name.
* @param content Inner XML string.
* @param attrs Optional attributes on the opening tag.
* @param allowEmpty Emit the tag even when content is empty.
*/
static string xmlSection(
const string& tag,
const string& content,
const Values& attrs = {}
const Values& attrs = {},
bool allowEmpty = false
) noexcept;

/**
Expand Down
56 changes: 53 additions & 3 deletions src/config/ConfigFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
*/

#include "ConfigFile.hpp"
#include "ProjectFile.hpp"

using namespace LEDSpicerUI::Config;

Expand Down Expand Up @@ -252,6 +251,35 @@ string ConfigFile::processLayoutAndGroups() {
return errors;
}

vector<LEDSpicerUI::Ui::Storage::Data*> ConfigFile::expandDeviceElements(const BoxButtonCollection& devices) noexcept {
vector<Ui::Storage::Data*> order;
for (const auto deviceBtn : devices) {
for (const auto elementBtn : *static_cast<Ui::Storage::Parent*>(deviceBtn->getData())->getPrimaryChild()) {
auto element {static_cast<Ui::Storage::Element*>(elementBtn->getData())};
auto children {element->copyStripChildren()};
if (children.empty())
order.push_back(element);
else
for (auto child : children)
order.push_back(child);
}
}
return order;
}

bool ConfigFile::matchesDeviceOrder(
const BoxButtonCollection& links,
const vector<Ui::Storage::Data*>& deviceElementOrder
) noexcept {
return std::equal(
links.begin(), links.end(),
deviceElementOrder.begin(), deviceElementOrder.end(),
[](Ui::Storage::BoxButton* linkBtn, Ui::Storage::Data* element) {
return *linkBtn->getData() == *element;
}
);
}

void ConfigFile::save(const ConfigData& data) {

// Both Collections are app wide.
Expand Down Expand Up @@ -300,12 +328,34 @@ void ConfigFile::save(const ConfigData& data) {

// Devices (optional if restrictors are present)
xmlData += xmlSection("devices", collectParents(data.devices));
// Layout with groups

auto deviceElementOrder {expandDeviceElements(data.devices)};

// Same as collectParents, but omits All when it's just the default order.
const auto collectGroups {[&deviceElementOrder](const BoxButtonCollection& col) {
string r;
for (const auto btn : col) {
auto group {static_cast<Ui::Storage::Parent*>(btn->getData())};
auto links {group->getPrimaryChild()};
if (not links->getSize()) continue;

if (btn->getData()->getValue(NAME) == GROUP_ALL_NAME and matchesDeviceOrder(*links, deviceElementOrder))
continue;

r += btn->getData()->toXML();
}
return r;
}};

/*
Layout with groups. defaultProfile is required whenever devices exist, so the
tag must survive even when every group (including All) is empty or the default.
*/
Values layoutAttrs;
if (not data.defaultProject.empty())
layoutAttrs.setValue("defaultProject", data.defaultProject);
layoutAttrs.setValue("defaultProfile", data.defaultProfile);
xmlData += xmlSection("layout", collectParents(data.groups), layoutAttrs);
xmlData += xmlSection("layout", collectGroups(data.groups), layoutAttrs, true);
}

Defaults::reduceTab();
Expand Down
24 changes: 23 additions & 1 deletion src/config/ConfigFile.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "ProjectFile.hpp"
#include "Storage/BoxButtonCollection.hpp"
#include "XMLHelper.hpp"
#include "Storage/Element.hpp"

#pragma once

Expand Down Expand Up @@ -82,6 +83,27 @@ class ConfigFile : public XMLHelper {

static void save(const ConfigData& data);

/**
* Walks devices in order, expanding each strip into its children (never the
* parent, which carries PROP_NO_SELECT and is never linkable itself) — the
* order the daemon uses when it auto-builds All from the file.
* @param devices Devices collection, in save/file order.
* @return Flat, ordered list of every linkable element.
*/
static vector<Ui::Storage::Data*> expandDeviceElements(
const BoxButtonCollection& devices
) noexcept;

/**
* @param links A group's links collection, in its current order.
* @param deviceElementOrder Result of expandDeviceElements().
* @return True if links is identical to deviceElementOrder, position for position.
*/
static bool matchesDeviceOrder(
const BoxButtonCollection& links,
const vector<Ui::Storage::Data*>& deviceElementOrder
) noexcept;

protected:

string processDevices();
Expand Down
Loading