From 0a70817ed19d77084a39b411f476f6fc6f0f8c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Sun, 4 Sep 2022 04:06:38 -0300 Subject: [PATCH] Added UIDataBind: simple two way data binding between data and widgets. UIDocFindReplace: first working version completed. TextDocument: Fixed findTextLast when using case-insensitive search. StyleSheet: Added StyleSheet::markerExists. ecode: DocSearchController minor refactor. --- TODO.md | 4 +- include/eepp/ui.hpp | 2 + include/eepp/ui/css/propertydefinition.hpp | 1 + include/eepp/ui/css/stylesheet.hpp | 2 + include/eepp/ui/tools/uidocfindreplace.hpp | 21 ++- include/eepp/ui/uidatabind.hpp | 143 ++++++++++++++++ include/eepp/ui/uimenucheckbox.hpp | 5 + include/eepp/ui/uimenuradiobutton.hpp | 5 + include/eepp/ui/uiwidget.hpp | 4 + projects/linux/ee.files | 1 + src/eepp/ui/css/stylesheet.cpp | 8 + src/eepp/ui/doc/textdocument.cpp | 6 +- src/eepp/ui/tools/uidocfindreplace.cpp | 153 ++++++++++++------ src/eepp/ui/uicheckbox.cpp | 4 + src/eepp/ui/uieventdispatcher.cpp | 8 +- src/eepp/ui/uimenucheckbox.cpp | 36 +++++ src/eepp/ui/uimenuradiobutton.cpp | 31 ++++ src/eepp/ui/uiradiobutton.cpp | 2 + src/eepp/ui/uiselectbutton.cpp | 4 + src/eepp/ui/uiwidget.cpp | 14 ++ src/tools/ecode/docsearchcontroller.cpp | 180 ++++++++++----------- src/tools/ecode/docsearchcontroller.hpp | 21 ++- 22 files changed, 490 insertions(+), 165 deletions(-) create mode 100644 include/eepp/ui/uidatabind.hpp diff --git a/TODO.md b/TODO.md index 8c362cdd4..881545c1d 100644 --- a/TODO.md +++ b/TODO.md @@ -19,7 +19,7 @@ * Implement a rich text view. -* Provide some facilities for basic data-binding. +* Provide some facilities for basic data-binding (WIP). ### CSS @@ -29,8 +29,6 @@ ### UICodeEditor -* Implement and embed a default find-replace widget inside the editor. - * Add better support for double-width characters. * Add new CSS properties related to the widget. diff --git a/include/eepp/ui.hpp b/include/eepp/ui.hpp index 692b0ac64..067a5889e 100644 --- a/include/eepp/ui.hpp +++ b/include/eepp/ui.hpp @@ -82,4 +82,6 @@ #include #include +#include + #endif diff --git a/include/eepp/ui/css/propertydefinition.hpp b/include/eepp/ui/css/propertydefinition.hpp index 41192efe9..fb75d4784 100644 --- a/include/eepp/ui/css/propertydefinition.hpp +++ b/include/eepp/ui/css/propertydefinition.hpp @@ -91,6 +91,7 @@ enum class PropertyId : Uint32 { TabSeparation = String::hash( "tab-separation" ), TabHeight = String::hash( "tab-height" ), Selected = String::hash( "selected" ), + Checked = String::hash( "checked" ), PopUpToRoot = String::hash( "popup-to-root" ), MaxVisibleItems = String::hash( "max-visible-items" ), SelectedIndex = String::hash( "selected-index" ), diff --git a/include/eepp/ui/css/stylesheet.hpp b/include/eepp/ui/css/stylesheet.hpp index f9f28b8ff..6196d3ba3 100644 --- a/include/eepp/ui/css/stylesheet.hpp +++ b/include/eepp/ui/css/stylesheet.hpp @@ -53,6 +53,8 @@ class EE_API StyleSheet { void removeAllWithMarker( const Uint32& marker ); + bool markerExists( const Uint32& marker ) const; + protected: Uint32 mMarker{ 0 }; std::vector> mNodes; diff --git a/include/eepp/ui/tools/uidocfindreplace.hpp b/include/eepp/ui/tools/uidocfindreplace.hpp index c419ea37d..3b3aed9f2 100644 --- a/include/eepp/ui/tools/uidocfindreplace.hpp +++ b/include/eepp/ui/tools/uidocfindreplace.hpp @@ -4,22 +4,27 @@ #include #include #include +#include #include #include #include #include -#include namespace EE { namespace UI { namespace Tools { class EE_API UIDocFindReplace : public UILinearLayout, public WidgetCommandExecuter { public: static std::unordered_map getDefaultKeybindings() { - return { { "mod+g", "repeat-find" }, { "escape", "close-find-replace" }, - { "mod+r", "replace-selection" }, { "mod+shift+n", "find-and-replace" }, - { "mod+shift+r", "replace-all" }, { "mod+s", "change-case" }, - { "mod+w", "change-whole-word" }, { "mod+l", "toggle-lua-pattern" }, - { "mod+e", "change-escape-sequence" } }; + return { { "mod+g", "repeat-find" }, + { "escape", "close-find-replace" }, + { "mod+r", "replace-selection" }, + { "mod+shift+n", "find-and-replace" }, + { "mod+shift+r", "replace-all" }, + { "mod+s", "change-case" }, + { "mod+w", "change-whole-word" }, + { "mod+l", "toggle-lua-pattern" }, + { "mod+e", "change-escape-sequence" }, + { "mod+shift+g", "find-prev" } }; } static UIDocFindReplace* @@ -30,7 +35,7 @@ class EE_API UIDocFindReplace : public UILinearLayout, public WidgetCommandExecu void setDoc( const std::shared_ptr& doc ); - virtual void show(); + virtual void show( bool expanded = false ); virtual void hide(); @@ -58,6 +63,8 @@ class EE_API UIDocFindReplace : public UILinearLayout, public WidgetCommandExecu UIWidget* mToggle{ nullptr }; UIWidget* mReplaceBox{ nullptr }; std::shared_ptr mDoc; + std::vector>> mDataBinds; + std::unique_ptr> mPatternBind; UIDocFindReplace( UIWidget* parent, const std::shared_ptr& doc, diff --git a/include/eepp/ui/uidatabind.hpp b/include/eepp/ui/uidatabind.hpp new file mode 100644 index 000000000..12f825fb8 --- /dev/null +++ b/include/eepp/ui/uidatabind.hpp @@ -0,0 +1,143 @@ +#ifndef EE_UI_UIDATABIND_HPP +#define EE_UI_UIDATABIND_HPP + +#include +#include + +namespace EE { namespace UI { + +template class UIDataBind { + public: + struct Converter { + Converter( + std::function*, T&, const std::string& )> toVal = + []( const UIDataBind*, T& val, const std::string& str ) { + auto base = std::is_same::value ? std::boolalpha : std::dec; + return String::fromString( val, str, base ); + }, + std::function*, std::string&, const T& )> fromVal = + []( const UIDataBind*, std::string& str, const T& val ) { + str = String::toString( val ); + return true; + } ) : + toVal( toVal ), fromVal( fromVal ) {} + std::function*, T&, const std::string& )> toVal; + std::function*, std::string&, const T& )> fromVal; + }; + + UIDataBind( T* t, std::set widgets, const Converter& converter = {}, + const std::string& valueKey = "value" ) : + data( t ), + widgets( widgets ), + property( StyleSheetSpecification::instance()->getProperty( valueKey ) ), + converter( converter ) { + for ( auto widget : widgets ) + bindListeners( widget ); + set( *data ); + } + + UIDataBind( T* t, UIWidget* widget, const Converter& converter = {}, + const std::string& valueKey = "value" ) : + data( t ), + widgets( { widget } ), + property( StyleSheetSpecification::instance()->getProperty( valueKey ) ), + converter( converter ) { + for ( auto widget : widgets ) + bindListeners( widget ); + set( *data ); + } + + void set( const T& t ) { + inSetValue = true; + *data = t; + setValueChange(); + inSetValue = false; + } + + const T& get() const { return *data; } + + void bind( UIWidget* widget ) { + bindListeners( widget ); + widgets.insert( widget ); + inSetValue = true; + widget->applyProperty( StyleSheetProperty( property, String::toString( data ) ) ); + inSetValue = false; + } + + void unbind( UIWidget* widget ) { + if ( widgets.find( widget ) == widgets.end() ) + return; + widget->removeEventListener( valueCbs[widget] ); + widget->removeEventListener( closeCbs[widget] ); + valueCbs.erase( widget ); + closeCbs.erase( widget ); + widgets.erase( widget ); + } + + ~UIDataBind() { + for ( auto widget : widgets ) { + widget->removeEventListener( valueCbs[widget] ); + widget->removeEventListener( closeCbs[widget] ); + } + } + + const PropertyDefinition* getPropertyDefinition() const { return property; } + + protected: + T* data; + std::set widgets; + std::map valueCbs; + std::map closeCbs; + bool inSetValue{ false }; + const PropertyDefinition* property{ nullptr }; + Converter converter; + + void bindListeners( UIWidget* widget ) { + valueCbs[widget] = + widget->addEventListener( Event::OnValueChange, [this]( const Event* event ) { + processValueChange( event->getNode()->asType() ); + } ); + closeCbs[widget] = widget->addEventListener( Event::OnClose, [this]( const Event* event ) { + closeCbs.erase( event->getNode()->asType() ); + this->widgets.erase( event->getNode()->asType() ); + } ); + } + + std::string dataToString() const { + std::string str; + if ( !converter.fromVal( this, str, *data ) ) { + Log::error( "UIDataBind::dataToString converter::fromVal: unable to convert value " + "to string." ); + } + return str; + } + + void processValueChange( UIWidget* emitter ) { + if ( inSetValue ) + return; + bool success = false; + T val; + success = converter.toVal( this, val, emitter->getPropertyString( property ) ); + + if ( success ) { + *data = val; + StyleSheetProperty prop( property, dataToString() ); + inSetValue = true; + for ( auto widget : widgets ) { + if ( widget != emitter ) + widget->applyProperty( prop ); + } + inSetValue = false; + } + } + + void setValueChange() { + StyleSheetProperty prop( property, dataToString() ); + for ( auto widget : widgets ) + widget->applyProperty( prop ); + } +}; + +}} // namespace EE::UI + +#endif // EE_UI_UIDATABIND_HPP diff --git a/include/eepp/ui/uimenucheckbox.hpp b/include/eepp/ui/uimenucheckbox.hpp index 7c0a4672e..e2a466808 100644 --- a/include/eepp/ui/uimenucheckbox.hpp +++ b/include/eepp/ui/uimenucheckbox.hpp @@ -25,6 +25,11 @@ class EE_API UIMenuCheckBox : public UIMenuItem { void switchActive(); + virtual bool applyProperty( const StyleSheetProperty& attribute ); + + virtual std::string getPropertyString( const PropertyDefinition* propertyDef, + const Uint32& propertyIndex ) const; + protected: bool mActive; UISkin* mSkinActive; diff --git a/include/eepp/ui/uimenuradiobutton.hpp b/include/eepp/ui/uimenuradiobutton.hpp index 582b29795..87fa673a4 100644 --- a/include/eepp/ui/uimenuradiobutton.hpp +++ b/include/eepp/ui/uimenuradiobutton.hpp @@ -25,6 +25,11 @@ class EE_API UIMenuRadioButton : public UIMenuItem { void switchActive(); + virtual bool applyProperty( const StyleSheetProperty& attribute ); + + virtual std::string getPropertyString( const PropertyDefinition* propertyDef, + const Uint32& propertyIndex = 0 ) const; + protected: bool mActive; UISkin* mSkinActive; diff --git a/include/eepp/ui/uiwidget.hpp b/include/eepp/ui/uiwidget.hpp index 1f0de25fb..9e4fa143c 100644 --- a/include/eepp/ui/uiwidget.hpp +++ b/include/eepp/ui/uiwidget.hpp @@ -215,6 +215,8 @@ class EE_API UIWidget : public UINode { bool isTabStop() const; + void setTabStop(); + UIWidget* getNextTabWidget() const; bool hasPseudoClass( const std::string& pseudoCls ) const; @@ -254,6 +256,8 @@ class EE_API UIWidget : public UINode { virtual void onChildCountChange( Node* child, const bool& removed ); + virtual Uint32 onKeyDown( const KeyEvent& event ); + virtual Uint32 onMouseMove( const Vector2i& Pos, const Uint32& Flags ); virtual Uint32 onMouseOver( const Vector2i& Pos, const Uint32& Flags ); diff --git a/projects/linux/ee.files b/projects/linux/ee.files index b02a348ed..f1f4ea65b 100644 --- a/projects/linux/ee.files +++ b/projects/linux/ee.files @@ -361,6 +361,7 @@ ../../include/eepp/ui/uicodeeditor.hpp ../../include/eepp/ui/uicombobox.hpp ../../include/eepp/ui/uiconsole.hpp +../../include/eepp/ui/uidatabind.hpp ../../include/eepp/ui/uidropdownlist.hpp ../../include/eepp/ui/uieventdispatcher.hpp ../../include/eepp/ui/uifiledialog.hpp diff --git a/src/eepp/ui/css/stylesheet.cpp b/src/eepp/ui/css/stylesheet.cpp index 286edc255..1fdefeae0 100644 --- a/src/eepp/ui/css/stylesheet.cpp +++ b/src/eepp/ui/css/stylesheet.cpp @@ -98,6 +98,14 @@ void StyleSheet::removeAllWithMarker( const Uint32& marker ) { invalidateCache(); } +bool StyleSheet::markerExists( const Uint32& marker ) const { + for ( auto node : mNodes ) { + if ( node->getMarker() == marker ) + return true; + } + return false; +} + bool StyleSheet::addStyleToNodeIndex( StyleSheetStyle* style ) { const std::string& id = style->getSelector().getSelectorId(); const std::string& tag = style->getSelector().getSelectorTagName(); diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index 044815508..9b1fb7dc6 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -1579,11 +1579,13 @@ TextRange TextDocument::findTextLast( String text, TextPosition from, const bool if ( i == from.line() ) { col = caseSensitive ? findLastType( line( i ).getText().substr( 0, from.column() ), text, type ) - : findLastType( String::toLower( line( i ).getText() ), text, type ); + : findLastType( + String::toLower( line( i ).getText().substr( 0, from.column() ) ), text, + type ); } else if ( i == to.line() ) { col = caseSensitive ? findLastType( line( i ).getText().substr( to.column() ), text, type ) - : findLastType( String::toLower( line( i ).getText() ).substr( to.column() ), + : findLastType( String::toLower( line( i ).getText().substr( to.column() ) ), text, type ); if ( String::StringType::npos != col.first ) { col.first += to.column(); diff --git a/src/eepp/ui/tools/uidocfindreplace.cpp b/src/eepp/ui/tools/uidocfindreplace.cpp index 74c4624dc..ac01d182e 100644 --- a/src/eepp/ui/tools/uidocfindreplace.cpp +++ b/src/eepp/ui/tools/uidocfindreplace.cpp @@ -1,14 +1,15 @@ -#include "eepp/window/clipboard.hpp" #include +#include #include #include +#include #include namespace EE { namespace UI { namespace Tools { -const char DOC_FIND_REPLACE_XML[] = R"xml( - +.ce_find_replace_box .input-find.error, +.ce_find_replace_box .input-replace.error { + border-color: #ff4040; +} +)css"; + +const char DOC_FIND_REPLACE_XML[] = R"xml(