Add new pref="" attribute to <check> widgets to bind check boxes with bool preference options automatically

With this change we've moved the propagateToChildren/propagateToParent
flags from ui::KeyMessage to ui::Message so anykind of
message (e.g. user defined messages like kSavePreferencesMessage) can
use these flags (processed by ui::Widget::onProcessMessage()).
This commit is contained in:
David Capello 2018-06-06 15:44:52 -03:00
parent 05f6aec8b2
commit 7a35eb26a1
11 changed files with 228 additions and 51 deletions

View File

@ -145,8 +145,10 @@
<!-- Timeline -->
<vbox id="section_timeline">
<separator text="@.section_timeline" horizontal="true" />
<check text="@.autotimeline" id="autotimeline" tooltip="@.autotimeline_tooltip" />
<check text="@.rewind_on_stop" id="rewind_on_stop" tooltip="@.rewind_on_stop_tooltip" />
<check text="@.autotimeline" id="autotimeline" tooltip="@.autotimeline_tooltip"
pref="general.autoshow_timeline" />
<check text="@.rewind_on_stop" id="rewind_on_stop" tooltip="@.rewind_on_stop_tooltip"
pref="general.rewind_on_stop" />
<hbox>
<label text="@.default_first_frame" />
<entry id="first_frame" maxsize="3" />

View File

@ -366,6 +366,7 @@ if(ENABLE_UI)
ui/palette_view.cpp
ui/palettes_listbox.cpp
ui/popup_window_pin.cpp
ui/pref_widget.cpp
ui/preview_editor.cpp
ui/recent_listbox.cpp
ui/resources_listbox.cpp

View File

@ -22,6 +22,7 @@
#include "app/recent_files.h"
#include "app/resource_finder.h"
#include "app/ui/color_button.h"
#include "app/ui/pref_widget.h"
#include "app/ui/separator_in_view.h"
#include "app/ui/skin/skin_theme.h"
#include "base/bind.h"
@ -188,12 +189,6 @@ public:
defaultSliceColor()->setColor(m_pref.slices.defaultColor());
// Others
if (m_pref.general.autoshowTimeline())
autotimeline()->setSelected(true);
if (m_pref.general.rewindOnStop())
rewindOnStop()->setSelected(true);
firstFrame()->setTextf("%d", m_globPref.timeline.firstFrame());
if (m_pref.general.expandMenubarOnMouseover())
@ -397,12 +392,17 @@ public:
}
void saveConfig() {
// Save preferences in widgets that are bound to options automatically
{
Message* msg = new Message(kSavePreferencesMessage);
msg->setPropagateToChildren(msg);
sendMessage(msg);
}
// Update language
Strings::instance()->setCurrentLanguage(
language()->getItemText(language()->getSelectedItemIndex()));
m_pref.general.autoshowTimeline(autotimeline()->isSelected());
m_pref.general.rewindOnStop(rewindOnStop()->isSelected());
m_globPref.timeline.firstFrame(firstFrame()->textInt());
m_pref.general.showFullPath(showFullPath()->isSelected());
m_pref.saveFile.defaultExtension(getExtension(defaultExtension()));

View File

@ -13,11 +13,17 @@
namespace app {
class OptionBase;
class Section {
public:
Section(const std::string& name) : m_name(name) { }
virtual ~Section() { }
const char* name() const { return m_name.c_str(); }
virtual Section* section(const char* id) = 0;
virtual OptionBase* option(const char* id) = 0;
obs::signal<void()> BeforeChange;
obs::signal<void()> AfterChange;
@ -31,6 +37,7 @@ namespace app {
: m_section(section)
, m_id(id) {
}
virtual ~OptionBase() { }
const char* section() const { return m_section->name(); }
const char* id() const { return m_id; }
protected:

View File

@ -0,0 +1,17 @@
// Aseprite
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/pref_widget.h"
namespace app {
ui::RegisterMessage kSavePreferencesMessage;
} // namespace app

70
src/app/ui/pref_widget.h Normal file
View File

@ -0,0 +1,70 @@
// Aseprite
// Copyright (C) 2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_PREF_WIDGET_H_INCLUDED
#define APP_UI_PREF_WIDGET_H_INCLUDED
#pragma once
#include "app/pref/preferences.h"
#include "base/split_string.h"
#include "base/exception.h"
#include "ui/message.h"
#include "ui/register_message.h"
namespace app {
extern ui::RegisterMessage kSavePreferencesMessage;
template<class Base>
class BoolPrefWidget : public Base {
public:
template<typename...Args>
BoolPrefWidget(Args&&...args)
: Base(args...)
, m_option(nullptr) {
}
void setPref(const char* prefString) {
ASSERT(prefString);
std::vector<std::string> parts;
base::split_string(prefString, parts, ".");
if (parts.size() == 2) {
auto& pref = Preferences::instance();
auto section = pref.section(parts[0].c_str());
if (!section)
throw base::Exception("Preference section not found: %s", prefString);
m_option =
dynamic_cast<Option<bool>*>(
section->option(parts[1].c_str()));
if (!m_option)
throw base::Exception("Preference option not found: %s", prefString);
// Load option value
this->setSelected((*m_option)());
}
}
protected:
bool onProcessMessage(ui::Message* msg) override {
if (msg->type() == kSavePreferencesMessage) {
ASSERT(m_option);
// Update Option value.
(*m_option)(this->isSelected());
}
return Base::onProcessMessage(msg);
}
private:
Option<bool>* m_option;
};
} // namespace app
#endif

View File

@ -1,5 +1,5 @@
// Aseprite
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@ -8,6 +8,8 @@
#include "config.h"
#endif
#include "app/ui/pref_widget.h"
#include "app/widget_loader.h"
#include "app/app.h"
@ -184,15 +186,29 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
}
}
else if (elem_name == "check") {
const char *looklike = elem->Attribute("looklike");
const char* looklike = elem->Attribute("looklike");
const char* pref = elem->Attribute("pref");
ASSERT(!widget || !pref); // widget && pref is not supported
if (looklike != NULL && strcmp(looklike, "button") == 0) {
ASSERT(!pref); // not supported yet
if (!widget)
widget = new CheckBox("", kButtonWidget);
}
else {
if (!widget)
widget = new CheckBox("");
if (!widget) {
// Automatic bind <check> widget with bool preference option
if (pref) {
auto prefWidget = new BoolPrefWidget<CheckBox>("");
prefWidget->setPref(pref);
widget = prefWidget;
}
else {
widget = new CheckBox("");
}
}
}
bool center = bool_attr_is_true(elem, "center");

View File

@ -1,5 +1,5 @@
// Aseprite Code Generator
// Copyright (c) 2014-2016 David Capello
// Copyright (c) 2014-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -29,7 +29,9 @@ static void print_pref_class_def(TiXmlElement* elem, const std::string& classNam
std::cout
<< indent << " void load();\n"
<< indent << " void save();\n";
<< indent << " void save();\n"
<< indent << " Section* section(const char* id) override;\n"
<< indent << " OptionBase* option(const char* id) override;\n";
TiXmlElement* child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
while (child) {
@ -100,6 +102,8 @@ static void print_pref_class_impl(TiXmlElement* elem, const std::string& prefix,
<< "{\n"
<< "}\n";
// Section::load()
std::cout
<< "\n"
<< "void " << prefix << className << "::load()\n"
@ -136,7 +140,11 @@ static void print_pref_class_impl(TiXmlElement* elem, const std::string& prefix,
std::cout
<< "}\n"
<< "\n"
<< "\n";
// Section::save()
std::cout
<< "void " << prefix << className << "::save()\n"
<< "{\n";
@ -157,8 +165,58 @@ static void print_pref_class_impl(TiXmlElement* elem, const std::string& prefix,
}
std::cout
<< "}\n"
<< "\n";
// Section::section(id)
std::cout
<< "Section* " << prefix << className << "::section(const char* id)\n"
<< "{\n";
child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
while (child) {
if (child->Value()) {
std::string name = child->Value();
const char* childId = child->Attribute("id");
if (name == "section") {
std::string memberName = convert_xmlid_to_cppid(childId, false);
std::cout << " if (std::strcmp(id, " << memberName << ".name()) == 0) return &" << memberName << ";\n";
}
}
child = child->NextSiblingElement();
}
std::cout
<< " return nullptr;\n"
<< "}\n"
<< "\n";
// Section::option(id)
std::cout
<< "OptionBase* " << prefix << className << "::option(const char* id)\n"
<< "{\n";
child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
while (child) {
if (child->Value()) {
std::string name = child->Value();
const char* childId = child->Attribute("id");
if (name == "option") {
std::string memberName = convert_xmlid_to_cppid(childId, false);
std::cout << " if (std::strcmp(id, " << memberName << ".id()) == 0) return &" << memberName << ";\n";
}
}
child = child->NextSiblingElement();
}
std::cout
<< " return nullptr;\n"
<< "}\n";
// Sub-sections
child = (elem->FirstChild() ? elem->FirstChild()->ToElement(): NULL);
while (child) {
if (child->Value()) {
@ -253,6 +311,8 @@ void gen_pref_impl(TiXmlDocument* doc, const std::string& inputFn)
<< "#include \"app/pref/option_io.h\"\n"
<< "#include \"app/pref/preferences.h\"\n"
<< "\n"
<< "#include <cstring>\n"
<< "\n"
<< "namespace app {\n"
<< "namespace gen {\n";

View File

@ -1,5 +1,5 @@
// Aseprite UI Library
// Copyright (C) 2001-2017 David Capello
// Copyright (C) 2001-2018 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -21,8 +21,7 @@ namespace ui {
Message::Message(MessageType type, KeyModifiers modifiers)
: m_type(type)
, m_used(false)
, m_fromFilter(false)
, m_flags(0)
{
if (modifiers == kKeyUninitializedModifier && she::instance())
m_modifiers = she::instance()->keyModifiers();
@ -77,9 +76,8 @@ KeyMessage::KeyMessage(MessageType type,
, m_unicodeChar(unicodeChar)
, m_repeat(repeat)
, m_isDead(false)
, m_propagate_to_children(false)
, m_propagate_to_parent(true)
{
setPropagateToParent(true);
}
} // namespace ui

View File

@ -24,6 +24,12 @@ namespace ui {
class Widget;
class Message {
enum Flags {
Used = 1, // Message already used/processed by one widget
FromFilter = 2, // Sent from pre-filter
PropagateToChildren = 4,
PropagateToParent = 8,
};
public:
typedef WidgetsList::iterator& recipients_iterator;
@ -34,10 +40,10 @@ namespace ui {
MessageType type() const { return m_type; }
const WidgetsList& recipients() const { return m_recipients; }
bool hasRecipients() const { return !m_recipients.empty(); }
bool isUsed() const { return m_used; }
bool fromFilter() const { return m_fromFilter; }
void setFromFilter(bool state) { m_fromFilter = state; }
void markAsUsed() { m_used = true; }
bool isUsed() const { return hasFlag(Used); }
bool fromFilter() const { return hasFlag(FromFilter); }
void setFromFilter(const bool state) { setFlag(FromFilter, state); }
void markAsUsed() { setFlag(Used, true); }
KeyModifiers modifiers() const { return m_modifiers; }
bool shiftPressed() const { return (m_modifiers & kKeyShiftModifier) == kKeyShiftModifier; }
bool ctrlPressed() const { return (m_modifiers & kKeyCtrlModifier) == kKeyCtrlModifier; }
@ -56,11 +62,23 @@ namespace ui {
void broadcastToChildren(Widget* widget);
bool propagateToChildren() const { return hasFlag(PropagateToChildren); }
bool propagateToParent() const { return hasFlag(PropagateToParent); }
void setPropagateToChildren(const bool state) { setFlag(PropagateToChildren, state); }
void setPropagateToParent(const bool state) { setFlag(PropagateToParent, state); }
private:
bool hasFlag(const Flags flag) const {
return (m_flags & flag) == flag;
}
void setFlag(const Flags flag, const bool state) {
m_flags = (state ? (m_flags | flag):
(m_flags & ~flag));
}
MessageType m_type; // Type of message
WidgetsList m_recipients; // List of recipients of the message
bool m_used; // Was used
bool m_fromFilter; // Sent from pre-filter
int m_flags; // Was used
KeyModifiers m_modifiers; // Key modifiers pressed when message was created
};
@ -77,18 +95,12 @@ namespace ui {
int repeat() const { return m_repeat; }
bool isDeadKey() const { return m_isDead; }
void setDeadKey(bool state) { m_isDead = state; }
bool propagateToChildren() const { return m_propagate_to_children; }
bool propagateToParent() const { return m_propagate_to_parent; }
void setPropagateToChildren(bool flag) { m_propagate_to_children = flag; }
void setPropagateToParent(bool flag) { m_propagate_to_parent = flag; }
private:
KeyScancode m_scancode;
int m_unicodeChar;
int m_repeat; // repeat=0 means the first time the key is pressed
bool m_isDead;
bool m_propagate_to_children;
bool m_propagate_to_parent;
};
class PaintMessage : public Message {

View File

@ -1423,23 +1423,6 @@ bool Widget::onProcessMessage(Message* msg)
return paintEvent(graphics.get(), false);
}
case kKeyDownMessage:
case kKeyUpMessage:
if (static_cast<KeyMessage*>(msg)->propagateToChildren()) {
// Broadcast the message to the children.
for (auto child : m_children)
if (child->sendMessage(msg))
return true;
}
// Propagate the message to the parent.
if (static_cast<KeyMessage*>(msg)->propagateToParent() &&
parent()) {
return parent()->sendMessage(msg);
}
else
break;
case kDoubleClickMessage: {
// Convert double clicks into mouse down
MouseMessage* mouseMsg = static_cast<MouseMessage*>(msg);
@ -1475,6 +1458,17 @@ bool Widget::onProcessMessage(Message* msg)
}
// Broadcast the message to the children.
if (msg->propagateToChildren()) {
for (auto child : m_children)
if (child->sendMessage(msg))
return true;
}
// Propagate the message to the parent.
if (msg->propagateToParent() && parent())
return parent()->sendMessage(msg);
return false;
}