diff --git a/.ecode/project_build.json b/.ecode/project_build.json
index 81c527aa9..f10d306cd 100644
--- a/.ecode/project_build.json
+++ b/.ecode/project_build.json
@@ -369,6 +369,12 @@
"command": "${project_root}/bin/eepp-ui-markdownview-debug",
"name": "eepp-ui-markdownview-debug",
"working_dir": "${project_root}/bin"
+ },
+ {
+ "args": "",
+ "command": "${project_root}/bin/eepp-ui-dropdownmodellist-debug",
+ "name": "eepp-ui-dropdownmodellist-debug",
+ "working_dir": "${project_root}/bin"
}
],
"var": {
diff --git a/bin/assets/plugins/aiassistant.json b/bin/assets/plugins/aiassistant.json
index 06af6c662..882de7e68 100644
--- a/bin/assets/plugins/aiassistant.json
+++ b/bin/assets/plugins/aiassistant.json
@@ -469,6 +469,10 @@
{
"name": "arcee-ai/trinity-large-preview:free",
"display_name": "Arcee AI: Trinity Large Preview (free)"
+ },
+ {
+ "name": "nvidia/nemotron-3-super-120b-a12b:free",
+ "display_name": "NVIDIA: Nemotron 3 Super (free)"
}
]
},
diff --git a/bin/assets/ui/breeze.css b/bin/assets/ui/breeze.css
index f0758618a..49e06d3fc 100644
--- a/bin/assets/ui/breeze.css
+++ b/bin/assets/ui/breeze.css
@@ -244,7 +244,8 @@ Anchor:hover {
PushButton,
SelectButton,
-DropDownList {
+DropDownList,
+DropDownModelList {
padding-left: var(--base-horizontal-padding);
padding-right: var(--base-horizontal-padding);
padding-top: var(--base-vertical-padding);
@@ -259,6 +260,7 @@ DropDownList {
}
DropDownList,
+DropDownModelList,
ComboBox::DropDownList {
max-visible-items: 6;
}
@@ -267,6 +269,8 @@ PushButton:hover,
PushButton:focus,
DropDownList:hover,
DropDownList:focus,
+DropDownModelList:hover,
+DropDownModelList:focus,
SelectButton:hover,
SelectButton:focus,
ComboBox:hover,
@@ -800,12 +804,14 @@ Window::border::bottom {
background-color: var(--separator);
}
-DropDownList {
+DropDownList,
+DropDownModelList {
padding-right: 16dp;
text-overflow: ellipsis;
}
DropDownList,
+DropDownModelList,
ComboBox::Button {
foreground-image: url("data:image/svg,");
foreground-position-x: right 6dp;
@@ -816,6 +822,8 @@ ComboBox::Button {
DropDownList:hover,
DropDownList:focus,
+DropDownModelList:hover,
+DropDownModelList:focus,
ComboBox::Button:focus,
ComboBox::Button:hover {
foreground-tint: var(--icon-active);
diff --git a/include/eepp/core/containers.hpp b/include/eepp/core/containers.hpp
index 10d52d1ad..db0524ed5 100644
--- a/include/eepp/core/containers.hpp
+++ b/include/eepp/core/containers.hpp
@@ -1,7 +1,9 @@
#ifndef EE_CONTAINERS_HPP
#define EE_CONTAINERS_HPP
-#ifdef EEPP_NO_THIRDPARTY_CONTAINERS
+#include
+
+#if defined( EEPP_NO_THIRDPARTY_CONTAINERS ) || ( defined( EE_DEBUG ) && defined( EE_COMPILER_MSVC ) )
#include
#include
#else
@@ -11,7 +13,7 @@
namespace EE {
-#ifdef EEPP_NO_THIRDPARTY_CONTAINERS
+#if defined( EEPP_NO_THIRDPARTY_CONTAINERS ) || ( defined( EE_DEBUG ) && defined( EE_COMPILER_MSVC ) )
template using UnorderedMap = std::unordered_map;
diff --git a/include/eepp/ui/abstract/uiabstracttableview.hpp b/include/eepp/ui/abstract/uiabstracttableview.hpp
index 9c85f953a..4df910bc2 100644
--- a/include/eepp/ui/abstract/uiabstracttableview.hpp
+++ b/include/eepp/ui/abstract/uiabstracttableview.hpp
@@ -13,6 +13,7 @@ using namespace EE::Math;
namespace EE { namespace UI {
class UIPushButton;
class UILinearLayout;
+class UIDropDownModelList;
}} // namespace EE::UI
namespace EE { namespace UI { namespace Abstract {
@@ -146,6 +147,7 @@ class EE_API UIAbstractTableView : public UIAbstractView {
protected:
friend class EE::UI::UITableHeaderColumn;
+ friend class EE::UI::UIDropDownModelList;
struct ColumnData {
Float minWidth{ 0 };
diff --git a/include/eepp/ui/uidropdown.hpp b/include/eepp/ui/uidropdown.hpp
new file mode 100644
index 000000000..06659d6db
--- /dev/null
+++ b/include/eepp/ui/uidropdown.hpp
@@ -0,0 +1,89 @@
+#ifndef EE_UI_UIDROPDOWN_HPP
+#define EE_UI_UIDROPDOWN_HPP
+
+#include
+
+namespace EE { namespace UI {
+
+class EE_API UIDropDown : public UITextInput {
+ public:
+ enum class MenuWidthMode {
+ DropDown,
+ Contents,
+ ContentsCentered,
+ ExpandIfNeeded,
+ ExpandIfNeededCentered
+ };
+
+ static MenuWidthMode menuWidthModeFromString( std::string_view str );
+
+ static std::string menuWidthModeToString( MenuWidthMode rule );
+
+ struct StyleConfig {
+ Uint32 MaxNumVisibleItems = 10;
+ bool PopUpToRoot = false;
+ MenuWidthMode menuWidthRule{ MenuWidthMode::DropDown };
+ };
+
+ virtual ~UIDropDown();
+
+ virtual Uint32 getType() const;
+ virtual bool isType( const Uint32& type ) const;
+
+ virtual void setTheme( UITheme* Theme );
+
+ virtual UIDropDown* showList();
+
+ bool getPopUpToRoot() const;
+ UIDropDown* setPopUpToRoot( bool popUpToRoot );
+
+ Uint32 getMaxNumVisibleItems() const;
+ virtual UIDropDown* setMaxNumVisibleItems( const Uint32& maxNumVisibleItems );
+
+ const StyleConfig& getStyleConfig() const;
+ UIDropDown* setStyleConfig( const StyleConfig& styleConfig );
+
+ UIDropDown* setMenuWidthMode( MenuWidthMode rule );
+ MenuWidthMode getMenuWidthMode() const;
+
+ virtual bool applyProperty( const StyleSheetProperty& attribute );
+ virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
+ const Uint32& propertyIndex = 0 ) const;
+ virtual std::vector getPropertiesImplemented() const;
+
+ protected:
+ StyleConfig mStyleConfig;
+ UINode* mFriendNode{ nullptr };
+
+ UIDropDown( const std::string& tag );
+
+ virtual UIWidget* getPopUpWidget() const;
+
+ void onPopUpFocusLoss( const Event* Event );
+
+ virtual void onItemSelected( const Event* Event );
+ virtual void show();
+ virtual void hide();
+
+ virtual Uint32 onMouseOver( const Vector2i& position, const Uint32& flags );
+ virtual Uint32 onMouseLeave( const Vector2i& position, const Uint32& flags );
+ virtual Uint32 onMouseClick( const Vector2i& position, const Uint32& flags );
+
+ virtual void onItemClicked( const Event* Event );
+ virtual void onItemKeyDown( const Event* Event );
+ virtual void onWidgetClear( const Event* Event );
+ virtual Uint32 onKeyDown( const KeyEvent& Event );
+
+ virtual void onSizeChange();
+ virtual void onAutoSize();
+ virtual void onThemeLoaded();
+
+ void setFriendNode( UINode* friendNode );
+
+ Float getPopUpWidth( Float contentsWidth ) const;
+ void alignPopUp( UIWidget* widget );
+};
+
+}} // namespace EE::UI
+
+#endif
diff --git a/include/eepp/ui/uidropdownlist.hpp b/include/eepp/ui/uidropdownlist.hpp
index 66f1a619e..2ed5f4daa 100644
--- a/include/eepp/ui/uidropdownlist.hpp
+++ b/include/eepp/ui/uidropdownlist.hpp
@@ -1,30 +1,15 @@
#ifndef EE_UICUIDROPDOWNLIST_HPP
#define EE_UICUIDROPDOWNLIST_HPP
+#include
#include
-#include
namespace EE { namespace UI {
-class EE_API UIDropDownList : public UITextInput {
+class EE_API UIDropDownList : public UIDropDown {
public:
- enum class MenuWidthMode {
- DropDown,
- Contents,
- ContentsCentered,
- ExpandIfNeeded,
- ExpandIfNeededCentered
- };
-
- static MenuWidthMode menuWidthModeFromString( std::string_view str );
-
- static std::string menuWidthModeToString( MenuWidthMode rule );
-
- struct StyleConfig {
- Uint32 MaxNumVisibleItems = 10;
- bool PopUpToRoot = false;
- MenuWidthMode menuWidthRule{ MenuWidthMode::DropDown };
- };
+ using MenuWidthMode = UIDropDown::MenuWidthMode;
+ using StyleConfig = UIDropDown::StyleConfig;
static UIDropDownList* NewWithTag( const std::string& tag );
@@ -33,84 +18,39 @@ class EE_API UIDropDownList : public UITextInput {
virtual ~UIDropDownList();
virtual Uint32 getType() const;
-
virtual bool isType( const Uint32& type ) const;
- virtual void setTheme( UITheme* Theme );
-
UIListBox* getListBox() const;
UIDropDownList* showList();
- bool getPopUpToRoot() const;
-
- UIDropDownList* setPopUpToRoot( bool popUpToRoot );
-
- Uint32 getMaxNumVisibleItems() const;
-
- UIDropDownList* setMaxNumVisibleItems( const Uint32& maxNumVisibleItems );
-
- const StyleConfig& getStyleConfig() const;
-
- UIDropDownList* setStyleConfig( const StyleConfig& styleConfig );
+ virtual UIDropDownList* setMaxNumVisibleItems( const Uint32& maxNumVisibleItems );
virtual bool applyProperty( const StyleSheetProperty& attribute );
-
virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
const Uint32& propertyIndex = 0 ) const;
-
virtual std::vector getPropertiesImplemented() const;
virtual void loadFromXmlNode( const pugi::xml_node& node );
- UIDropDownList* setMenuWidthMode( MenuWidthMode rule );
-
- MenuWidthMode getMenuWidthMode() const;
-
protected:
friend class UIComboBox;
- StyleConfig mStyleConfig;
UIListBox* mListBox;
- UINode* mFriendNode;
Uint32 mListBoxCloseCb{ 0 };
UIDropDownList( const std::string& tag = "dropdownlist" );
- void onListBoxFocusLoss( const Event* Event );
+ virtual UIWidget* getPopUpWidget() const;
virtual void onItemSelected( const Event* Event );
- virtual void show();
-
- virtual void hide();
-
- virtual Uint32 onMouseOver( const Vector2i& position, const Uint32& flags );
-
- virtual Uint32 onMouseLeave( const Vector2i& position, const Uint32& flags );
-
virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
- virtual Uint32 onMouseClick( const Vector2i& position, const Uint32& flags );
-
- virtual void onItemClicked( const Event* Event );
-
- virtual void onItemKeyDown( const Event* Event );
-
- virtual void onWidgetClear( const Event* Event );
-
virtual Uint32 onKeyDown( const KeyEvent& Event );
virtual void onClassChange();
- virtual void onSizeChange();
-
- virtual void onAutoSize();
-
- virtual void onThemeLoaded();
-
- void setFriendNode( UINode* friendNode );
-
void destroyListBox();
};
diff --git a/include/eepp/ui/uidropdownmodellist.hpp b/include/eepp/ui/uidropdownmodellist.hpp
new file mode 100644
index 000000000..4fa61f217
--- /dev/null
+++ b/include/eepp/ui/uidropdownmodellist.hpp
@@ -0,0 +1,68 @@
+#ifndef EE_UI_UIDROPDOWNMODELLIST_HPP
+#define EE_UI_UIDROPDOWNMODELLIST_HPP
+
+#include
+#include
+
+namespace EE { namespace UI {
+
+class EE_API UIDropDownModelList : public UIDropDown {
+ public:
+ using MenuWidthMode = UIDropDown::MenuWidthMode;
+ using StyleConfig = UIDropDown::StyleConfig;
+
+ static UIDropDownModelList* NewWithTag( const std::string& tag );
+
+ static UIDropDownModelList* New();
+
+ virtual ~UIDropDownModelList();
+
+ virtual Uint32 getType() const;
+ virtual bool isType( const Uint32& type ) const;
+
+ UIAbstractTableView* getListView() const;
+
+ void setListView( UIAbstractTableView* listView );
+
+ std::shared_ptr getModel() const;
+
+ virtual void setModel( std::shared_ptr model );
+
+ UIDropDownModelList* showList();
+
+ virtual UIDropDownModelList* setMaxNumVisibleItems( const Uint32& maxNumVisibleItems );
+
+ virtual std::string getPropertyString( const PropertyDefinition* propertyDef,
+ const Uint32& propertyIndex = 0 ) const;
+
+ virtual std::vector getPropertiesImplemented() const;
+
+ protected:
+ UIAbstractTableView* mListView;
+ Uint32 mListViewCloseCb{ 0 };
+ std::shared_ptr mModel;
+
+ UIDropDownModelList( const std::string& tag = "dropdownmodellist" );
+
+ virtual UIWidget* getPopUpWidget() const;
+
+ virtual void onItemSelected( const Event* Event );
+
+ virtual Uint32 onMouseUp( const Vector2i& position, const Uint32& flags );
+
+ virtual Uint32 onKeyDown( const KeyEvent& Event );
+
+ virtual void onItemClicked( const Event* Event );
+
+ virtual void onClassChange();
+
+ void destroyListView();
+
+ UIWidget* createDefaultListView();
+
+ void updateSelectionIndex();
+};
+
+}} // namespace EE::UI
+
+#endif
diff --git a/include/eepp/ui/uihelper.hpp b/include/eepp/ui/uihelper.hpp
index 7e87df911..e1aa2bc5b 100644
--- a/include/eepp/ui/uihelper.hpp
+++ b/include/eepp/ui/uihelper.hpp
@@ -65,6 +65,7 @@ enum UINodeType {
UI_TYPE_PROGRESSBAR,
UI_TYPE_LISTBOX,
UI_TYPE_LISTBOXITEM,
+ UI_TYPE_DROPDOWN,
UI_TYPE_DROPDOWNLIST,
UI_TYPE_MENU_SEPARATOR,
UI_TYPE_COMBOBOX,
@@ -117,6 +118,7 @@ enum UINodeType {
UI_TYPE_HTML_TABLE_FOOTER,
UI_TYPE_HTML_TABLE_ROW,
UI_TYPE_HTML_TABLE_CELL,
+ UI_TYPE_DROPDOWNMODELLIST,
UI_TYPE_MODULES = 10000,
UI_TYPE_TERMINAL = 10001,
UI_TYPE_USER = 200000,
diff --git a/include/eepp/ui/uilistview.hpp b/include/eepp/ui/uilistview.hpp
index b80d89237..c1c933d30 100644
--- a/include/eepp/ui/uilistview.hpp
+++ b/include/eepp/ui/uilistview.hpp
@@ -9,6 +9,8 @@ class EE_API UIListView : public UITableView {
public:
static UIListView* New();
+ static UIListView* NewWithTag( const std::string& tag );
+
Uint32 getType() const;
bool isType( const Uint32& type ) const;
@@ -16,7 +18,7 @@ class EE_API UIListView : public UITableView {
void setTheme( UITheme* Theme );
protected:
- UIListView();
+ UIListView( const std::string& tag = "listview" );
};
}} // namespace EE::UI
diff --git a/premake4.lua b/premake4.lua
index 1789be88e..61c96e3f5 100644
--- a/premake4.lua
+++ b/premake4.lua
@@ -1618,6 +1618,12 @@ solution "eepp"
files { "src/examples/ui_application_hello_world/*.cpp" }
build_link_configuration( "eepp-ui-application-hello-world", true )
+ project "eepp-ui-dropdownmodellist"
+ set_kind()
+ language "C++"
+ files { "src/examples/ui_dropdownmodellist/*.cpp" }
+ build_link_configuration( "eepp-ui-dropdownmodellist", true )
+
project "eepp-ui-richtext"
set_kind()
language "C++"
@@ -1770,6 +1776,7 @@ solution "eepp"
linkoptions { "../../bin/assets/icon/ecode.res" }
end
buildoptions{ "-Wa,-mbig-obj" }
+ linkoptions { "-Wl,--export-all-symbols" }
end
build_link_configuration( "ecode", false )
configuration { "release", "windows" }
diff --git a/premake5.lua b/premake5.lua
index 631912ef7..87e476c0a 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -1507,6 +1507,12 @@ workspace "eepp"
files { "src/examples/ui_application_hello_world/*.cpp" }
build_link_configuration( "eepp-ui-application-hello-world", true )
+ project "eepp-ui-dropdownmodellist"
+ set_kind()
+ language "C++"
+ files { "src/examples/ui_dropdownmodellist/*.cpp" }
+ build_link_configuration( "eepp-ui-dropdownmodellist", true )
+
project "eepp-ui-richtext"
set_kind()
language "C++"
@@ -1628,6 +1634,7 @@ workspace "eepp"
build_link_configuration( "ecode", false )
filter { "system:windows", "action:not vs*" }
buildoptions{ "-Wa,-mbig-obj" }
+ linkoptions { "-Wl,--export-all-symbols" }
filter { "system:windows", "action:vs*" }
files { "bin/assets/icon/ecode.rc", "bin/assets/icon/ecode.ico" }
vpaths { ['Resources/*'] = { "ecode.rc", "ecode.ico" } }
diff --git a/src/eepp/system/fileinfo.cpp b/src/eepp/system/fileinfo.cpp
index 4bca0d75c..ac3aa255d 100644
--- a/src/eepp/system/fileinfo.cpp
+++ b/src/eepp/system/fileinfo.cpp
@@ -115,8 +115,11 @@ void FileInfo::getInfo() {
struct stat st;
int res = stat( mFilepath.c_str(), &st );
#else
- struct _stat st;
- int res = _wstat( String::fromUtf8( mFilepath ).toWideString().c_str(), &st );
+ std::string_view fp( mFilepath );
+ if ( fp.size() > 3 && ( fp.back() == '/' || fp.back() == '\\' ) )
+ fp.remove_suffix( 1 );
+ struct __stat64 st;
+ int res = _wstat64( String( fp ).toWideString().c_str(), &st );
#endif
if ( 0 == res ) {
@@ -261,8 +264,11 @@ bool FileInfo::exists() const {
struct stat st;
int res = stat( mFilepath.c_str(), &st );
#else
- struct _stat st;
- int res = _wstat( String::fromUtf8( mFilepath ).toWideString().c_str(), &st );
+ std::string_view fp( mFilepath );
+ if ( fp.size() > 3 && ( fp.back() == '/' || fp.back() == '\\' ) )
+ fp.remove_suffix( 1 );
+ struct __stat64 st;
+ int res = _wstat64( String( fp ).toWideString().c_str(), &st );
#endif
if ( isDirectory() )
diff --git a/src/eepp/system/filesystem.cpp b/src/eepp/system/filesystem.cpp
index fb62844f4..8b2f348b5 100644
--- a/src/eepp/system/filesystem.cpp
+++ b/src/eepp/system/filesystem.cpp
@@ -211,8 +211,11 @@ bool FileSystem::fileHide( const std::string& filepath ) {
Uint32 FileSystem::fileGetModificationDate( const std::string& filepath ) {
#if EE_PLATFORM == EE_PLATFORM_WIN
- struct _stat st;
- int res = _wstat( String( filepath ).toWideString().c_str(), &st );
+ std::string_view fp( filepath );
+ if ( fp.size() > 3 && ( fp.back() == '/' || fp.back() == '\\' ) )
+ fp.remove_suffix( 1 );
+ struct __stat64 st;
+ int res = _wstat64( String( fp ).toWideString().c_str(), &st );
#else
struct stat st;
int res = stat( filepath.c_str(), &st );
@@ -540,8 +543,11 @@ std::vector FileSystem::filesGetInPath( const std::string& path,
Uint64 FileSystem::fileSize( const std::string& Filepath ) {
#if EE_PLATFORM == EE_PLATFORM_WIN
- struct _stat st;
- int res = _wstat( String( Filepath ).toWideString().c_str(), &st );
+ std::string_view fp( Filepath );
+ if ( fp.size() > 3 && ( fp.back() == '/' || fp.back() == '\\' ) )
+ fp.remove_suffix( 1 );
+ struct __stat64 st;
+ int res = _wstat64( String( fp ).toWideString().c_str(), &st );
#else
struct stat st;
int res = stat( Filepath.c_str(), &st );
@@ -555,8 +561,11 @@ Uint64 FileSystem::fileSize( const std::string& Filepath ) {
bool FileSystem::fileExists( const std::string& Filepath ) {
#if EE_PLATFORM == EE_PLATFORM_WIN
- struct _stat st;
- return ( _wstat( String( Filepath ).toWideString().c_str(), &st ) == 0 );
+ std::string_view fp( Filepath );
+ if ( fp.size() > 3 && ( fp.back() == '/' || fp.back() == '\\' ) )
+ fp.remove_suffix( 1 );
+ struct __stat64 st;
+ return ( _wstat64( String( fp ).toWideString().c_str(), &st ) == 0 );
#else
struct stat st;
return ( stat( Filepath.c_str(), &st ) == 0 );
diff --git a/src/eepp/system/sys.cpp b/src/eepp/system/sys.cpp
index f64295856..6f7f96500 100644
--- a/src/eepp/system/sys.cpp
+++ b/src/eepp/system/sys.cpp
@@ -643,7 +643,7 @@ static std::string sGetProcessPath() {
_splitpath_s( dllstrName.c_str(), szDrive, _MAX_DRIVE, szDir, _MAX_DIR, szFilename, _MAX_DIR,
szExt, _MAX_DIR );
#else
- _splitpath( szDllName, szDrive, szDir, szFilename, szExt );
+ _splitpath( dllstrName.c_str(), szDrive, szDir, szFilename, szExt );
#endif
return std::string( szDrive ) + std::string( szDir );
diff --git a/src/eepp/ui/doc/languages/css.cpp b/src/eepp/ui/doc/languages/css.cpp
index adee86d4f..c98e26a4a 100644
--- a/src/eepp/ui/doc/languages/css.cpp
+++ b/src/eepp/ui/doc/languages/css.cpp
@@ -384,6 +384,7 @@ void addCSS() {
{ "RadioButton", "keyword" },
{ "ComboBox", "keyword" },
{ "DropDownList", "keyword" },
+ { "DropDownModelList", "keyword" },
{ "Image", "keyword" },
{ "ListBox", "keyword" },
{ "MenuBar", "keyword" },
@@ -425,6 +426,7 @@ void addCSS() {
{ "PopUpMenu", "keyword" },
{ "ImageViewer", "keyword" },
{ "AudioPlayer", "keyword" },
+ { "Table", "keyword" },
},
"",
diff --git a/src/eepp/ui/doc/languages/json.cpp b/src/eepp/ui/doc/languages/json.cpp
index e200de35c..a3164ca71 100644
--- a/src/eepp/ui/doc/languages/json.cpp
+++ b/src/eepp/ui/doc/languages/json.cpp
@@ -14,6 +14,7 @@ void addJSON() {
"%.jsonc$",
"%.ipynb$",
"%.webmanifest$",
+ "%.cps$",
},
{
{ { "//.-\n" }, "comment" },
diff --git a/src/eepp/ui/doc/syntaxhighlighter.cpp b/src/eepp/ui/doc/syntaxhighlighter.cpp
index 43de79416..97a7f06d2 100644
--- a/src/eepp/ui/doc/syntaxhighlighter.cpp
+++ b/src/eepp/ui/doc/syntaxhighlighter.cpp
@@ -132,7 +132,7 @@ void SyntaxHighlighter::setMaxTokenizationLength( const Int64& maxTokenizationLe
void SyntaxHighlighter::tokenizeAsync( std::shared_ptr pool,
const std::function& onDone ) {
- if ( mTokenizeAsync && false )
+ if ( mTokenizeAsync )
return;
mTokenizeAsync = true;
pool->run( [this, onDone] {
diff --git a/src/eepp/ui/uidropdown.cpp b/src/eepp/ui/uidropdown.cpp
new file mode 100644
index 000000000..7186e574e
--- /dev/null
+++ b/src/eepp/ui/uidropdown.cpp
@@ -0,0 +1,339 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace EE { namespace UI {
+
+UIDropDown::MenuWidthMode UIDropDown::menuWidthModeFromString( std::string_view str ) {
+ if ( "contents" == str || "fit-to-contents" == str )
+ return MenuWidthMode::Contents;
+ if ( "contents-centered" == str || "fit-to-contents-centered" == str )
+ return MenuWidthMode::ContentsCentered;
+ if ( "expand-if-needed" == str || "fit-to-drop-down-expand-if-needed" == str )
+ return MenuWidthMode::ExpandIfNeeded;
+ if ( "expand-if-needed-centered" == str || "fit-to-drop-down-expand-if-needed-centered" == str )
+ return MenuWidthMode::ExpandIfNeededCentered;
+ return MenuWidthMode::DropDown; // "dropdown"
+}
+
+std::string UIDropDown::menuWidthModeToString( MenuWidthMode rule ) {
+ switch ( rule ) {
+ case MenuWidthMode::DropDown:
+ return "dropdown";
+ case MenuWidthMode::Contents:
+ return "contents";
+ case MenuWidthMode::ContentsCentered:
+ return "contents-centered";
+ case MenuWidthMode::ExpandIfNeeded:
+ return "expand-if-needed";
+ case MenuWidthMode::ExpandIfNeededCentered:
+ return "expand-if-needed-centered";
+ }
+ return "dropdown";
+}
+
+UIDropDown::UIDropDown( const std::string& tag ) : UITextInput( tag ) {
+ mEnabledCreateContextMenu = false;
+ setClipType( ClipType::ContentBox );
+ setFlags( UI_AUTO_SIZE | UI_AUTO_PADDING | UI_SCROLLABLE );
+ unsetFlags( UI_TEXT_SELECTION_ENABLED );
+ setAllowEditing( false );
+}
+
+UIDropDown::~UIDropDown() {}
+
+Uint32 UIDropDown::getType() const {
+ return UI_TYPE_DROPDOWN;
+}
+
+bool UIDropDown::isType( const Uint32& type ) const {
+ return UIDropDown::getType() == type ? true : UITextInput::isType( type );
+}
+
+void UIDropDown::setTheme( UITheme* Theme ) {
+ UIWidget::setTheme( Theme );
+ setThemeSkin( Theme, "dropdownlist" );
+ onThemeLoaded();
+}
+
+void UIDropDown::onSizeChange() {
+ onAutoSize();
+ UITextInput::onSizeChange();
+}
+
+void UIDropDown::onThemeLoaded() {
+ autoPadding();
+ onAutoSize();
+}
+
+void UIDropDown::setFriendNode( UINode* friendNode ) {
+ mFriendNode = friendNode;
+}
+
+void UIDropDown::onAutoSize() {
+ Float max = eemax( PixelDensity::dpToPxI( getSkinSize().getHeight() ),
+ mTextCache.getLineSpacing() );
+
+ if ( mHeightPolicy == SizePolicy::WrapContent ) {
+ setInternalPixelsHeight( eeceil( max + mPaddingPx.Top + mPaddingPx.Bottom ) );
+ } else if ( ( ( mFlags & UI_AUTO_SIZE ) || 0 == getSize().getHeight() ) && max > 0 ) {
+ setInternalPixelsHeight( eeceil( max ) );
+ }
+}
+
+UIWidget* UIDropDown::getPopUpWidget() const {
+ return nullptr;
+}
+
+Uint32 UIDropDown::onMouseClick( const Vector2i& Pos, const Uint32& Flags ) {
+ if ( ( Flags & EE_BUTTON_LMASK ) && NULL == mFriendNode )
+ showList();
+
+ if ( NULL != mFriendNode ) {
+ UITextInput::onMouseClick( Pos, Flags );
+ }
+
+ return 1;
+}
+
+UIDropDown* UIDropDown::showList() {
+ return this;
+}
+
+Float UIDropDown::getPopUpWidth( Float contentsWidth ) const {
+ Float width = NULL != mFriendNode ? mFriendNode->getSize().getWidth() : getSize().getWidth();
+
+ if ( mStyleConfig.menuWidthRule == MenuWidthMode::Contents ||
+ mStyleConfig.menuWidthRule == MenuWidthMode::ContentsCentered ) {
+ width = contentsWidth;
+ }
+
+ if ( ( mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeeded ||
+ mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeededCentered ) &&
+ contentsWidth > width ) {
+ width = contentsWidth;
+ }
+
+ return width;
+}
+
+void UIDropDown::alignPopUp( UIWidget* widget ) {
+ if ( !mStyleConfig.PopUpToRoot )
+ widget->setParent( getWindowContainer() );
+ else
+ widget->setParent( getUISceneNode()->getRoot() );
+
+ widget->toFront();
+
+ bool center = mStyleConfig.menuWidthRule == MenuWidthMode::ContentsCentered ||
+ mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeededCentered;
+
+ Float width = widget->getSize().getWidth();
+ Float offsetX = center ? eefloor( ( getSize().getWidth() - width ) * 0.5f ) : 0;
+
+ Vector2f pos( mDpPos.x + offsetX, mDpPos.y + getSize().getHeight() );
+ Vector2f posCpy( pos );
+ nodeToWorld( posCpy );
+
+ if ( !getUISceneNode()->getWorldBounds().contains( Rectf( posCpy, widget->getSize() ) ) ) {
+ pos = Vector2f( mDpPos.x + offsetX, mDpPos.y - widget->getSize().getHeight() );
+ }
+
+ if ( mStyleConfig.PopUpToRoot ) {
+ getParent()->nodeToWorld( pos );
+ pos = PixelDensity::pxToDp( pos );
+ } else {
+ Node* parentNode = getParent();
+ Node* rp = getWindowContainer();
+ while ( rp != parentNode ) {
+ pos += parentNode->getPosition();
+ parentNode = parentNode->getParent();
+ }
+ }
+
+ widget->setPosition( pos );
+ show();
+ widget->setFocus();
+}
+
+bool UIDropDown::getPopUpToRoot() const {
+ return mStyleConfig.PopUpToRoot;
+}
+
+UIDropDown* UIDropDown::setPopUpToRoot( bool popUpToRoot ) {
+ mStyleConfig.PopUpToRoot = popUpToRoot;
+ return this;
+}
+
+Uint32 UIDropDown::getMaxNumVisibleItems() const {
+ return mStyleConfig.MaxNumVisibleItems;
+}
+
+UIDropDown* UIDropDown::setMaxNumVisibleItems( const Uint32& maxNumVisibleItems ) {
+ mStyleConfig.MaxNumVisibleItems = maxNumVisibleItems;
+ return this;
+}
+
+const UIDropDown::StyleConfig& UIDropDown::getStyleConfig() const {
+ return mStyleConfig;
+}
+
+UIDropDown* UIDropDown::setStyleConfig( const StyleConfig& styleConfig ) {
+ mStyleConfig = styleConfig;
+
+ setMaxNumVisibleItems( mStyleConfig.MaxNumVisibleItems );
+ setPopUpToRoot( mStyleConfig.PopUpToRoot );
+ setMenuWidthMode( mStyleConfig.menuWidthRule );
+ return this;
+}
+
+void UIDropDown::onWidgetClear( const Event* ) {
+ setText( "" );
+ sendCommonEvent( Event::OnClear );
+}
+
+void UIDropDown::onItemKeyDown( const Event* Event ) {
+ const KeyEvent* KEvent = reinterpret_cast( Event );
+
+ if ( KEvent->getKeyCode() == KEY_RETURN )
+ onItemClicked( Event );
+ else if ( KEvent->getKeyCode() == KEY_ESCAPE ) {
+ hide();
+ setFocus();
+ }
+}
+
+void UIDropDown::onPopUpFocusLoss( const Event* ) {
+ if ( NULL == getEventDispatcher() )
+ return;
+
+ bool frienIsFocus = NULL != mFriendNode && mFriendNode == getEventDispatcher()->getFocusNode();
+ bool isChildFocus = isChild( getEventDispatcher()->getFocusNode() );
+
+ if ( getEventDispatcher()->getFocusNode() != this && !isChildFocus && !frienIsFocus ) {
+ hide();
+ }
+}
+
+void UIDropDown::onItemClicked( const Event* ) {
+ hide();
+ setFocus();
+}
+
+void UIDropDown::onItemSelected( const Event* ) {}
+
+void UIDropDown::show() {
+ UIWidget* widget = getPopUpWidget();
+ if ( NULL == widget )
+ return;
+
+ widget->setEnabled( true );
+ widget->setVisible( true );
+
+ if ( NULL != getUISceneNode() &&
+ getUISceneNode()->getUIThemeManager()->getDefaultEffectsEnabled() ) {
+ widget->runAction( Actions::Sequence::New(
+ Actions::Fade::New( 255.f == widget->getAlpha() ? 0.f : widget->getAlpha(), 255.f,
+ getUISceneNode()->getUIThemeManager()->getWidgetsFadeOutTime() ),
+ Actions::Spawn::New( Actions::Enable::New(), Actions::Visible::New( true ) ) ) );
+ }
+}
+
+void UIDropDown::hide() {
+ UIWidget* widget = getPopUpWidget();
+ if ( NULL == widget )
+ return;
+
+ if ( NULL != getUISceneNode() &&
+ getUISceneNode()->getUIThemeManager()->getDefaultEffectsEnabled() ) {
+ widget->runAction( Actions::Sequence::New(
+ Actions::FadeOut::New( getUISceneNode()->getUIThemeManager()->getWidgetsFadeOutTime() ),
+ Actions::Spawn::New( Actions::Disable::New(), Actions::Visible::New( false ) ) ) );
+ } else {
+ widget->setEnabled( false );
+ widget->setVisible( false );
+ }
+}
+
+Uint32 UIDropDown::onMouseOver( const Vector2i& position, const Uint32& flags ) {
+ if ( getParent()->isType( UI_TYPE_COMBOBOX ) ) {
+ return UITextInput::onMouseOver( position, flags );
+ } else {
+ return UITextView::onMouseOver( position, flags );
+ }
+}
+
+Uint32 UIDropDown::onMouseLeave( const Vector2i& position, const Uint32& flags ) {
+ if ( getParent()->isType( UI_TYPE_COMBOBOX ) ) {
+ return UITextInput::onMouseLeave( position, flags );
+ } else {
+ return UITextView::onMouseLeave( position, flags );
+ }
+}
+
+Uint32 UIDropDown::onKeyDown( const KeyEvent& Event ) {
+ return UITextInput::onKeyDown( Event );
+}
+
+bool UIDropDown::applyProperty( const StyleSheetProperty& attribute ) {
+ if ( !checkPropertyDefinition( attribute ) )
+ return false;
+
+ switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
+ case PropertyId::PopUpToRoot:
+ setPopUpToRoot( attribute.asBool() );
+ break;
+ case PropertyId::MaxVisibleItems:
+ setMaxNumVisibleItems( attribute.asUint() );
+ break;
+ case PropertyId::MenuWidthMode:
+ setMenuWidthMode( menuWidthModeFromString( attribute.getValue() ) );
+ break;
+ default:
+ return UITextInput::applyProperty( attribute );
+ }
+
+ return true;
+}
+
+std::string UIDropDown::getPropertyString( const PropertyDefinition* propertyDef,
+ const Uint32& propertyIndex ) const {
+ if ( NULL == propertyDef )
+ return "";
+
+ switch ( propertyDef->getPropertyId() ) {
+ case PropertyId::PopUpToRoot:
+ return mStyleConfig.PopUpToRoot ? "true" : "false";
+ case PropertyId::MaxVisibleItems:
+ return String::toString( mStyleConfig.MaxNumVisibleItems );
+ case PropertyId::MenuWidthMode:
+ return menuWidthModeToString( mStyleConfig.menuWidthRule );
+ default:
+ return UITextInput::getPropertyString( propertyDef, propertyIndex );
+ }
+
+ return "";
+}
+
+std::vector UIDropDown::getPropertiesImplemented() const {
+ auto props = UITextInput::getPropertiesImplemented();
+ auto local = { PropertyId::PopUpToRoot, PropertyId::MaxVisibleItems,
+ PropertyId::MenuWidthMode };
+ props.insert( props.end(), local.begin(), local.end() );
+ return props;
+}
+
+UIDropDown* UIDropDown::setMenuWidthMode( MenuWidthMode rule ) {
+ mStyleConfig.menuWidthRule = rule;
+ return this;
+}
+
+UIDropDown::MenuWidthMode UIDropDown::getMenuWidthMode() const {
+ return mStyleConfig.menuWidthRule;
+}
+
+}} // namespace EE::UI
diff --git a/src/eepp/ui/uidropdownlist.cpp b/src/eepp/ui/uidropdownlist.cpp
index ba5c7fe21..cc6cab9e3 100644
--- a/src/eepp/ui/uidropdownlist.cpp
+++ b/src/eepp/ui/uidropdownlist.cpp
@@ -10,34 +10,6 @@
namespace EE { namespace UI {
-UIDropDownList::MenuWidthMode UIDropDownList::menuWidthModeFromString( std::string_view str ) {
- if ( "contents" == str || "fit-to-contents" == str )
- return MenuWidthMode::Contents;
- if ( "contents-centered" == str || "fit-to-contents-centered" == str )
- return MenuWidthMode::ContentsCentered;
- if ( "expand-if-needed" == str || "fit-to-drop-down-expand-if-needed" == str )
- return MenuWidthMode::ExpandIfNeeded;
- if ( "expand-if-needed-centered" == str || "fit-to-drop-down-expand-if-needed-centered" == str )
- return MenuWidthMode::ExpandIfNeededCentered;
- return MenuWidthMode::DropDown; // "dropdown"
-}
-
-std::string UIDropDownList::menuWidthModeToString( MenuWidthMode rule ) {
- switch ( rule ) {
- case MenuWidthMode::DropDown:
- return "dropdown";
- case MenuWidthMode::Contents:
- return "contents";
- case MenuWidthMode::ContentsCentered:
- return "contents-centered";
- case MenuWidthMode::ExpandIfNeeded:
- return "expand-if-needed";
- case MenuWidthMode::ExpandIfNeededCentered:
- return "expand-if-needed-centered";
- }
- return "dropdown";
-}
-
UIDropDownList* UIDropDownList::NewWithTag( const std::string& tag ) {
return eeNew( UIDropDownList, ( tag ) );
}
@@ -46,15 +18,7 @@ UIDropDownList* UIDropDownList::New() {
return eeNew( UIDropDownList, () );
}
-UIDropDownList::UIDropDownList( const std::string& tag ) :
- UITextInput( tag ), mListBox( NULL ), mFriendNode( NULL ) {
- mEnabledCreateContextMenu = false;
- setClipType( ClipType::ContentBox );
- setFlags( UI_AUTO_SIZE | UI_AUTO_PADDING | UI_SCROLLABLE );
- unsetFlags( UI_TEXT_SELECTION_ENABLED );
-
- setAllowEditing( false );
-
+UIDropDownList::UIDropDownList( const std::string& tag ) : UIDropDown( tag ), mListBox( NULL ) {
applyDefaultTheme();
mListBox = UIListBox::NewWithTag( mTag + "::listbox" );
@@ -65,7 +29,7 @@ UIDropDownList::UIDropDownList( const std::string& tag ) :
// This will force to change the parent when shown, and force the CSS style reload.
mListBox->setParent( this );
- mListBox->on( Event::OnWidgetFocusLoss, [this]( auto event ) { onListBoxFocusLoss( event ); } );
+ mListBox->on( Event::OnWidgetFocusLoss, [this]( auto event ) { onPopUpFocusLoss( event ); } );
mListBox->on( Event::OnItemSelected, [this]( auto event ) { onItemSelected( event ); } );
mListBox->on( Event::OnItemClicked, [this]( auto event ) { onItemClicked( event ); } );
mListBox->on( Event::OnItemKeyDown, [this]( auto event ) { onItemKeyDown( event ); } );
@@ -95,42 +59,11 @@ Uint32 UIDropDownList::getType() const {
}
bool UIDropDownList::isType( const Uint32& type ) const {
- return UIDropDownList::getType() == type ? true : UITextInput::isType( type );
+ return UIDropDownList::getType() == type ? true : UIDropDown::isType( type );
}
-void UIDropDownList::setTheme( UITheme* Theme ) {
- UIWidget::setTheme( Theme );
-
- setThemeSkin( Theme, "dropdownlist" );
-
- onThemeLoaded();
-}
-
-void UIDropDownList::onSizeChange() {
- onAutoSize();
-
- UITextInput::onSizeChange();
-}
-
-void UIDropDownList::onThemeLoaded() {
- autoPadding();
-
- onAutoSize();
-}
-
-void UIDropDownList::setFriendNode( UINode* friendNode ) {
- mFriendNode = friendNode;
-}
-
-void UIDropDownList::onAutoSize() {
- Float max = eemax( PixelDensity::dpToPxI( getSkinSize().getHeight() ),
- mTextCache.getLineSpacing() );
-
- if ( mHeightPolicy == SizePolicy::WrapContent ) {
- setInternalPixelsHeight( eeceil( max + mPaddingPx.Top + mPaddingPx.Bottom ) );
- } else if ( ( ( mFlags & UI_AUTO_SIZE ) || 0 == getSize().getHeight() ) && max > 0 ) {
- setInternalPixelsHeight( eeceil( max ) );
- }
+UIWidget* UIDropDownList::getPopUpWidget() const {
+ return mListBox;
}
UIListBox* UIDropDownList::getListBox() const {
@@ -152,18 +85,14 @@ Uint32 UIDropDownList::onMouseUp( const Vector2i& Pos, const Uint32& Flags ) {
}
}
- return UITextInput::onMouseUp( Pos, Flags );
+ return UIDropDown::onMouseUp( Pos, Flags );
}
-Uint32 UIDropDownList::onMouseClick( const Vector2i& Pos, const Uint32& Flags ) {
- if ( ( Flags & EE_BUTTON_LMASK ) && NULL == mFriendNode )
- showList();
+Uint32 UIDropDownList::onKeyDown( const KeyEvent& Event ) {
+ if ( NULL != mListBox )
+ mListBox->onKeyDown( Event );
- if ( NULL != mFriendNode ) {
- UITextInput::onMouseClick( Pos, Flags );
- }
-
- return 1;
+ return UIDropDown::onKeyDown( Event );
}
UIDropDownList* UIDropDownList::showList() {
@@ -173,85 +102,30 @@ UIDropDownList* UIDropDownList::showList() {
if ( !mListBox->isVisible() ) {
if ( mListBox->getItemsCount() ) {
Rectf tPadding = mListBox->getContainerPadding();
-
Float sliderValue = mListBox->getVerticalScrollBar()->getValue();
- Float width =
- NULL != mFriendNode ? mFriendNode->getSize().getWidth() : getSize().getWidth();
+ Float contentsWidth = eeceil( PixelDensity::pxToDp(
+ mListBox->getMaxTextWidth() +
+ PixelDensity::dpToPx( mListBox->getContainerPadding().getWidth() ) +
+ mListBox->getReferenceItem()->getPixelsPadding().getWidth() +
+ mListBox->getVerticalScrollBar()->getPixelsSize().getWidth() ) );
- bool center = mStyleConfig.menuWidthRule == MenuWidthMode::ContentsCentered ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeededCentered;
+ Float width = getPopUpWidth( contentsWidth );
- Float contentsWidth = 0;
- if ( mStyleConfig.menuWidthRule == MenuWidthMode::Contents ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ContentsCentered ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeeded ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeededCentered ) {
- contentsWidth = eeceil( PixelDensity::pxToDp(
- mListBox->getMaxTextWidth() +
- PixelDensity::dpToPx( mListBox->getContainerPadding().getWidth() ) +
- mListBox->getReferenceItem()->getPixelsPadding().getWidth() +
- mListBox->getVerticalScrollBar()->getPixelsSize().getWidth() ) );
- }
-
- if ( mStyleConfig.menuWidthRule == MenuWidthMode::Contents ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ContentsCentered ) {
- width = contentsWidth;
- }
-
- if ( ( mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeeded ||
- mStyleConfig.menuWidthRule == MenuWidthMode::ExpandIfNeededCentered ) &&
- contentsWidth > width ) {
- width = contentsWidth;
- }
-
- mListBox->setSize(
- width, (Int32)( eemin( mListBox->getItemsCount(), mStyleConfig.MaxNumVisibleItems ) *
- mListBox->getRowHeight() ) +
- tPadding.Top + tPadding.Bottom +
- ( mListBox->getHorizontalScrollBar() &&
- mListBox->getHorizontalScrollBar()->isVisible() &&
- PixelDensity::dpToPx( width ) < mListBox->getMaxTextWidth()
- ? mListBox->getHorizontalScrollBar()->getSize().getHeight()
- : 0.f ) );
+ Float height =
+ (Int32)( eemin( mListBox->getItemsCount(), mStyleConfig.MaxNumVisibleItems ) *
+ mListBox->getRowHeight() ) +
+ tPadding.Top + tPadding.Bottom +
+ ( mListBox->getHorizontalScrollBar() &&
+ mListBox->getHorizontalScrollBar()->isVisible() &&
+ PixelDensity::dpToPx( width ) < mListBox->getMaxTextWidth()
+ ? mListBox->getHorizontalScrollBar()->getSize().getHeight()
+ : 0.f );
+ mListBox->setSize( width, height );
mListBox->getVerticalScrollBar()->setValue( sliderValue );
- if ( !mStyleConfig.PopUpToRoot )
- mListBox->setParent( getWindowContainer() );
- else
- mListBox->setParent( getUISceneNode()->getRoot() );
-
- mListBox->toFront();
-
- Float offsetX = center ? eefloor( ( getSize().getWidth() - width ) * 0.5f ) : 0;
-
- Vector2f pos( mDpPos.x + offsetX, mDpPos.y + getSize().getHeight() );
- Vector2f posCpy( pos );
- nodeToWorld( posCpy );
-
- if ( !getUISceneNode()->getWorldBounds().contains(
- Rectf( posCpy, mListBox->getSize() ) ) ) {
- pos = Vector2f( mDpPos.x + offsetX, mDpPos.y - mListBox->getSize().getHeight() );
- }
-
- if ( mStyleConfig.PopUpToRoot ) {
- getParent()->nodeToWorld( pos );
- pos = PixelDensity::pxToDp( pos );
- } else {
- Node* parentNode = getParent();
- Node* rp = getWindowContainer();
- while ( rp != parentNode ) {
- pos += parentNode->getPosition();
- parentNode = parentNode->getParent();
- }
- }
-
- mListBox->setPosition( pos );
-
- show();
-
- mListBox->setFocus();
+ alignPopUp( mListBox );
}
} else {
hide();
@@ -259,19 +133,6 @@ UIDropDownList* UIDropDownList::showList() {
return this;
}
-bool UIDropDownList::getPopUpToRoot() const {
- return mStyleConfig.PopUpToRoot;
-}
-
-UIDropDownList* UIDropDownList::setPopUpToRoot( bool popUpToRoot ) {
- mStyleConfig.PopUpToRoot = popUpToRoot;
- return this;
-}
-
-Uint32 UIDropDownList::getMaxNumVisibleItems() const {
- return mStyleConfig.MaxNumVisibleItems;
-}
-
UIDropDownList* UIDropDownList::setMaxNumVisibleItems( const Uint32& maxNumVisibleItems ) {
if ( maxNumVisibleItems != mStyleConfig.MaxNumVisibleItems ) {
mStyleConfig.MaxNumVisibleItems = maxNumVisibleItems;
@@ -284,52 +145,6 @@ UIDropDownList* UIDropDownList::setMaxNumVisibleItems( const Uint32& maxNumVisib
return this;
}
-const UIDropDownList::StyleConfig& UIDropDownList::getStyleConfig() const {
- return mStyleConfig;
-}
-
-UIDropDownList* UIDropDownList::setStyleConfig( const StyleConfig& styleConfig ) {
- mStyleConfig = styleConfig;
-
- setMaxNumVisibleItems( mStyleConfig.MaxNumVisibleItems );
- setPopUpToRoot( mStyleConfig.PopUpToRoot );
- setMenuWidthMode( mStyleConfig.menuWidthRule );
- return this;
-}
-
-void UIDropDownList::onWidgetClear( const Event* ) {
- setText( "" );
- sendCommonEvent( Event::OnClear );
-}
-
-void UIDropDownList::onItemKeyDown( const Event* Event ) {
- const KeyEvent* KEvent = reinterpret_cast( Event );
-
- if ( KEvent->getKeyCode() == KEY_RETURN )
- onItemClicked( Event );
- else if ( KEvent->getKeyCode() == KEY_ESCAPE ) {
- hide();
- setFocus();
- }
-}
-
-void UIDropDownList::onListBoxFocusLoss( const Event* ) {
- if ( NULL == getEventDispatcher() )
- return;
-
- bool frienIsFocus = NULL != mFriendNode && mFriendNode == getEventDispatcher()->getFocusNode();
- bool isChildFocus = isChild( getEventDispatcher()->getFocusNode() );
-
- if ( getEventDispatcher()->getFocusNode() != this && !isChildFocus && !frienIsFocus ) {
- hide();
- }
-}
-
-void UIDropDownList::onItemClicked( const Event* ) {
- hide();
- setFocus();
-}
-
void UIDropDownList::onItemSelected( const Event* ) {
setText( mListBox->getItemSelectedText() );
@@ -341,60 +156,6 @@ void UIDropDownList::onItemSelected( const Event* ) {
sendCommonEvent( Event::OnSelectionChanged );
}
-void UIDropDownList::show() {
- if ( NULL == mListBox )
- return;
-
- mListBox->setEnabled( true );
- mListBox->setVisible( true );
-
- if ( NULL != getUISceneNode() &&
- getUISceneNode()->getUIThemeManager()->getDefaultEffectsEnabled() ) {
- mListBox->runAction( Actions::Sequence::New(
- Actions::Fade::New( 255.f == mListBox->getAlpha() ? 0.f : mListBox->getAlpha(), 255.f,
- getUISceneNode()->getUIThemeManager()->getWidgetsFadeOutTime() ),
- Actions::Spawn::New( Actions::Enable::New(), Actions::Visible::New( true ) ) ) );
- }
-}
-
-void UIDropDownList::hide() {
- if ( NULL == mListBox )
- return;
-
- if ( NULL != getUISceneNode() &&
- getUISceneNode()->getUIThemeManager()->getDefaultEffectsEnabled() ) {
- mListBox->runAction( Actions::Sequence::New(
- Actions::FadeOut::New( getUISceneNode()->getUIThemeManager()->getWidgetsFadeOutTime() ),
- Actions::Spawn::New( Actions::Disable::New(), Actions::Visible::New( false ) ) ) );
- } else {
- mListBox->setEnabled( false );
- mListBox->setVisible( false );
- }
-}
-
-Uint32 UIDropDownList::onMouseOver( const Vector2i& position, const Uint32& flags ) {
- if ( getParent()->isType( UI_TYPE_COMBOBOX ) ) {
- return UITextInput::onMouseOver( position, flags );
- } else {
- return UITextView::onMouseOver( position, flags );
- }
-}
-
-Uint32 UIDropDownList::onMouseLeave( const Vector2i& position, const Uint32& flags ) {
- if ( getParent()->isType( UI_TYPE_COMBOBOX ) ) {
- return UITextInput::onMouseLeave( position, flags );
- } else {
- return UITextView::onMouseLeave( position, flags );
- }
-}
-
-Uint32 UIDropDownList::onKeyDown( const KeyEvent& Event ) {
- if ( NULL != mListBox )
- mListBox->onKeyDown( Event );
-
- return UITextInput::onKeyDown( Event );
-}
-
void UIDropDownList::destroyListBox() {
if ( !SceneManager::instance()->isShuttingDown() && NULL != mListBox &&
mListBox->getParent() != this ) {
@@ -407,15 +168,6 @@ bool UIDropDownList::applyProperty( const StyleSheetProperty& attribute ) {
return false;
switch ( attribute.getPropertyDefinition()->getPropertyId() ) {
- case PropertyId::PopUpToRoot:
- setPopUpToRoot( attribute.asBool() );
- break;
- case PropertyId::MaxVisibleItems:
- setMaxNumVisibleItems( attribute.asUint() );
- break;
- case PropertyId::MenuWidthMode:
- setMenuWidthMode( menuWidthModeFromString( attribute.getValue() ) );
- break;
case PropertyId::SelectedIndex:
case PropertyId::SelectedText:
case PropertyId::ScrollBarStyle:
@@ -427,7 +179,7 @@ bool UIDropDownList::applyProperty( const StyleSheetProperty& attribute ) {
else
return false;
default:
- return UITextInput::applyProperty( attribute );
+ return UIDropDown::applyProperty( attribute );
}
return true;
@@ -439,12 +191,6 @@ std::string UIDropDownList::getPropertyString( const PropertyDefinition* propert
return "";
switch ( propertyDef->getPropertyId() ) {
- case PropertyId::PopUpToRoot:
- return mStyleConfig.PopUpToRoot ? "true" : "false";
- case PropertyId::MaxVisibleItems:
- return String::toString( mStyleConfig.MaxNumVisibleItems );
- case PropertyId::MenuWidthMode:
- return menuWidthModeToString( mStyleConfig.menuWidthRule );
case PropertyId::SelectedIndex:
case PropertyId::SelectedText:
case PropertyId::ScrollBarStyle:
@@ -454,18 +200,16 @@ std::string UIDropDownList::getPropertyString( const PropertyDefinition* propert
if ( NULL != mListBox )
return mListBox->getPropertyString( propertyDef, propertyIndex );
default:
- return UITextInput::getPropertyString( propertyDef, propertyIndex );
+ return UIDropDown::getPropertyString( propertyDef, propertyIndex );
}
return "";
}
std::vector UIDropDownList::getPropertiesImplemented() const {
- auto props = UITextInput::getPropertiesImplemented();
- auto local = {
- PropertyId::PopUpToRoot, PropertyId::MaxVisibleItems, PropertyId::SelectedIndex,
- PropertyId::SelectedText, PropertyId::ScrollBarStyle, PropertyId::RowHeight,
- PropertyId::VScrollMode, PropertyId::HScrollMode, PropertyId::MenuWidthMode };
+ auto props = UIDropDown::getPropertiesImplemented();
+ auto local = { PropertyId::SelectedIndex, PropertyId::SelectedText, PropertyId::ScrollBarStyle,
+ PropertyId::RowHeight, PropertyId::VScrollMode, PropertyId::HScrollMode };
props.insert( props.end(), local.begin(), local.end() );
return props;
}
@@ -476,7 +220,7 @@ void UIDropDownList::loadFromXmlNode( const pugi::xml_node& node ) {
if ( NULL != mListBox )
mListBox->loadItemsFromXmlNode( node );
- UITextInput::loadFromXmlNode( node );
+ UIDropDown::loadFromXmlNode( node );
endAttributesTransaction();
}
@@ -486,13 +230,4 @@ void UIDropDownList::onClassChange() {
mListBox->setClasses( getClasses() );
}
-UIDropDownList* UIDropDownList::setMenuWidthMode( MenuWidthMode rule ) {
- mStyleConfig.menuWidthRule = rule;
- return this;
-}
-
-UIDropDownList::MenuWidthMode UIDropDownList::getMenuWidthMode() const {
- return mStyleConfig.menuWidthRule;
-}
-
}} // namespace EE::UI
diff --git a/src/eepp/ui/uidropdownmodellist.cpp b/src/eepp/ui/uidropdownmodellist.cpp
new file mode 100644
index 000000000..9eed3e2b5
--- /dev/null
+++ b/src/eepp/ui/uidropdownmodellist.cpp
@@ -0,0 +1,267 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define PUGIXML_HEADER_ONLY
+#include
+
+namespace EE { namespace UI {
+
+UIDropDownModelList* UIDropDownModelList::NewWithTag( const std::string& tag ) {
+ return eeNew( UIDropDownModelList, ( tag ) );
+}
+
+UIDropDownModelList* UIDropDownModelList::New() {
+ return eeNew( UIDropDownModelList, () );
+}
+
+UIDropDownModelList::UIDropDownModelList( const std::string& tag ) :
+ UIDropDown( tag ), mListView( NULL ) {
+ mListView = static_cast( createDefaultListView() );
+ mListView->setEnabled( false );
+ mListView->setVisible( false );
+ mListView->setParent( this );
+ mListView->setSingleClickNavigation( true );
+
+ mListView->on( Event::OnWidgetFocusLoss, [this]( auto event ) { onPopUpFocusLoss( event ); } );
+ mListView->on( Event::OnModelEvent, [this]( auto event ) { onItemSelected( event ); } );
+ mListView->on( Event::KeyDown, [this]( auto event ) { onItemKeyDown( event ); } );
+ mListView->on( Event::OnClear, [this]( auto event ) { onWidgetClear( event ); } );
+ mListViewCloseCb =
+ mListView->on( Event::OnClose, [this]( const Event* ) { mListView = nullptr; } );
+
+ mListView->setOnSelectionChange( [this]() {
+ if ( !mListView->getSelection().isEmpty() ) {
+ updateSelectionIndex();
+ sendCommonEvent( Event::OnSelectionChanged );
+ }
+ } );
+
+ applyDefaultTheme();
+}
+
+UIDropDownModelList::~UIDropDownModelList() {
+ if ( mListView != nullptr && mListViewCloseCb )
+ mListView->removeEventListener( mListViewCloseCb );
+ destroyListView();
+}
+
+UIWidget* UIDropDownModelList::createDefaultListView() {
+ return UIListView::NewWithTag( mTag + "::listview" );
+}
+
+Uint32 UIDropDownModelList::getType() const {
+ return UI_TYPE_DROPDOWNMODELLIST;
+}
+
+bool UIDropDownModelList::isType( const Uint32& type ) const {
+ return UIDropDownModelList::getType() == type ? true : UIDropDown::isType( type );
+}
+
+UIWidget* UIDropDownModelList::getPopUpWidget() const {
+ return mListView;
+}
+
+UIAbstractTableView* UIDropDownModelList::getListView() const {
+ return mListView;
+}
+
+void UIDropDownModelList::setListView( UIAbstractTableView* listView ) {
+ if ( listView == mListView )
+ return;
+
+ if ( mListView != nullptr ) {
+ if ( mListViewCloseCb )
+ mListView->removeEventListener( mListViewCloseCb );
+ mListView->close();
+ }
+
+ mListView = listView;
+ mListView->setEnabled( false );
+ mListView->setVisible( false );
+ mListView->setParent( this );
+
+ mListView->on( Event::OnWidgetFocusLoss, [this]( auto event ) { onPopUpFocusLoss( event ); } );
+ mListView->on( Event::OnModelEvent, [this]( auto event ) { onItemSelected( event ); } );
+ mListView->on( Event::KeyDown, [this]( auto event ) { onItemKeyDown( event ); } );
+ mListView->on( Event::OnClear, [this]( auto event ) { onWidgetClear( event ); } );
+ mListViewCloseCb =
+ mListView->on( Event::OnClose, [this]( const Event* ) { mListView = nullptr; } );
+
+ mListView->setOnSelectionChange( [this]() {
+ if ( !mListView->getSelection().isEmpty() ) {
+ sendCommonEvent( Event::OnSelectionChanged );
+ }
+ } );
+
+ if ( mModel ) {
+ mListView->setModel( mModel );
+ }
+}
+
+std::shared_ptr UIDropDownModelList::getModel() const {
+ return mModel;
+}
+
+void UIDropDownModelList::setModel( std::shared_ptr model ) {
+ mModel = model;
+ if ( mListView ) {
+ mListView->setModel( mModel );
+ }
+}
+
+Uint32 UIDropDownModelList::onMouseUp( const Vector2i& Pos, const Uint32& Flags ) {
+ if ( mEnabled && mVisible && isMouseOver() && NULL != mListView && mModel ) {
+ if ( Flags & EE_BUTTONS_WUWD ) {
+ if ( Flags & EE_BUTTON_WUMASK ) {
+ mListView->moveSelection( -1 );
+ updateSelectionIndex();
+ } else if ( Flags & EE_BUTTON_WDMASK ) {
+ if ( !mListView->getSelection().isEmpty() ) {
+ mListView->moveSelection( 1 );
+ updateSelectionIndex();
+ } else if ( mModel->hasChildren() ) {
+ mListView->getSelection().set( mModel->index( 0, 0 ) );
+ updateSelectionIndex();
+ }
+ }
+ }
+ }
+
+ return UIDropDown::onMouseUp( Pos, Flags );
+}
+
+Uint32 UIDropDownModelList::onKeyDown( const KeyEvent& Event ) {
+ if ( NULL != mListView )
+ mListView->onKeyDown( Event );
+
+ return UIDropDown::onKeyDown( Event );
+}
+
+UIDropDownModelList* UIDropDownModelList::showList() {
+ if ( NULL == mListView || NULL == mModel )
+ return this;
+
+ if ( !mListView->isVisible() ) {
+ if ( !mModel->hasChildren() )
+ return this;
+
+ Rectf tPadding = mListView->getPadding();
+
+ Float sliderValue = 0;
+ if ( mListView->getVerticalScrollBar() )
+ sliderValue = mListView->getVerticalScrollBar()->getValue();
+
+ Float contentsWidth = eeceil( PixelDensity::pxToDp(
+ mListView->getMaxColumnContentWidth( 0, true ) +
+ PixelDensity::dpToPx( mListView->getPadding().getWidth() ) +
+ ( mListView->getVerticalScrollBar()
+ ? mListView->getVerticalScrollBar()->getPixelsSize().getWidth()
+ : 0.f ) ) );
+
+ Float width = getPopUpWidth( contentsWidth );
+
+ Float height =
+ (Int32)( eemin( (Uint32)mModel->rowCount(), mStyleConfig.MaxNumVisibleItems ) *
+ mListView->getRowHeight() ) +
+ tPadding.Top + tPadding.Bottom + mListView->getHeaderHeight() +
+ ( mListView->getHorizontalScrollBar() &&
+ mListView->getHorizontalScrollBar()->isVisible()
+ ? mListView->getHorizontalScrollBar()->getSize().getHeight()
+ : 0.f );
+
+ mListView->setSize( width, height );
+
+ if ( mListView->getVerticalScrollBar() )
+ mListView->getVerticalScrollBar()->setValue( sliderValue );
+
+ alignPopUp( mListView );
+ } else {
+ hide();
+ }
+ return this;
+}
+
+UIDropDownModelList*
+UIDropDownModelList::setMaxNumVisibleItems( const Uint32& maxNumVisibleItems ) {
+ if ( maxNumVisibleItems != mStyleConfig.MaxNumVisibleItems ) {
+ mStyleConfig.MaxNumVisibleItems = maxNumVisibleItems;
+
+ if ( NULL != mListView && mModel )
+ mListView->setSize( getSize().getWidth(), std::min( mStyleConfig.MaxNumVisibleItems,
+ (Uint32)mModel->rowCount() ) *
+ mListView->getRowHeight() );
+ }
+ return this;
+}
+
+void UIDropDownModelList::onItemClicked( const Event* ) {
+ updateSelectionIndex();
+ hide();
+ setFocus();
+}
+
+void UIDropDownModelList::updateSelectionIndex() {
+ if ( mListView->getSelection().isEmpty() )
+ return;
+ ModelIndex idx = mListView->getSelection().first();
+ if ( idx.isValid() ) {
+ Variant var = mModel->data( idx, ModelRole::Display );
+ if ( var.isValid() && var.isString() )
+ setText( var.toString() );
+ sendCommonEvent( Event::OnItemSelected );
+ sendCommonEvent( Event::OnValueChange );
+ }
+}
+
+void UIDropDownModelList::onItemSelected( const Event* event ) {
+ if ( event->getType() == Event::OnModelEvent ) {
+ const ModelEvent* modelEvent = static_cast( event );
+ if ( modelEvent->getModelEventType() == ModelEventType::Open ) {
+ updateSelectionIndex();
+ hide();
+ setFocus();
+ }
+ }
+}
+
+void UIDropDownModelList::destroyListView() {
+ if ( !SceneManager::instance()->isShuttingDown() && NULL != mListView &&
+ mListView->getParent() != this ) {
+ mListView->setParent( this );
+ }
+}
+
+std::string UIDropDownModelList::getPropertyString( const PropertyDefinition* propertyDef,
+ const Uint32& propertyIndex ) const {
+ if ( NULL == propertyDef )
+ return "";
+
+ std::string res = UIDropDown::getPropertyString( propertyDef, propertyIndex );
+ if ( res.empty() && NULL != mListView ) {
+ res = mListView->getPropertyString( propertyDef, propertyIndex );
+ }
+ return res;
+}
+
+std::vector UIDropDownModelList::getPropertiesImplemented() const {
+ auto props = UIDropDown::getPropertiesImplemented();
+ if ( mListView ) {
+ auto listProps = mListView->getPropertiesImplemented();
+ props.insert( props.end(), listProps.begin(), listProps.end() );
+ }
+ return props;
+}
+
+void UIDropDownModelList::onClassChange() {
+ if ( mListView )
+ mListView->setClasses( getClasses() );
+}
+
+}} // namespace EE::UI
diff --git a/src/eepp/ui/uilistview.cpp b/src/eepp/ui/uilistview.cpp
index b922b6e8b..5be3ae1a7 100644
--- a/src/eepp/ui/uilistview.cpp
+++ b/src/eepp/ui/uilistview.cpp
@@ -6,7 +6,11 @@ UIListView* UIListView::New() {
return eeNew( UIListView, () );
}
-UIListView::UIListView() : UITableView( "listview" ) {
+UIListView* UIListView::NewWithTag( const std::string& tag ) {
+ return eeNew( UIListView, () );
+}
+
+UIListView::UIListView( const std::string& tag ) : UITableView( tag ) {
setHeadersVisible( false );
setAutoExpandOnSingleColumn( true );
applyDefaultTheme();
diff --git a/src/eepp/ui/uithememanager.cpp b/src/eepp/ui/uithememanager.cpp
index ff322973e..5e1a9bf14 100644
--- a/src/eepp/ui/uithememanager.cpp
+++ b/src/eepp/ui/uithememanager.cpp
@@ -14,8 +14,8 @@ UIThemeManager::UIThemeManager() :
mThemeDefault( NULL ),
mAutoApplyDefaultTheme( true ),
mEnableDefaultEffects( false ),
- mFadeInTime( Milliseconds( 100.f ) ),
- mFadeOutTime( Milliseconds( 100.f ) ),
+ mFadeInTime( Milliseconds( 25.f ) ),
+ mFadeOutTime( Milliseconds( 25.f ) ),
mTooltipTimeToShow( Milliseconds( 400 ) ),
mTooltipFollowMouse( false ),
mCursorSize( 16, 16 ) {}
diff --git a/src/eepp/ui/uiwidgetcreator.cpp b/src/eepp/ui/uiwidgetcreator.cpp
index c80b0c112..7c4a164d7 100644
--- a/src/eepp/ui/uiwidgetcreator.cpp
+++ b/src/eepp/ui/uiwidgetcreator.cpp
@@ -5,6 +5,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -67,6 +68,7 @@ void UIWidgetCreator::createBaseWidgetList() {
registeredWidget["radiobutton"] = UIRadioButton::New;
registeredWidget["combobox"] = UIComboBox::New;
registeredWidget["dropdownlist"] = UIDropDownList::New;
+ registeredWidget["dropdownmodellist"] = UIDropDownModelList::New;
registeredWidget["image"] = UIImage::New;
registeredWidget["listbox"] = UIListBox::New;
registeredWidget["menubar"] = UIMenuBar::New;
diff --git a/src/examples/ui_dropdownmodellist/ui_dropdownmodellist.cpp b/src/examples/ui_dropdownmodellist/ui_dropdownmodellist.cpp
new file mode 100644
index 000000000..48e1ad7c2
--- /dev/null
+++ b/src/examples/ui_dropdownmodellist/ui_dropdownmodellist.cpp
@@ -0,0 +1,51 @@
+#include
+#include
+#include
+
+using namespace EE::UI;
+using namespace EE::UI::Models;
+
+EE_MAIN_FUNC int main( int, char** ) {
+ UIApplication app( { 640, 480, "eepp - UIDropDownModelList Example" } );
+ app.getUI()->loadLayoutFromString( R"xml(
+
+
+
+
+
+ )xml" );
+
+ if ( !app.getWindow()->isOpen() )
+ return EXIT_FAILURE;
+
+ UIDropDownModelList* dropDown = app.getUI()->find( "dropdown" );
+ if ( dropDown ) {
+ std::vector options = { "Option 1: OpenGL", "Option 2: Vulkan",
+ "Option 3: Direct3D 11", "Option 4: Direct3D 12",
+ "Option 5: Metal", "Option 6: WebGL",
+ "Option 7: Software" };
+
+ auto model = ItemListOwnerModel::create( options );
+ dropDown->setModel( model );
+ dropDown->addEventListener( Event::OnItemSelected, []( const Event* event ) {
+ UIDropDownModelList* dropDown = event->getNode()->asType();
+ ModelIndex index = dropDown->getListView()->getSelection().first();
+ if ( index.isValid() ) {
+ String text = dropDown->getListView()->getModel()->data( index ).toString();
+ Log::info( "Selected item index: %d, value: %s", (int)index.row(), text.c_str() );
+ }
+ } );
+ }
+
+ return app.run();
+}
diff --git a/src/modules/eterm/src/eterm/system/process.cpp b/src/modules/eterm/src/eterm/system/process.cpp
index 5096493c5..b003b7458 100644
--- a/src/modules/eterm/src/eterm/system/process.cpp
+++ b/src/modules/eterm/src/eterm/system/process.cpp
@@ -248,42 +248,6 @@ static std::wstring stringToWideString( const std::string& str ) {
#define HANDLE_WIN_ERR( err ) HRESULT_FROM_WIN32( err ), PrintWinApiError( err )
-static HRESULT InitializeStartupInfoAttachedToPseudoConsole( STARTUPINFOEXW* pStartupInfo,
- HPCON hpc ) {
- // Prepare Startup Information structure
- STARTUPINFOEXW si;
- ZeroMemory( &si, sizeof( si ) );
- si.StartupInfo.cb = sizeof( STARTUPINFOEXW );
-
- // Discover the size required for the list
- SIZE_T bytesRequired;
- InitializeProcThreadAttributeList( NULL, 1, 0, &bytesRequired );
-
- // Allocate memory to represent the list
- si.lpAttributeList =
- (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc( GetProcessHeap(), 0, bytesRequired );
- if ( !si.lpAttributeList ) {
- return E_OUTOFMEMORY;
- }
-
- // Initialize the list memory location
- if ( !InitializeProcThreadAttributeList( si.lpAttributeList, 1, 0, &bytesRequired ) ) {
- HeapFree( GetProcessHeap(), 0, si.lpAttributeList );
- return HRESULT_FROM_WIN32( GetLastError() );
- }
-
- // Set the pseudoconsole information into the list
- if ( !UpdateProcThreadAttribute( si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
- hpc, sizeof( hpc ), NULL, NULL ) ) {
- HeapFree( GetProcessHeap(), 0, si.lpAttributeList );
- return HRESULT_FROM_WIN32( GetLastError() );
- }
-
- *pStartupInfo = si;
-
- return S_OK;
-}
-
Process::Process( AutoHandle&& hProcess, void* lpAttributeList, int pid ) :
mStatus( ProcessStatus::RUNNING ),
mExitCode( 1 ),
@@ -393,11 +357,13 @@ std::unique_ptr Process::createWithPipe( const std::string& program,
oss << ' ' << arg;
}
std::wstring commandLine = stringToWideString( oss.str() );
- // TODO: If workingDirectory is relative, make it absolute (relative to the
- // current process working directory)
+
+ std::vector cmdLineMutable(commandLine.begin(), commandLine.end());
+ cmdLineMutable.push_back(0);
+
std::wstring workingDir = stringToWideString( workingDirectory );
- if ( CreateProcessW( NULL, (LPWSTR)commandLine.c_str(), NULL, NULL, TRUE, 0, NULL,
+ if ( CreateProcessW( NULL, (LPWSTR)cmdLineMutable.data(), NULL, NULL, TRUE, 0, NULL,
workingDir.empty() ? NULL : workingDir.c_str(), &siStartInfo,
&piProcInfo ) ) {
std::unique_ptr pipe = std::unique_ptr(
@@ -434,6 +400,12 @@ Process::createWithPseudoTerminal( const std::string& program, const std::vector
AutoHandle hThread{};
STARTUPINFOEXW startupInfo{};
+ startupInfo.StartupInfo.cb = sizeof( STARTUPINFOEXW );
+ startupInfo.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
+ startupInfo.StartupInfo.hStdInput = INVALID_HANDLE_VALUE;
+ startupInfo.StartupInfo.hStdOutput = INVALID_HANDLE_VALUE;
+ startupInfo.StartupInfo.hStdError = INVALID_HANDLE_VALUE;
+
PROCESS_INFORMATION piClient{};
std::ostringstream oss;
@@ -442,19 +414,32 @@ Process::createWithPseudoTerminal( const std::string& program, const std::vector
oss << ' ' << arg;
}
std::wstring commandLine = stringToWideString( oss.str() );
- // TODO: If workingDirectory is relative, make it absolute (relative to the
- // current process working directory)
std::wstring workingDir = stringToWideString( workingDirectory );
- if ( ( hr = InitializeStartupInfoAttachedToPseudoConsole( &startupInfo,
- pseudoTerminal.mPHPC ) ) != S_OK ) {
- Log::error( "InitializeStartupInfoAttachedToPseudoConsole failed." );
- PrintErrorResult( hr );
+ SIZE_T bytesRequired = 0;
+ InitializeProcThreadAttributeList( NULL, 1, 0, &bytesRequired );
+
+ startupInfo.lpAttributeList =
+ (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc( GetProcessHeap(), 0, bytesRequired );
+
+ if ( !startupInfo.lpAttributeList ) {
+ Log::error( "HeapAlloc failed for attribute list." );
+ goto fail;
+ }
+
+ if ( !InitializeProcThreadAttributeList( startupInfo.lpAttributeList, 1, 0, &bytesRequired ) ) {
+ Log::error( "InitializeProcThreadAttributeList failed." );
+ goto fail;
+ }
+
+ if ( !UpdateProcThreadAttribute( startupInfo.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
+ pseudoTerminal.mPHPC, sizeof( HPCON ), NULL, NULL ) ) {
+ Log::error( "UpdateProcThreadAttribute failed." );
goto fail;
}
hr = CreateProcessW( NULL, // No module name - use Command Line
- (wchar_t*)commandLine.c_str(), // Command Line
+ commandLine.data(), // Must be a mutable buffer to prevent ERROR_INSUFFICIENT_BUFFER
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Inherit handles
@@ -480,6 +465,10 @@ Process::createWithPseudoTerminal( const std::string& program, const std::vector
return std::unique_ptr(
new Process( std::move( hProcess ), startupInfo.lpAttributeList, piClient.dwProcessId ) );
fail:
+ if ( startupInfo.lpAttributeList ) {
+ DeleteProcThreadAttributeList( startupInfo.lpAttributeList );
+ HeapFree( GetProcessHeap(), 0, startupInfo.lpAttributeList );
+ }
return std::unique_ptr();
}
@@ -489,4 +478,4 @@ int Process::pid() {
}} // namespace eterm::System
-#endif
+#endif
\ No newline at end of file
diff --git a/src/tests/unit_tests/uidropdownmodellist.cpp b/src/tests/unit_tests/uidropdownmodellist.cpp
new file mode 100644
index 000000000..97854e7a4
--- /dev/null
+++ b/src/tests/unit_tests/uidropdownmodellist.cpp
@@ -0,0 +1,43 @@
+#include "utest.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace EE;
+using namespace EE::Window;
+using namespace EE::Scene;
+using namespace EE::UI;
+using namespace EE::UI::Models;
+
+UTEST( UIDropDownModelList, basicFunctionality ) {
+ UIApplication app(
+ WindowSettings( 800, 600, "eepp - UIDropDownModelList Test", WindowStyle::Default,
+ WindowBackend::Default, 32, {}, 1, false, true ),
+ UIApplication::Settings( Sys::getProcessPath() + ".." + FileSystem::getOSSlash(), 1.5 ) );
+ FileSystem::changeWorkingDirectory( Sys::getProcessPath() );
+
+ UISceneNode* sceneNode = app.getUI();
+
+ UIDropDownModelList* dropDown = UIDropDownModelList::New();
+ dropDown->setParent( sceneNode );
+
+ std::vector items = { "Item 1", "Item 2", "Item 3" };
+ auto model = ItemListOwnerModel::create( items );
+ dropDown->setModel( model );
+
+ // Model should be set
+ EXPECT_TRUE( dropDown->getModel() == model );
+
+ // Items count should match
+ EXPECT_EQ( dropDown->getListView()->getModel()->rowCount(), 3ul );
+
+ // Max visible items
+ dropDown->setMaxNumVisibleItems( 2 );
+ EXPECT_EQ( dropDown->getMaxNumVisibleItems(), 2ul );
+}
diff --git a/src/thirdparty/backward-cpp/backward.hpp b/src/thirdparty/backward-cpp/backward.hpp
index 8c44d657d..f92637426 100644
--- a/src/thirdparty/backward-cpp/backward.hpp
+++ b/src/thirdparty/backward-cpp/backward.hpp
@@ -332,6 +332,10 @@
#if defined(BACKWARD_SYSTEM_WINDOWS)
+#ifdef __GNUC__
+#include
+#endif
+
#include
#include
#include
@@ -612,14 +616,20 @@ template struct demangler_impl {
static std::string demangle(const char *funcname) { return funcname; }
};
-#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN)
+#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) || (defined(BACKWARD_SYSTEM_WINDOWS) && defined(__GNUC__))
template <> struct demangler_impl {
demangler_impl() : _demangle_buffer_length(0) {}
std::string demangle(const char *funcname) {
using namespace details;
- char *result = abi::__cxa_demangle(funcname, _demangle_buffer.get(),
+ const char* target = funcname;
+ std::string mangled;
+ if (target && target[0] == 'Z') {
+ mangled = "_" + std::string(target);
+ target = mangled.c_str();
+ }
+ char *result = abi::__cxa_demangle(target, _demangle_buffer.get(),
&_demangle_buffer_length, nullptr);
if (result) {
_demangle_buffer.update(result);
@@ -3674,41 +3684,38 @@ public:
ResolvedTrace resolve(ResolvedTrace t) override {
HANDLE process = GetCurrentProcess();
- char name[256];
-
memset(&sym, 0, sizeof(sym));
sym.sym.SizeOfStruct = sizeof(SYMBOL_INFO);
sym.sym.MaxNameLen = max_sym_len;
if (!SymFromAddr(process, (ULONG64)t.addr, &displacement, &sym.sym)) {
- // TODO: error handling everywhere
- char* lpMsgBuf;
- DWORD dw = GetLastError();
-
- if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
- FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS,
- NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
- (char*)&lpMsgBuf, 0, NULL)) {
- std::fprintf(stderr, "%s\n", lpMsgBuf);
- LocalFree(lpMsgBuf);
- }
-
- // abort();
+ t.object_function = "??";
+ return t;
+ }
+ std::string name = demangle(sym.sym.Name);
+ if (name == sym.sym.Name) {
+ char undecorated[256];
+ if (UnDecorateSymbolName(sym.sym.Name, undecorated, 256, UNDNAME_COMPLETE)) {
+ name = undecorated;
+ }
}
- UnDecorateSymbolName(sym.sym.Name, (PSTR)name, 256, UNDNAME_COMPLETE);
DWORD offset = 0;
IMAGEHLP_LINE line;
if (SymGetLineFromAddr(process, (ULONG64)t.addr, &offset, &line)) {
- t.object_filename = line.FileName;
t.source.filename = line.FileName;
t.source.line = line.LineNumber;
t.source.col = offset;
}
+ IMAGEHLP_MODULE64 module;
+ memset(&module, 0, sizeof(module));
+ module.SizeOfStruct = sizeof(module);
+ if (SymGetModuleInfo64(process, (ULONG64)t.addr, &module)) {
+ t.object_filename = module.ModuleName;
+ }
+
t.source.function = name;
- t.object_filename = "";
t.object_function = name;
return t;
diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp
index 1538a2b87..41ef068b9 100644
--- a/src/tools/ecode/appconfig.cpp
+++ b/src/tools/ecode/appconfig.cpp
@@ -732,6 +732,15 @@ void AppConfig::loadDocuments( UICodeEditorSplitter* editorSplitter, json j,
editorLoadedCounter( app );
} else {
+ auto isSnapshotFile =
+ std::find_if( sessionSnapshotFiles.begin(), sessionSnapshotFiles.end(),
+ [&path]( const SessionSnapshotFile& file ) {
+ return file.cachePath == path;
+ } );
+
+ if ( isSnapshotFile != sessionSnapshotFiles.end() )
+ return; // snapshot files are loaded later
+
editorSplitter->loadAsyncFileFromPathInNewTab(
path,
[this, curTabWidget, selection, totalToLoad, currentPage, app, path,
diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp
index 98e1453b1..40f29b57a 100644
--- a/src/tools/ecode/ecode.cpp
+++ b/src/tools/ecode/ecode.cpp
@@ -4566,7 +4566,11 @@ void App::init( InitParameters& params ) {
} else if ( mConfig.term.warnBeforeClosingTab && widget->isType( UI_TYPE_TERMINAL ) ) {
UITerminal* term = widget->asType();
ProcessID pid = term->getTerm()->getTerminal()->getProcess()->pid();
+ std::string msgBoxId = String::format( "msgbox_%p", this );
if ( Sys::processHasChildren( pid ) ) {
+ if ( nullptr != getUISceneNode()->find( msgBoxId ) )
+ return false;
+
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::OK_CANCEL,
i18n( "terminal_close_warn", "Are you sure you want to close this "
@@ -4584,6 +4588,7 @@ void App::init( InitParameters& params ) {
msgBox->setTitle( "ecode" );
msgBox->center();
msgBox->showWhenReady();
+ msgBox->setId( msgBoxId );
return false;
}
}
diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp
index ce6a0241b..7ff601c00 100644
--- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp
+++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp
@@ -98,7 +98,10 @@ static std::map parseLLMProviders( const nlohmann::jso
model.cacheConfiguration = cache;
}
- provider.models.push_back( model );
+ model.hash = hashCombine( std::hash()( model.name ),
+ std::hash()( model.provider ) );
+
+ provider.models.emplace_back( std::move( model ) );
}
}
diff --git a/src/tools/ecode/plugins/aiassistant/chatui.cpp b/src/tools/ecode/plugins/aiassistant/chatui.cpp
index f45b43f7e..a3925f1fc 100644
--- a/src/tools/ecode/plugins/aiassistant/chatui.cpp
+++ b/src/tools/ecode/plugins/aiassistant/chatui.cpp
@@ -30,6 +30,107 @@ using namespace EE::Window;
namespace ecode {
+class LLMModelsModel : public Model {
+ public:
+ enum Columns { Name, Provider, Hash };
+
+ LLMModelsModel( const std::vector& models, UISceneNode* uiSceneNode = nullptr ) :
+ mModels( models ), mUISceneNode( uiSceneNode ) {
+ mCurModels.reserve( mModels.size() );
+ for ( const auto& model : mModels ) {
+ mCurModels.emplace_back( &model );
+ }
+ }
+
+ virtual size_t rowCount( const ModelIndex& = ModelIndex() ) const override {
+ return mCurModels.size();
+ }
+
+ virtual size_t columnCount( const ModelIndex& = ModelIndex() ) const override { return 2; }
+
+ virtual std::string columnName( const size_t& column ) const override {
+ switch ( column ) {
+ case Columns::Name:
+ return mUISceneNode ? mUISceneNode->i18n( "name", "Name" ) : "Name";
+ case Columns::Provider:
+ return mUISceneNode ? mUISceneNode->i18n( "provider", "Provider" ) : "Provider";
+ case Columns::Hash:
+ return mUISceneNode ? mUISceneNode->i18n( "hash", "Hash" ) : "Hash";
+ }
+ return "";
+ }
+
+ virtual Variant data( const ModelIndex& index,
+ ModelRole role = ModelRole::Display ) const override {
+ if ( role != ModelRole::Display )
+ return {};
+
+ if ( index.row() < 0 || static_cast( index.row() ) >= mCurModels.size() )
+ return {};
+
+ const auto& model = *mCurModels[index.row()];
+
+ switch ( index.column() ) {
+ case Columns::Name: {
+ if ( model.displayName.has_value() && !model.displayName->empty() ) {
+ return Variant( model.displayName->c_str() );
+ }
+ return Variant( model.name.c_str() );
+ }
+ case Columns::Provider: {
+ return Variant( model.provider.c_str() );
+ }
+ case Columns::Hash: {
+ return Variant( static_cast( model.hash ) );
+ }
+ }
+
+ return {};
+ }
+
+ ModelIndex getFromHash( Uint64 hash ) {
+ auto it = std::find_if( mCurModels.begin(), mCurModels.end(),
+ [hash]( const LLMModel* model ) { return hash == model->hash; } );
+ return it != mCurModels.end() ? index( std::distance( mCurModels.begin(), it ) )
+ : index( 0 );
+ }
+
+ void setFilter( const std::string& filter ) {
+ if ( mCurFilter == filter )
+ return;
+ mCurFilter = filter;
+ mCurModels.clear();
+
+ for ( const auto& model : mModels ) {
+ if ( filter.empty() ) {
+ mCurModels.emplace_back( &model );
+ continue;
+ }
+
+ bool matchesName = String::icontains( model.name, filter );
+ bool matchesDisplayName =
+ model.displayName.has_value() && String::icontains( *model.displayName, filter );
+ bool matchesProvider = String::icontains( model.provider, filter );
+
+ if ( matchesName || matchesDisplayName || matchesProvider ) {
+ mCurModels.emplace_back( &model );
+ }
+ }
+
+ invalidate();
+ }
+
+ const std::vector& getCurModels() const { return mCurModels; }
+
+ void refresh() { setFilter( mCurFilter ); }
+
+ protected:
+ const std::vector& mModels;
+ std::vector mCurModels;
+ std::string mCurFilter;
+ UISceneNode* mUISceneNode;
+};
+
static const char* DEFAULT_PROVIDER = "google";
static const char* DEFAULT_MODEL = "gemini-2.5-flash";
@@ -71,13 +172,7 @@ LLMChat::Role LLMChat::stringToRole( UIPushButton* userBut ) {
static const char* DEFAULT_LAYOUT = R"xml(
@@ -172,17 +291,17 @@ DropDownList.role_ui {
+
+
+
+
-
-
-
-
+
-
@@ -219,10 +338,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
->asType();
mChatsList = findByClass( "llm_chats" );
- mModelDDL = findByClass( "model_ui" );
-
- // mRefreshModels = find( "refresh_model_ui" );
- // mRefreshModels->onClick( [this]( auto ) { execute( "ai-refresh-local-models" ); } );
+ mModelBtn = findByClass( "model_ui" );
mChatMore = find( "llm_more" );
mChatMore->onClick( [this]( auto ) { execute( "ai-show-menu" ); } );
@@ -276,10 +392,6 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
mChatUserRole = find( "llm_user" );
mChatUserRole->onClick( [this]( auto ) { execute( "ai-chat-toggle-role" ); } );
- /* mChatPrivate = find( "llm_private_chat" );
- mChatPrivate->on( Event::OnValueChange,
- [this]( auto ) { mChatIsPrivate = mChatPrivate->isSelected(); } ); */
-
auto setCmd = [this]( const std::string& name, const CommandCallback& cb ) {
setCommand( name, cb );
mChatInput->getDocument().setCommand( name, cb );
@@ -387,9 +499,26 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
mLocateInput->getDocument().selectAll();
} );
+ setCmd( "ai-select-model", [this] {
+ if ( !mLocateModelBarLayout->isVisible() ) {
+ if ( mLocateModelTable->getModel() ) {
+ mLocateModelInput->setText( "" );
+ static_cast( mLocateModelTable->getModel() )->setFilter( "" );
+ }
+
+ showSelectModel();
+
+ mLocateModelTable->runOnMainThread( [this] {
+ auto model = static_cast( mLocateModelTable->getModel() );
+ if ( model )
+ mLocateModelTable->setSelection( model->getFromHash( mCurModel.hash ) );
+ } );
+ } else
+ hideSelectModel();
+ } );
+
setCmd( "ai-toggle-private-chat", [this] {
mChatIsPrivate = !mChatIsPrivate;
- /* mChatPrivate->toggleSelection(); */
if ( mChatIsPrivate )
mChatInput->addClass( "incognito" );
@@ -533,7 +662,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
} );
} );
- setCmd( "ai-refresh-local-models", [this] { fillApiModels( mModelDDL ); } );
+ setCmd( "ai-refresh-local-models", [this] { fillApiModels(); } );
mChatHistory = find( "llm_chat_history" );
mChatHistory->onClick( [this]( auto ) { showChatHistory(); } );
@@ -541,10 +670,13 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
mChatAttach = find( "llm_attach" );
mChatAttach->onClick( [this]( auto ) { execute( "ai-show-add-context-menu" ); } );
+ mModelBtn->onClick( [this]( auto ) { execute( "ai-select-model" ); } );
+
if ( getPlugin() == nullptr )
return;
initAttachFile();
+ initSelectModel();
auto providers = getPlugin()->getProviders();
setProviders( std::move( providers ) );
@@ -566,7 +698,7 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
}
}
- fillModelDropDownList( mModelDDL );
+ fillModelDropDownList();
const auto appendShortcutToTooltip = [this]( UIPushButton* but, const std::string& cmd ) {
auto kb = getKeyBindings().getCommandKeybindString( cmd );
@@ -583,10 +715,9 @@ LLMChatUI::LLMChatUI( PluginManager* manager ) :
appendShortcutToTooltip( mChatStop, "ai-prompt" );
appendShortcutToTooltip( mChatAdd, "ai-add-chat" );
appendShortcutToTooltip( mChatSettings, "ai-settings" );
- // appendShortcutToTooltip( mChatPrivate, "ai-toggle-private-chat" );
appendShortcutToTooltip( mChatMore, "ai-show-menu" );
appendShortcutToTooltip( mChatUserRole, "ai-chat-toggle-role" );
- // appendShortcutToTooltip( mRefreshModels, "ai-refresh-local-models" );
+ appendShortcutToTooltip( mModelBtn, "ai-select-model" );
addKb( mChatInput, "mod+keypad enter", "ai-prompt", true, false );
addKb( mChatInput, "mod+shift+keypad enter", "ai-add-chat", true, false );
@@ -643,6 +774,7 @@ void LLMChatUI::bindCmds( UICodeEditor* editor, bool bindToChatUI ) {
addKb( editor, "mod+shift+l", "ai-refresh-local-models", bindToChatUI );
addKb( editor, "mod+shift+a", "ai-attach-file", bindToChatUI );
addKb( editor, "mod+shift+z", "ai-link-file", bindToChatUI );
+ addKb( editor, "mod+shift+x", "ai-select-model", bindToChatUI );
if ( bindToChatUI )
addKb( editor, "mod+shift+return", "ai-add-chat", bindToChatUI );
@@ -663,6 +795,14 @@ std::optional LLMChatUI::getModel( const std::string& provider,
return {};
}
+std::optional LLMChatUI::getModel( Uint64 hash ) {
+ auto modelIt = std::find_if( mModels.begin(), mModels.end(),
+ [hash]( const LLMModel& model ) { return hash == model.hash; } );
+ if ( modelIt != mModels.end() )
+ return *modelIt;
+ return {};
+}
+
void LLMChatUI::showChatHistory() {
auto plugin = getPlugin();
if ( plugin == nullptr )
@@ -832,8 +972,10 @@ void LLMChatUI::showChatHistory() {
} );
}
-void LLMChatUI::fillApiModels( UIDropDownList* modelDDL ) {
+void LLMChatUI::fillApiModels() {
mPendingModelsToLoad = 0;
+ mNewModels.clear();
+
for ( auto& [name, data] : mProviders ) {
if ( !data.enabled || !data.fetchModelsUrl )
continue;
@@ -869,6 +1011,8 @@ void LLMChatUI::fillApiModels( UIDropDownList* modelDDL ) {
model.displayName = el.value( "display_name", "" );
model.isEphemeral = true;
+ model.hash = hashCombine( std::hash()( model.name ),
+ std::hash()( model.provider ) );
if ( model.name.empty() )
continue;
@@ -877,39 +1021,25 @@ void LLMChatUI::fillApiModels( UIDropDownList* modelDDL ) {
model.maxOutputTokens = el.value( "max_context_length", 0 );
data.models.emplace_back( model );
+ mNewModels.push_back( model );
}
mPendingModelsToLoad++;
- std::string pname = name;
- modelDDL->runOnMainThread( [pname = std::move( pname ), modelDDL, this] {
- String providerName( pname );
- std::vector removeValues;
- size_t count = modelDDL->getListBox()->getItemsCount();
- for ( size_t i = 0; i < count; i++ ) {
- const String& txt = modelDDL->getListBox()->getItemText( i );
- if ( txt.contains( providerName ) )
- removeValues.emplace_back( txt );
- }
-
- for ( const auto& val : removeValues )
- modelDDL->getListBox()->removeListBoxItem( val );
-
- const auto& models = mProviders[pname].models;
- std::vector newModels;
- for ( const auto& model : models ) {
- if ( !model.isEphemeral )
- continue;
- newModels.emplace_back( String::format( "%s (%s)", model.name, pname ) );
- mModelsMap[newModels[newModels.size() - 1].getHash()] = model;
- }
-
- modelDDL->getListBox()->addListBoxItems( newModels );
-
+ runOnMainThread( [this] {
mPendingModelsToLoad--;
+ if ( mPendingModelsToLoad == 0 ) {
+ mModels.erase(
+ std::remove_if( mModels.begin(), mModels.end(),
+ []( const LLMModel& model ) { return model.isEphemeral; } ),
+ mModels.end() );
- if ( mPendingModelsToLoad == 0 )
+ mModels.insert( mModels.end(), mNewModels.begin(), mNewModels.end() );
+
+ if ( mLocateModelTable && mLocateModelTable->getModel() )
+ loadSelectModel();
onInit();
+ }
} );
}
@@ -926,46 +1056,28 @@ String LLMChatUI::getModelDisplayName( const LLMModel& model ) const {
data.displayName ? *data.displayName : String::capitalize( data.name ) );
}
-bool LLMChatUI::selectModel( UIDropDownList* modelDDL, const LLMModel& model ) {
- auto modelName = getModelDisplayName( model );
- auto index = modelDDL->getListBox()->getItemIndex( modelName );
- if ( index != eeINDEX_NOT_FOUND ) {
- modelDDL->getListBox()->setSelected( index );
+bool LLMChatUI::selectModel( std::optional model ) {
+ if ( model ) {
+ mModelBtn->setText( getModelDisplayName( *model ) );
+ mCurModel = *model;
return true;
}
return false;
}
-void LLMChatUI::fillModelDropDownList( UIDropDownList* modelDDL ) {
- std::vector models;
- std::size_t selectedIndex = 0;
+void LLMChatUI::fillModelDropDownList() {
+ mModels.clear();
+ std::size_t reserve = 0;
+ for ( const auto& [_, data] : mProviders )
+ reserve += data.models.size();
+ mModels.reserve( reserve + 8 /* extra space for local models */ );
for ( const auto& [name, data] : mProviders ) {
if ( !data.enabled )
continue;
-
- for ( const auto& model : data.models ) {
- String modelName( String::format(
- "%s (%s)", model.displayName ? *model.displayName : model.name,
- data.displayName ? *data.displayName : String::capitalize( data.name ) ) );
- mModelsMap[modelName.getHash()] = model;
- if ( model.provider == mCurModel.provider && model.name == mCurModel.name )
- selectedIndex = models.size();
- models.push_back( std::move( modelName ) );
- }
+ for ( const auto& model : data.models )
+ mModels.push_back( model );
}
- modelDDL->getListBox()->clear();
- modelDDL->getListBox()->addListBoxItems( std::move( models ) );
- modelDDL->getListBox()->setSelected( selectedIndex );
- modelDDL->on( Event::OnValueChange, [this, modelDDL]( auto ) {
- auto selectedModel =
- mModelsMap.find( modelDDL->getListBox()->getItemSelectedText().getHash() );
- if ( selectedModel != mModelsMap.end() ) {
- mCurModel = selectedModel->second;
- }
- } );
-
- modelDDL->getUISceneNode()->getThreadPool()->run(
- [this, modelDDL] { fillApiModels( modelDDL ); } );
+ getUISceneNode()->getThreadPool()->run( [this] { fillApiModels(); } );
}
void LLMChatUI::resizeToFit( UICodeEditor* editor ) {
@@ -1077,8 +1189,8 @@ std::string LLMChatUI::unserialize( const nlohmann::json& payload ) {
if ( mCurModel.name.empty() )
return payload.value( "input", "" );
- if ( !selectModel( mModelDDL, mCurModel ) )
- fillModelDropDownList( mModelDDL );
+ if ( !selectModel( mCurModel ) )
+ fillModelDropDownList();
if ( payload.contains( "chat" ) && payload["chat"].is_object() ) {
const auto& chat = payload["chat"];
@@ -1488,10 +1600,10 @@ const LLMModel& LLMChatUI::getCheapestModelFromCurrentProvider() const {
}
void LLMChatUI::onInit() {
- if ( !mModelDDL )
+ if ( !mModelBtn )
return;
- if ( getModelDisplayName( mCurModel ) != mModelDDL->getListBox()->getItemSelectedText() )
- selectModel( mModelDDL, mCurModel );
+ if ( getModelDisplayName( mCurModel ) != mModelBtn->getText() )
+ selectModel( mCurModel );
}
void LLMChatUI::updateTabTitle() {
@@ -1549,9 +1661,12 @@ void LLMChatUI::updateLocateBarColumns() {
mLocateTable->setColumnWidth( 1, width - mLocateTable->getColumnWidth( 0 ) );
}
+// File picker
+
void LLMChatUI::showAttachFile() {
if ( getPlugin() == nullptr )
return;
+ hideSelectModel();
auto text = mLocateInput->getText();
auto ctx = getPlugin()->getPluginContext();
if ( !ctx->isDirTreeReady() ) {
@@ -1687,4 +1802,90 @@ void LLMChatUI::initAttachFile() {
} );
}
+// Model Picker
+
+void LLMChatUI::updateLocateModelBarColumns() {
+ Float width = eeceil( mLocateModelTable->getPixelsSize().getWidth() );
+ width -= mLocateModelTable->getVerticalScrollBar()->getPixelsSize().getWidth();
+ mLocateModelTable->setColumnsVisible( { 0, 1 } );
+ mLocateModelTable->setColumnWidth( 0, eeceil( width * 0.8 ) );
+ mLocateModelTable->setColumnWidth( 1, width - mLocateModelTable->getColumnWidth( 0 ) );
+}
+
+void LLMChatUI::loadSelectModel() {
+ auto ctx = getPlugin()->getPluginContext();
+ mLocateModelTable->setModel(
+ std::make_shared( mModels, ctx->getUISceneNode() ) );
+
+ static_cast( mLocateModelTable->getModel() )
+ ->setFilter( mLocateModelInput->getText() );
+}
+
+void LLMChatUI::showSelectModel() {
+ if ( getPlugin() == nullptr )
+ return;
+ hideAttachFile();
+
+ if ( nullptr == mLocateModelTable->getModel() )
+ loadSelectModel();
+
+ static_cast( mLocateModelTable->getModel() )
+ ->setFilter( mLocateModelInput->getText() );
+
+ mLocateModelBarLayout->setVisible( true );
+ mLocateModelInput->setFocus();
+ updateLocateModelBarColumns();
+}
+
+void LLMChatUI::hideSelectModel() {
+ mLocateModelBarLayout->setVisible( false );
+}
+
+void LLMChatUI::initSelectModel() {
+ mLocateModelBarLayout = findByClass( "llm_chat_select_model" );
+ mLocateModelInput = findByClass( "llm_chat_select_model_input" );
+ mLocateModelTable = findByClass( "llm_chat_model_locate" );
+ mLocateModelTable->setHeadersVisible( false );
+
+ mLocateModelTable->on( Event::OnSizeChange,
+ [this]( const Event* ) { updateLocateModelBarColumns(); } );
+
+ mLocateModelInput->on( Event::OnTextChanged, [this]( const Event* ) {
+ showSelectModel();
+ updateLocateModelBarColumns();
+ } );
+ mLocateModelInput->on( Event::OnPressEnter, [this]( const Event* ) {
+ KeyEvent keyEvent( mLocateModelTable, Event::KeyDown, KEY_RETURN, SCANCODE_UNKNOWN, 0, 0 );
+ mLocateModelTable->forceKeyDown( keyEvent );
+ } );
+ mLocateModelInput->on( Event::KeyDown, [this]( const Event* event ) {
+ const KeyEvent* keyEvent = static_cast( event );
+ mLocateModelTable->forceKeyDown( *keyEvent );
+ } );
+ mLocateModelBarLayout->setCommand( "close-locatebar", [this] {
+ hideSelectModel();
+ if ( mChatInput )
+ mChatInput->setFocus();
+ } );
+ mLocateModelBarLayout->getKeyBindings().addKeybindsString( {
+ { "escape", "close-locatebar" },
+ } );
+ mLocateModelTable->on( Event::KeyDown, [this]( const Event* event ) {
+ const KeyEvent* keyEvent = static_cast( event );
+ if ( keyEvent->getKeyCode() == KEY_ESCAPE )
+ mLocateModelBarLayout->execute( "close-locatebar" );
+ } );
+ mLocateModelTable->on( Event::OnModelEvent, [this]( const Event* event ) {
+ const ModelEvent* modelEvent = static_cast( event );
+ if ( modelEvent->getModelEventType() == ModelEventType::Open ) {
+ Variant vHash( modelEvent->getModel()->data(
+ modelEvent->getModel()->index( modelEvent->getModelIndex().row(),
+ LLMModelsModel::Hash ),
+ ModelRole::Display ) );
+ selectModel( getModel( vHash.asUint64() ) );
+ mLocateModelBarLayout->execute( "close-locatebar" );
+ }
+ } );
+}
+
} // namespace ecode
diff --git a/src/tools/ecode/plugins/aiassistant/chatui.hpp b/src/tools/ecode/plugins/aiassistant/chatui.hpp
index f8e5edec6..0f3c2c73f 100644
--- a/src/tools/ecode/plugins/aiassistant/chatui.hpp
+++ b/src/tools/ecode/plugins/aiassistant/chatui.hpp
@@ -110,20 +110,28 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
UIPushButton* mChatAttach{ nullptr };
UISelectButton* mChatPrivate{ nullptr };
UIScrollView* mChatScrollView{ nullptr };
- UIDropDownList* mModelDDL{ nullptr };
+ UIPushButton* mModelBtn{ nullptr };
+
+ // Locate file
UIVLinearLayoutCommandExecuter* mLocateBarLayout{ nullptr };
UITextInput* mLocateInput{ nullptr };
UITableView* mLocateTable{ nullptr };
+ // Select model
+ UIVLinearLayoutCommandExecuter* mLocateModelBarLayout{ nullptr };
+ UITextInput* mLocateModelInput{ nullptr };
+ UITableView* mLocateModelTable{ nullptr };
+
std::unique_ptr mRequest;
std::unique_ptr mSummaryRequest;
LLMProviders mProviders;
LLMModel mCurModel;
- std::unordered_map mModelsMap;
+ std::vector mModels;
int mPendingModelsToLoad{ 0 };
bool mChatIsPrivate{ false };
bool mChatLocked{ false };
bool mLinkMode{ false };
+ std::vector mNewModels;
LLMModel findModel( const std::string& provider, const std::string& model );
@@ -151,13 +159,13 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
UIWidget* addChatUI( LLMChat::Role role );
- void fillApiModels( UIDropDownList* modelDDL );
+ void fillApiModels();
String getModelDisplayName( const LLMModel& model ) const;
- bool selectModel( UIDropDownList* modelDDL, const LLMModel& model );
+ bool selectModel( std::optional model );
- void fillModelDropDownList( UIDropDownList* modelDDL );
+ void fillModelDropDownList();
void resizeToFit( UICodeEditor* editor );
@@ -173,6 +181,8 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
std::optional getModel( const std::string& provider, const std::string& modelName );
+ std::optional getModel( Uint64 hash );
+
void saveChat();
void onInit();
@@ -188,12 +198,22 @@ class LLMChatUI : public UILinearLayout, public WidgetCommandExecuter {
void initAttachFile();
+ void initSelectModel();
+
void updateLocateBarColumns();
void showAttachFile();
void hideAttachFile();
+ void updateLocateModelBarColumns();
+
+ void loadSelectModel();
+
+ void showSelectModel();
+
+ void hideSelectModel();
+
void insertFileToDocument( std::string path, std::shared_ptr cdoc );
void replaceFileLinksToContents( std::string& text );
diff --git a/src/tools/ecode/plugins/aiassistant/protocol.hpp b/src/tools/ecode/plugins/aiassistant/protocol.hpp
index 0382f4add..9166bf2f2 100644
--- a/src/tools/ecode/plugins/aiassistant/protocol.hpp
+++ b/src/tools/ecode/plugins/aiassistant/protocol.hpp
@@ -14,6 +14,7 @@ struct LLMCacheConfiguration {
};
struct LLMModel {
+ std::size_t hash{ 0 };
std::string name;
std::string provider;
std::optional displayName;
diff --git a/src/tools/ecode/statusbuildoutputcontroller.cpp b/src/tools/ecode/statusbuildoutputcontroller.cpp
index 827bed229..aa0eefb4c 100644
--- a/src/tools/ecode/statusbuildoutputcontroller.cpp
+++ b/src/tools/ecode/statusbuildoutputcontroller.cpp
@@ -217,16 +217,20 @@ void StatusBuildOutputController::runBuild( const std::string& buildName,
}
if ( enableBuildButton && buildButton )
- buildButton->setEnabled( true );
+ buildButton->ensureMainThread( [buildButton] { buildButton->setEnabled( true ); } );
if ( enableCleanButton && cleanButton )
- cleanButton->runOnMainThread( [cleanButton] { cleanButton->setEnabled( true ); } );
+ cleanButton->ensureMainThread( [cleanButton] { cleanButton->setEnabled( true ); } );
- if ( buildAndRunButton )
- buildAndRunButton->setEnabled( true );
+ if ( buildAndRunButton ) {
+ buildAndRunButton->ensureMainThread(
+ [buildAndRunButton] { buildAndRunButton->setEnabled( true ); } );
+ }
- mBuildButton->setEnabled( true );
- mStopButton->setEnabled( false );
+ mBuildButton->ensureMainThread( [this] {
+ mBuildButton->setEnabled( true );
+ mStopButton->setEnabled( false );
+ } );
};
auto res = pbm->build(
diff --git a/src/tools/ecode/uitreeviewfs.cpp b/src/tools/ecode/uitreeviewfs.cpp
index 15733e5a2..1cc1653b0 100644
--- a/src/tools/ecode/uitreeviewfs.cpp
+++ b/src/tools/ecode/uitreeviewfs.cpp
@@ -148,6 +148,8 @@ UITreeViewFS::UITreeViewFS() : UITreeView(), mKeyBindings( getInput() ) {
mCommands["paste"] = [this] {
if ( mWasCut )
moveFile( mSrcCopy, getSelectionPath() );
+ else
+ copyFile( mSrcCopy, getSelectionPath() );
};
mKeyBindings.addKeybinds( getDefaultKeybindings() );
diff --git a/src/tools/ecode/universallocator.cpp b/src/tools/ecode/universallocator.cpp
index 4f76dfb99..e53be18e5 100644
--- a/src/tools/ecode/universallocator.cpp
+++ b/src/tools/ecode/universallocator.cpp
@@ -317,7 +317,8 @@ UniversalLocator::UniversalLocator( UICodeEditorSplitter* editorSplitter, UIScen
mApp->getSettingsMenu()->updateCurrentFileType();
mLocateBarLayout->execute( "close-locatebar" );
}
- } } );
+ },
+ nullptr, false } );
mApp->getPluginManager()->subscribeMessages(
"universallocator", [this]( const PluginMessage& msg ) -> PluginRequestHandle {