├── .gitignore ├── CMakeLists.txt ├── DropDownInput ├── ChoosedTagItem.qml ├── ChoosedTagItemsView.qml ├── DropDownInput.qml ├── FilteredTags.cpp ├── FilteredTags.h ├── StandardRectangleLayout.qml ├── TagEditor.qml ├── TagItem.qml ├── TagItemData.h ├── TagItemInput.qml ├── TagItemsModel.cpp ├── TagItemsModel.h └── TagItemsView.qml ├── LICENSE ├── README.md ├── main.cpp ├── main.qml └── qml.qrc /.gitignore: -------------------------------------------------------------------------------- 1 | *.user 2 | 3 | # This file is used to ignore files which are generated 4 | # ---------------------------------------------------------------------------- 5 | 6 | *~ 7 | *.autosave 8 | *.a 9 | *.core 10 | *.moc 11 | *.o 12 | *.obj 13 | *.orig 14 | *.rej 15 | *.so 16 | *.so.* 17 | *_pch.h.cpp 18 | *_resource.rc 19 | *.qm 20 | .#* 21 | *.*# 22 | core 23 | !core/ 24 | tags 25 | .DS_Store 26 | .directory 27 | *.debug 28 | Makefile* 29 | *.prl 30 | *.app 31 | moc_*.cpp 32 | ui_*.h 33 | qrc_*.cpp 34 | Thumbs.db 35 | *.res 36 | *.rc 37 | /.qmake.cache 38 | /.qmake.stash 39 | 40 | # qtcreator generated files 41 | *.pro.user* 42 | 43 | # xemacs temporary files 44 | *.flc 45 | 46 | # Vim temporary files 47 | .*.swp 48 | 49 | # Visual Studio generated files 50 | *.ib_pdb_index 51 | *.idb 52 | *.ilk 53 | *.pdb 54 | *.sln 55 | *.suo 56 | *.vcproj 57 | *vcproj.*.*.user 58 | *.ncb 59 | *.sdf 60 | *.opensdf 61 | *.vcxproj 62 | *vcxproj.* 63 | 64 | # MinGW generated files 65 | *.Debug 66 | *.Release 67 | 68 | # Python byte code 69 | *.pyc 70 | 71 | # Binaries 72 | # -------- 73 | *.dll 74 | *.exe 75 | 76 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(DropDownInputQML VERSION 0.1 LANGUAGES CXX) 4 | 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | 7 | set(CMAKE_AUTOUIC ON) 8 | set(CMAKE_AUTOMOC ON) 9 | set(CMAKE_AUTORCC ON) 10 | 11 | set(CMAKE_CXX_STANDARD 11) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | 14 | find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Quick REQUIRED) 15 | find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Quick REQUIRED) 16 | 17 | set(PROJECT_SOURCES 18 | main.cpp 19 | qml.qrc 20 | DropDownInput/TagItemData.h 21 | DropDownInput/TagItemsModel.h 22 | DropDownInput/TagItemsModel.cpp 23 | DropDownInput/FilteredTags.h 24 | DropDownInput/FilteredTags.cpp 25 | ) 26 | 27 | if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) 28 | qt_add_executable(DropDownInputQML 29 | MANUAL_FINALIZATION 30 | ${PROJECT_SOURCES} 31 | ) 32 | # Define target properties for Android with Qt 6 as: 33 | # set_property(TARGET DropDownInputQML APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR 34 | # ${CMAKE_CURRENT_SOURCE_DIR}/android) 35 | # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation 36 | else() 37 | if(ANDROID) 38 | add_library(DropDownInputQML SHARED 39 | ${PROJECT_SOURCES} 40 | ) 41 | # Define properties for Android with Qt 5 after find_package() calls as: 42 | # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") 43 | else() 44 | add_executable(DropDownInputQML 45 | ${PROJECT_SOURCES} 46 | ) 47 | endif() 48 | endif() 49 | 50 | target_compile_definitions(DropDownInputQML 51 | PRIVATE $<$,$>:QT_QML_DEBUG>) 52 | target_link_libraries(DropDownInputQML 53 | PRIVATE Qt${QT_VERSION_MAJOR}::Core 54 | Qt${QT_VERSION_MAJOR}::Quick 55 | ) 56 | 57 | set_target_properties(DropDownInputQML PROPERTIES 58 | MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com 59 | MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} 60 | MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} 61 | MACOSX_BUNDLE TRUE 62 | WIN32_EXECUTABLE TRUE 63 | ) 64 | 65 | if(QT_VERSION_MAJOR EQUAL 6) 66 | qt_import_qml_plugins(DropDownInputQML) 67 | qt_finalize_executable(DropDownInputQML) 68 | endif() 69 | -------------------------------------------------------------------------------- /DropDownInput/ChoosedTagItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | import QtQuick.Layouts 1.3 3 | Rectangle { 4 | id:root 5 | signal deleteRequested() 6 | property string tagItemName: "NaN" 7 | color:"#3d4951" 8 | radius:5 9 | width:childrenRect.width 10 | height:childrenRect.height 11 | RowLayout{ 12 | spacing: 1 13 | StandardRectangleLayout{ 14 | Layout.alignment: Qt.AlignLeft 15 | Text { 16 | id: name 17 | color:"#99bdb3" 18 | anchors.centerIn: parent 19 | text: root.tagItemName 20 | } 21 | } 22 | Rectangle{ 23 | color:"transparent"; 24 | MouseArea{ 25 | anchors.fill: closeButton 26 | hoverEnabled: true 27 | onEntered: closeButton.color = "#9cc1db" 28 | onExited: closeButton.color = "transparent"; 29 | cursorShape: Qt.PointingHandCursor 30 | onClicked: { 31 | root.deleteRequested(); 32 | } 33 | } 34 | id:closeButton 35 | radius:4 36 | Layout.alignment: Qt.AlignRight 37 | Layout.minimumWidth: closeText.width + 5 38 | Layout.minimumHeight: closeText.height + 5 39 | Text { 40 | id: closeText 41 | text: qsTr("✖") 42 | anchors.centerIn: parent 43 | font.pointSize: 17; 44 | 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /DropDownInput/ChoosedTagItemsView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | Flow{ 3 | id:totalChoosed 4 | spacing:3 5 | padding: 4 6 | property var tagChoosed:[]; 7 | signal deleteRequested(string nameDeleted) 8 | 9 | signal addedNewChoosedTag(string name) 10 | signal deletedChoosedTag(string name) 11 | 12 | Item 13 | { 14 | id:privateProps 15 | property ListModel tags: privTags; 16 | ListModel { 17 | id:privTags 18 | onCountChanged: { 19 | for(var i = totalChoosed.tagChoosed.length; i !== 0; --i) 20 | totalChoosed.tagChoosed.pop(); 21 | for(var j = 0; j < privateProps.tags.count; ++j) 22 | totalChoosed.tagChoosed.push(privateProps.tags.get(j).name); 23 | } 24 | } 25 | 26 | function find(model, criteria) { 27 | for(var i = 0; i < model.count; ++i) if (model.get(i).name === criteria) return i; 28 | return null; 29 | } 30 | } 31 | 32 | function push(itemText) 33 | { 34 | var found = privateProps.find(privateProps.tags, itemText); 35 | if(found === null){ 36 | privateProps.tags.append({'name': itemText }); 37 | totalChoosed.addedNewChoosedTag(itemText); 38 | } 39 | else 40 | console.log("Exist!"); 41 | } 42 | function pop(itemText) 43 | { 44 | var found = privateProps.find(privateProps.tags, itemText); 45 | if(found !== null){ 46 | privateProps.tags.remove(found); 47 | totalChoosed.deletedChoosedTag(itemText); 48 | } 49 | else 50 | console.log("Not exist!"); 51 | } 52 | 53 | Repeater{ 54 | id:tagRepeater 55 | model:privateProps.tags 56 | ChoosedTagItem{ 57 | tagItemName: name 58 | onDeleteRequested: { 59 | console.log(tagItemName); 60 | totalChoosed.deleteRequested(tagItemName); 61 | } 62 | } 63 | } 64 | onDeleteRequested:(nameDeleted)=> { 65 | totalChoosed.pop(nameDeleted) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /DropDownInput/DropDownInput.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.3 3 | import ir.hcoding.models 1.0 4 | Item { 5 | id:rootDropDown 6 | property string currentText: tagItemInput.currentText 7 | required property var originalModel; 8 | property bool showSuggestion: true; 9 | property string filterPattern : filteredTags.filterPattern 10 | property var tagChoosed: tagItemInput.tagChoosed; 11 | 12 | 13 | FilteredTags{ 14 | id:filteredTags 15 | filterPattern: rootDropDown.currentText 16 | originalModel: rootDropDown.originalModel 17 | } 18 | 19 | ColumnLayout{ 20 | anchors.fill: parent 21 | TagItemInput{ 22 | id:tagItemInput 23 | Layout.fillWidth: true; 24 | height:45; 25 | originModel:filteredTags.originalModel; 26 | } 27 | /// More option like some checkbox ... 28 | // and etc 29 | } 30 | 31 | 32 | TagItemsView{ 33 | visible:rootDropDown.showSuggestion & tagItemInput.activeSuggestions 34 | model: filteredTags; 35 | x:tagItemInput.x 36 | y:tagItemInput.height+tagItemInput.y 37 | width: tagItemInput.width 38 | height: 120 39 | onTagItemSelected: (name)=> { 40 | tagItemInput.addToEditor(name); 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /DropDownInput/FilteredTags.cpp: -------------------------------------------------------------------------------- 1 | #include "FilteredTags.h" 2 | #include "TagItemsModel.h" 3 | #include 4 | #include 5 | #include 6 | 7 | FilteredTags::FilteredTags(QObject* parent) 8 | : QSortFilterProxyModel(parent) 9 | { 10 | // setSortCaseSensitivity(Qt::CaseInsensitive); 11 | // setFilterKeyColumn(0); 12 | setFilterRole(TagItemsModel::TagItemDataRole::Name); 13 | setDynamicSortFilter(true); 14 | connect(this, &FilteredTags::originalModelChanged, this, [this]() { 15 | setSourceModel(originalModel()); 16 | }); 17 | connect(this, &FilteredTags::filterPatternsChanged, this, &FilteredTags::setNewFilter); 18 | } 19 | 20 | TagItemsModel* FilteredTags::originalModel() const 21 | { 22 | return _originalModel; 23 | } 24 | 25 | void FilteredTags::setOriginalModel(TagItemsModel* newOriginalModel) 26 | { 27 | if(_originalModel == newOriginalModel) 28 | return; 29 | _originalModel = newOriginalModel; 30 | _originalModel->generateHeader(); 31 | emit originalModelChanged(); 32 | } 33 | 34 | const QString& FilteredTags::filterPattern() const 35 | { 36 | return _patternString; 37 | } 38 | 39 | void FilteredTags::setFilterPattern(const QString& newFilterPatterns) 40 | { 41 | if(_patternString == newFilterPatterns) 42 | return; 43 | _patternString = newFilterPatterns; 44 | emit filterPatternsChanged(); 45 | } 46 | 47 | void FilteredTags::setNewFilter() 48 | { 49 | QRegExp::PatternSyntax syntax = QRegExp::PatternSyntax::RegExp; //TODO options 50 | Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive; //TODO options! 51 | QRegExp regExp(filterPattern(), caseSensitivity, syntax); 52 | setFilterRegExp(regExp); 53 | } 54 | -------------------------------------------------------------------------------- /DropDownInput/FilteredTags.h: -------------------------------------------------------------------------------- 1 | #ifndef FILTEREDTAGS_H 2 | #define FILTEREDTAGS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | class TagItemsModel; 9 | class FilteredTags : public QSortFilterProxyModel 10 | { 11 | Q_OBJECT 12 | Q_PROPERTY(TagItemsModel* originalModel READ originalModel WRITE setOriginalModel NOTIFY 13 | originalModelChanged) 14 | Q_PROPERTY(QString filterPattern READ filterPattern WRITE setFilterPattern NOTIFY 15 | filterPatternsChanged) 16 | 17 | public: 18 | explicit FilteredTags(QObject* parent = nullptr); 19 | 20 | public: 21 | TagItemsModel* originalModel() const; 22 | void setOriginalModel(TagItemsModel* newOriginalModel); 23 | 24 | const QString& filterPattern() const; 25 | void setFilterPattern(const QString& newFilterPatterns); 26 | 27 | static inline void RegisterToQML() 28 | { 29 | qmlRegisterType("ir.hcoding.models", 1, 0, "QSortFilterProxyModel"); 30 | qmlRegisterType("ir.hcoding.models", 1, 0, "FilteredTags"); 31 | } 32 | signals: 33 | 34 | void originalModelChanged(); 35 | 36 | void filterPatternsChanged(); 37 | 38 | private: 39 | void setNewFilter(); 40 | 41 | private: 42 | QString _patternString; 43 | TagItemsModel* _originalModel; 44 | }; 45 | 46 | #endif // FILTEREDTAGS_H 47 | -------------------------------------------------------------------------------- /DropDownInput/StandardRectangleLayout.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.3 3 | Rectangle { 4 | color:"transparent" 5 | Layout.minimumWidth: childrenRect.width + 5 6 | Layout.minimumHeight: childrenRect.height + 5 7 | } 8 | -------------------------------------------------------------------------------- /DropDownInput/TagEditor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 2.5 3 | TextField{ 4 | id:editor 5 | maximumLength: 20 6 | //TODO implement navigating between choosed tags 7 | signal leftKey() 8 | signal rightKey() 9 | signal leftDelete() 10 | signal rightDelete() 11 | color: "#d7fce1" 12 | font.pointSize: 14; 13 | placeholderText:"Insert a tag" 14 | background: Rectangle{ 15 | color:"transparent"; 16 | border.color: "transparent" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DropDownInput/TagItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.3 3 | 4 | Rectangle { 5 | id:rootTagItem 6 | implicitWidth: 200 7 | implicitHeight:100 8 | clip:true 9 | signal clicked() 10 | 11 | property string nameText: "None"; 12 | property string descriptionText: "None"; 13 | property string linkText: "None"; 14 | 15 | onDescriptionTextChanged: { 16 | description.text = descriptionText.split(0,40)+" ..."; 17 | } 18 | 19 | color:"gray" 20 | radius: 1 21 | MouseArea{ 22 | id: linkAreaName 23 | anchors.fill: parent 24 | cursorShape: Qt.PointingHandCursor 25 | onClicked: { 26 | rootTagItem.clicked(); 27 | } 28 | } 29 | ColumnLayout{ 30 | id:colLayout 31 | anchors.fill: rootTagItem 32 | spacing: 2 33 | StandardRectangleLayout{ 34 | Layout.alignment: Qt.AlignCenter 35 | color:"#707070" 36 | // Layout.fillWidth: true 37 | width:rootTagItem.width 38 | Text { 39 | id: name 40 | font.bold: true 41 | color:"white" 42 | font.pointSize: 14; 43 | anchors.centerIn: parent 44 | text: rootTagItem.nameText 45 | } 46 | 47 | } 48 | 49 | StandardRectangleLayout{ 50 | // Layout.fillWidth: true 51 | Layout.fillHeight: true 52 | Text { 53 | id: description 54 | color:"white" 55 | wrapMode: Text.Wrap 56 | width:rootTagItem.width 57 | font.pointSize: 10; 58 | } 59 | } 60 | StandardRectangleLayout{ 61 | // Layout.fillWidth: true 62 | width:rootTagItem.width 63 | Text { 64 | id: hyperLink 65 | text: rootTagItem.linkText 66 | linkColor: "#DBB2FF" 67 | onLinkActivated: Qt.openUrlExternally(link) 68 | } 69 | } 70 | } 71 | 72 | 73 | property bool stateVisible: rootTagItem.visible 74 | states: [ 75 | State { when: rootTagItem.stateVisible; 76 | PropertyChanges { target: rootTagItem; opacity: 1.0 } 77 | }, 78 | State { when: !rootTagItem.stateVisible; 79 | PropertyChanges { target: rootTagItem; opacity: 0.0 } 80 | } 81 | ] 82 | transitions: Transition { 83 | NumberAnimation { property: "opacity"; duration: 400} 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /DropDownInput/TagItemData.h: -------------------------------------------------------------------------------- 1 | #ifndef TAGITEM_H 2 | #define TAGITEM_H 3 | #include 4 | #include 5 | #include 6 | struct TagItemData 7 | { 8 | Q_GADGET 9 | // register members 10 | Q_PROPERTY(QString name MEMBER m_name) 11 | Q_PROPERTY(QString description MEMBER m_description) 12 | Q_PROPERTY(QString link MEMBER m_link) 13 | public: 14 | bool operator==(const QString& name) const 15 | { 16 | return m_name == name; 17 | } 18 | bool operator==(const TagItemData& tagItem) const 19 | { 20 | return m_name == tagItem.m_name; 21 | } 22 | QString m_name; 23 | QString m_description; 24 | QString m_link; 25 | }; 26 | Q_DECLARE_METATYPE(TagItemData) 27 | #endif // TAGITEM_H 28 | -------------------------------------------------------------------------------- /DropDownInput/TagItemInput.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | import QtGraphicalEffects 1.15 3 | import QtQuick.Layouts 1.3 4 | import QtQuick.Controls 2.5 5 | import ir.hcoding.models 1.0 6 | import QtQuick.Dialogs 1.3 7 | ScrollView { 8 | id:rootRectInput 9 | height: 50 10 | clip:true 11 | contentItem.clip: true 12 | implicitHeight: height 13 | ScrollBar.horizontal.policy: ScrollBar.AsNeeded 14 | ScrollBar.vertical.policy: ScrollBar.AsNeeded 15 | property bool multiLineTags: true // ture-> tags will be in multiple line (width fixed) , false-> tags will be in one line (height fixed) 16 | property var originModel:; 17 | property string currentText: editor.text 18 | property var tagChoosed: choosedView.tagChoosed; 19 | property bool activeSuggestions: editor.focus; 20 | background: Rectangle{ 21 | radius:10 22 | color: "#2d2d2d" 23 | } 24 | layer.enabled: editor.focus 25 | layer.effect: Glow { 26 | radius: 14 27 | samples: 30 28 | color: "#4986b3" 29 | source: rootRectInput 30 | } 31 | function addToEditor(tagName){ 32 | editor.text = tagName; 33 | } 34 | MouseArea{ 35 | anchors.fill: parent 36 | onClicked: { 37 | rootRectInput.focus=true; 38 | } 39 | } 40 | 41 | ChoosedTagItemsView { 42 | id:choosedView 43 | width: rootRectInput.multiLineTags ? rootRectInput.width : rootRectInput.childrenRect.width * 1.5; 44 | TagEditor{ 45 | MessageDialog { 46 | id: messageDialog 47 | title: "Error" 48 | text: "The tag was not exist in tags!" 49 | onAccepted: { 50 | messageDialog.close(); 51 | } 52 | } 53 | id:editor 54 | onTextChanged: text= text.replace(/\s+/g,'') 55 | function addTextToTags(){ 56 | if(editor.text.length == 0) 57 | return; 58 | if(originModel.contains(editor.text)) 59 | { 60 | choosedView.push(editor.text) 61 | editor.text = ""; 62 | } 63 | else{ 64 | messageDialog.open(); 65 | } 66 | } 67 | 68 | onAccepted: addTextToTags(); 69 | focus: rootRectInput.focus; 70 | } 71 | } 72 | activeFocusOnTab: true 73 | } 74 | -------------------------------------------------------------------------------- /DropDownInput/TagItemsModel.cpp: -------------------------------------------------------------------------------- 1 | #include "TagItemsModel.h" 2 | 3 | #include 4 | 5 | TagItemsModel::TagItemsModel(QObject* parent) 6 | : QAbstractListModel(parent) 7 | { 8 | _roles = QHash{{TagItemsModel::TagItemDataRole::Name, "Name"}, 9 | {TagItemsModel::TagItemDataRole::Description, "Description"}, 10 | {TagItemsModel::TagItemDataRole::Link, "Link"}}; 11 | } 12 | 13 | int TagItemsModel::rowCount(const QModelIndex& parent) const 14 | { 15 | return _tagItems.size(); 16 | } 17 | 18 | int TagItemsModel::columnCount(const QModelIndex& parent) const 19 | { 20 | return 3; 21 | } 22 | 23 | QVariant TagItemsModel::data(const QModelIndex& index, int role) const 24 | { 25 | auto const row = index.row(); 26 | if(row > _tagItems.size()) 27 | return createTagItem().m_name; 28 | auto& res = _tagItems[row]; 29 | switch(role) 30 | { 31 | case TagItemsModel::Name: 32 | return res.m_name; 33 | case TagItemsModel::Description: 34 | return res.m_description; 35 | case TagItemsModel::Link: 36 | return res.m_link; 37 | default: 38 | return ""; 39 | } 40 | } 41 | 42 | QVariant TagItemsModel::headerData(int section, Qt::Orientation orientation, int role) const 43 | { 44 | return _roles[role]; 45 | } 46 | 47 | QHash TagItemsModel::roleNames() const 48 | { 49 | return _roles; 50 | } 51 | 52 | bool TagItemsModel::addTagItem(const TagItemData& data) 53 | { 54 | if(_tagItems.contains(data)) 55 | { 56 | return false; 57 | } 58 | // tell QT what you will be doing 59 | beginInsertRows(QModelIndex(), _tagItems.size(), _tagItems.size()); 60 | _tagItems.append(data); 61 | // tell QT you are done 62 | endInsertRows(); 63 | return true; 64 | } 65 | 66 | void TagItemsModel::generateHeader() 67 | { 68 | 69 | for(int i = TagItemsModel::TagItemDataRole::Name; i <= TagItemsModel::TagItemDataRole::Link; 70 | ++i) 71 | { 72 | setHeaderData(0, Qt::Horizontal, _roles[i], i); 73 | } 74 | } 75 | 76 | bool TagItemsModel::contains(const QString& name) 77 | { 78 | for(auto const& item : qAsConst(_tagItems)) 79 | { 80 | if(item == name) 81 | { 82 | return true; 83 | } 84 | } 85 | return false; 86 | } 87 | -------------------------------------------------------------------------------- /DropDownInput/TagItemsModel.h: -------------------------------------------------------------------------------- 1 | #ifndef TAGITEMSMODEL_H 2 | #define TAGITEMSMODEL_H 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "TagItemData.h" 9 | class TagItemsModel : public QAbstractListModel 10 | { 11 | Q_OBJECT 12 | public: 13 | TagItemsModel(QObject* parent = nullptr); 14 | 15 | enum TagItemDataRole 16 | { 17 | Name = Qt::UserRole + 1, 18 | Description, 19 | Link 20 | }; 21 | Q_ENUM(TagItemDataRole) 22 | public: 23 | ///////////////////@@ QAbstractListModel interface @@/////////////////// 24 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; 25 | int columnCount(const QModelIndex& parent = QModelIndex()) const override; 26 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; 27 | QVariant 28 | headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; 29 | 30 | QHash roleNames() const override; 31 | /////////////////////////////////////////////////////////////////// 32 | 33 | public: 34 | Q_INVOKABLE bool addTagItem(const TagItemData& data); 35 | Q_INVOKABLE void generateHeader(); 36 | Q_INVOKABLE bool contains(const QString& name); 37 | Q_INVOKABLE TagItemData getTagItemData(const QString& name) const 38 | { 39 | for(auto const& item : _tagItems) 40 | { 41 | if(item == name) 42 | { 43 | return item; 44 | } 45 | } 46 | return createTagItem(); 47 | } 48 | 49 | public: 50 | Q_INVOKABLE static inline TagItemData createTagItem() 51 | { 52 | TagItemData result; 53 | result.m_name = "None"; 54 | result.m_description = "description"; 55 | result.m_link = "link"; 56 | return result; 57 | } 58 | 59 | static inline void RegisterToQML() 60 | { 61 | qRegisterMetaType("TagItemData"); 62 | qmlRegisterType("ir.hcoding.models", 1, 0, "TagItemsModel"); 63 | } 64 | 65 | private: 66 | QHash _roles; 67 | QVector _tagItems; 68 | }; 69 | 70 | #endif // TAGITEMSMODEL_H 71 | -------------------------------------------------------------------------------- /DropDownInput/TagItemsView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.9 2 | import QtQuick.Controls 2.5 3 | import QtQuick.Layouts 1.3 4 | 5 | ScrollView{ 6 | id:root 7 | clip:true 8 | required property var model; 9 | signal tagItemSelected(string name); 10 | ScrollBar.vertical.policy: ScrollBar.AsNeeded 11 | background: Rectangle { 12 | id:backContent 13 | color:"#4f4f4f" 14 | radius: 8 15 | border.color: "gray" 16 | border.width: 1 17 | property bool stateVisible: backContent.visible 18 | states: [ 19 | State { when: backContent.stateVisible; 20 | PropertyChanges { target: backContent; opacity: 1.0 } 21 | }, 22 | State { when: !backContent.stateVisible; 23 | PropertyChanges { target: backContent; opacity: 0.0 } 24 | } 25 | ] 26 | transitions: Transition { 27 | NumberAnimation { property: "opacity"; duration: 270} 28 | } 29 | } 30 | 31 | Flickable{ 32 | id:content 33 | contentHeight: flow.height 34 | width: parent.width 35 | // height: 800 36 | // anchors.fill: parent 37 | Flow{ 38 | id:flow 39 | width:parent.width 40 | padding: 8 41 | spacing:10 42 | function mar(){ 43 | var rowCount = parent.width / (flow.children[0].width + flow.spacing); 44 | if(rowCount> flow.children.length){ 45 | rowCount = flow.children.length 46 | } 47 | rowCount = parseInt(rowCount) 48 | var rowWidth = rowCount * flow.children[0].width + (rowCount - 1) * flow.spacing 49 | print(flow.height) 50 | return (parent.width - rowWidth) / 2 51 | 52 | } 53 | leftPadding: mar() 54 | rightPadding: mar() 55 | Repeater 56 | { 57 | id:repeater 58 | model: root.model 59 | TagItem{ 60 | nameText: Name; 61 | descriptionText: Description; 62 | linkText: Link; 63 | onClicked: { 64 | root.tagItemSelected(nameText); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Heydar Mahmoodi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DropDownInputQML 2 | Drop Down Input QML , same as tag list in stackoverflow. 3 | 4 | Output at [03 Mar 2022]: 5 | ![Drop down Input text in qml](https://s6.uupload.ir/files/drop_down_input_text_in_qml_ta9.gif) 6 | 7 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "DropDownInput/FilteredTags.h" 5 | #include "DropDownInput/TagItemsModel.h" 6 | int main(int argc, char* argv[]) 7 | { 8 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 9 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 10 | #endif 11 | QGuiApplication app(argc, argv); 12 | TagItemsModel::RegisterToQML(); // register 13 | FilteredTags::RegisterToQML(); // register 14 | QQmlApplicationEngine engine; 15 | const QUrl url(QStringLiteral("qrc:/main.qml")); 16 | QObject::connect( 17 | &engine, 18 | &QQmlApplicationEngine::objectCreated, 19 | &app, 20 | [url](QObject* obj, const QUrl& objUrl) { 21 | if(!obj && url == objUrl) 22 | QCoreApplication::exit(-1); 23 | }, 24 | Qt::QueuedConnection); 25 | engine.load(url); 26 | 27 | return app.exec(); 28 | } 29 | -------------------------------------------------------------------------------- /main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | import QtQuick.Window 2.15 3 | import QtQuick.Controls 2.3 4 | import QtQuick.Layouts 1.3 5 | import "DropDownInput/" as Comps 6 | import ir.hcoding.models 1.0 7 | Window { 8 | width: 700 9 | height: 500 10 | visible: true 11 | title: qsTr("DropDown Test") 12 | Item{ 13 | TagItemsModel { 14 | // Example (original & init original data): 15 | id: tagListModel; 16 | function makeid(length) { 17 | var result = ''; 18 | var characters = 'abcdefg1234567890'; 19 | var charactersLength = characters.length; 20 | for ( var i = 0; i < length; i++ ) { 21 | result += characters.charAt(Math.floor(Math.random() * 22 | charactersLength)); 23 | } 24 | return result; 25 | } 26 | 27 | Component.onCompleted: { 28 | var x = tagListModel.createTagItem(); 29 | for(var i = 0; i<25;++i) 30 | { 31 | x.name = tagListModel.makeid(12); 32 | x.description = "Description: This is simple text to Show QML power.This is simple text to Show QML power."; 33 | 34 | x.link = 'HCoding'; 35 | tagListModel.addTagItem(x); 36 | } 37 | } 38 | } 39 | } 40 | Comps.DropDownInput{ 41 | width: parent.width * 0.7 42 | height: parent.height * 0.7 43 | anchors.centerIn:parent 44 | originalModel: tagListModel; 45 | } 46 | 47 | Button{text:"Exit";onClicked:{Qt.quit()}} 48 | } 49 | -------------------------------------------------------------------------------- /qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | DropDownInput/TagItem.qml 5 | DropDownInput/TagItemsView.qml 6 | DropDownInput/StandardRectangleLayout.qml 7 | DropDownInput/TagItemInput.qml 8 | DropDownInput/ChoosedTagItem.qml 9 | DropDownInput/ChoosedTagItemsView.qml 10 | DropDownInput/DropDownInput.qml 11 | DropDownInput/TagEditor.qml 12 | 13 | 14 | --------------------------------------------------------------------------------