diff --git a/THIRD_PARTY_COPYRIGHT.txt b/THIRD_PARTY_COPYRIGHT.txt index 75557839..c657a604 100644 --- a/THIRD_PARTY_COPYRIGHT.txt +++ b/THIRD_PARTY_COPYRIGHT.txt @@ -34,6 +34,38 @@ providing powerful tools and support for this project. For more information about the Qt project, please visit the official Qt website (https://www.qt.io/). + +************************************************************************************ +QHotkey + +Copyright (c) 2016, Felix Barz +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of QHotkey nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ************************************************************************************ framelesshelper diff --git a/example/example.qrc b/example/example.qrc index f69b7c55..643cb5bd 100644 --- a/example/example.qrc +++ b/example/example.qrc @@ -209,6 +209,7 @@ qml/window/FluentInitializrWindow.qml qml/page/T_OpenGL.qml qml/page/T_Icons.qml + qml/window/HotkeyWindow.qml diff --git a/example/example_en_US.ts b/example/example_en_US.ts index ceabe270..109d1894 100644 --- a/example/example_en_US.ts +++ b/example/example_en_US.ts @@ -1,6 +1,54 @@ + + App + + + Quit + + + + + Test1 + + + + + Test2 + + + + + Test3 + + + + + Test4 + + + + + Test5 + + + + + Test6 + + + + + Test7 + + + + + Test8 + + + CodeExpander @@ -71,6 +119,14 @@ + + HotkeyWindow + + + Hotkey + + + HotloadWindow @@ -1273,25 +1329,31 @@ My only desire is to be permitted to drive out the traitors and restore the Han. - + E-mail - + Calendar - + Contacts RadioButton Group + RadioButton Group111111111111111111111111 + + + + + Disabled @@ -1331,12 +1393,7 @@ My only desire is to be permitted to drive out the traitors and restore the Han. - - Search - - - - + You Copied @@ -1362,51 +1419,82 @@ My only desire is to be permitted to drive out the traitors and restore the Han. - + Info - - + + This is an InfoBar in the Info Style - + Warning - + This is an InfoBar in the Warning Style - + This is an InfoBar in the Error Style - + This is an InfoBar in the Success Style - + InfoBar that needs to be turned off manually - + Manual shutdown is supported + + + Manually close the info message box + + + + + + + close '%1' + + + + + + + show '%1 + + + + + + + This is an '%1' + + + + + clear all info + + T_LineChart - + Line Chart @@ -1704,22 +1792,30 @@ My only desire is to be permitted to drive out the traitors and restore the Han. - + + + Disabled - + + + Radio Button_1 + + Radio Button_2 - + + + Radio Button_3 @@ -1947,88 +2043,98 @@ Some contents... - + Modify the column name - + Cancel - + OK - + Search - - + + Name - + Delete - + Edit - + Select All - + Age - + Clear All - + + Delete Selection + + + + + Add a row of Data + + + + Insert a Row - + Focus not acquired: Please click any item in the form as the target for insertion! - + Avatar - + Address - + Nickname - + Long String - + Options @@ -2042,16 +2148,6 @@ Some contents... Next> - - - Delete Selection - - - - - Add a row of Data - - T_Text @@ -2203,12 +2299,12 @@ Some contents... - + Append - + clear diff --git a/example/example_zh_CN.ts b/example/example_zh_CN.ts index c86300be..3776583c 100644 --- a/example/example_zh_CN.ts +++ b/example/example_zh_CN.ts @@ -1,6 +1,54 @@ + + App + + + Quit + 退出 + + + + Test1 + 测试1 + + + + Test2 + 测试2 + + + + Test3 + 测试3 + + + + Test4 + 测试4 + + + + Test5 + 测试5 + + + + Test6 + 测试6 + + + + Test7 + 测试7 + + + + Test8 + 测试8 + + CodeExpander @@ -71,6 +119,14 @@ 创建 + + HotkeyWindow + + + Hotkey + + + HotloadWindow @@ -1359,27 +1415,33 @@ My only desire is to be permitted to drive out the traitors and restore the Han. - + E-mail 邮箱 - + Calendar 日历 - + Contacts 联系人 RadioButton Group + RadioButton Group111111111111111111111111 单选框分组 + + + Disabled + 禁用 + T_Home @@ -1417,12 +1479,11 @@ My only desire is to be permitted to drive out the traitors and restore the Han. 请输入关键字 - Search - 搜索 + 搜索 - + You Copied 您复制 @@ -1452,46 +1513,77 @@ My only desire is to be permitted to drive out the traitors and restore the Han. 信息栏 - + Info - - + + This is an InfoBar in the Info Style 这是一个Info样式的信息栏 - + Warning - + This is an InfoBar in the Warning Style 这是一个Warning样式的信息栏 - + This is an InfoBar in the Error Style 这是一个Error样式的信息栏 - + This is an InfoBar in the Success Style 这是一个Success样式的信息栏 - + InfoBar that needs to be turned off manually 需要手动关闭的信息栏 - + Manual shutdown is supported 支持手动关闭 + + + Manually close the info message box + + + + + + + close '%1' + + + + + + + show '%1 + + + + + + + This is an '%1' + + + + + clear all info + + Loading... 加载中... @@ -1500,7 +1592,7 @@ My only desire is to be permitted to drive out the traitors and restore the Han. T_LineChart - + Line Chart 线型图 @@ -1808,22 +1900,30 @@ My only desire is to be permitted to drive out the traitors and restore the Han. - + + + Disabled 禁用 - + + + Radio Button_1 + + Radio Button_2 - + + + Radio Button_3 @@ -1999,6 +2099,10 @@ Some contents... ShortcutPicker 快捷键选择器 + + Quit + 退出 + Activate the Shortcut 激活快捷键 @@ -2089,88 +2193,88 @@ Some contents... 表格 - + Modify the column name 修改列名 - + Cancel 取消 - + OK 确定 - + Search 搜索 - - + + Name 名称 - + Delete 删除 - + Edit 编辑 - + Select All 全选 - + Age 年龄 - + Clear All 清除所有 - + Insert a Row 插入一行 - + Focus not acquired: Please click any item in the form as the target for insertion! 焦点未获取:请点击表格中的任意一项,作为插入的靶点! - + Avatar 头像 - + Address 地址 - + Nickname 昵称 - + Long String 长字符串 - + Options 操作 @@ -2185,12 +2289,12 @@ Some contents... 下一页> - + Delete Selection 删除选中 - + Add a row of Data 添加一行数据 @@ -2345,12 +2449,12 @@ Some contents... 时间轴 - + Append 追加 - + clear 清空 diff --git a/example/qml/App.qml b/example/qml/App.qml index 6d33206d..2f4e8cda 100644 --- a/example/qml/App.qml +++ b/example/qml/App.qml @@ -43,7 +43,8 @@ FluLauncher { "/singleTaskWindow":"qrc:/example/qml/window/SingleTaskWindow.qml", "/standardWindow":"qrc:/example/qml/window/StandardWindow.qml", "/singleInstanceWindow":"qrc:/example/qml/window/SingleInstanceWindow.qml", - "/pageWindow":"qrc:/example/qml/window/PageWindow.qml" + "/pageWindow":"qrc:/example/qml/window/PageWindow.qml", + "/hotkey":"qrc:/example/qml/window/HotkeyWindow.qml" } var args = Qt.application.arguments if(args.length>=2 && args[1].startsWith("-crashed=")){ @@ -52,4 +53,73 @@ FluLauncher { FluRouter.navigate("/") } } + + property alias hotkeys: object_hotkey + FluObject{ + id: object_hotkey + FluHotkey{ + name: qsTr("Quit") + sequence: "Ctrl+Alt+Q" + onActivated: { + FluRouter.exit() + } + } + FluHotkey{ + name: qsTr("Test1") + sequence: "Alt+A" + onActivated: { + FluRouter.navigate("/hotkey",{sequence:sequence}) + } + } + FluHotkey{ + name: qsTr("Test2") + sequence: "Alt+B" + onActivated: { + FluRouter.navigate("/hotkey",{sequence:sequence}) + } + } + FluHotkey{ + name: qsTr("Test3") + sequence: "Alt+C" + onActivated: { + FluRouter.navigate("/hotkey",{sequence:sequence}) + } + } + FluHotkey{ + name: qsTr("Test4") + sequence: "Alt+D" + onActivated: { + FluRouter.navigate("/hotkey",{sequence:sequence}) + } + } + FluHotkey{ + name: qsTr("Test5") + sequence: "Alt+E" + onActivated: { + FluRouter.navigate("/hotkey",{sequence:sequence}) + } + } + FluHotkey{ + name: qsTr("Test6") + sequence: "Alt+F" + onActivated: { + FluRouter.navigate("/hotkey",{sequence:sequence}) + } + } + FluHotkey{ + name: qsTr("Test7") + sequence: "Alt+G" + onActivated: { + FluRouter.navigate("/hotkey",{sequence:sequence}) + } + } + FluHotkey{ + name: qsTr("Test8") + sequence: "Alt+H" + onActivated: { + FluRouter.navigate("/hotkey",{sequence:sequence}) + } + } + } + } diff --git a/example/qml/chart/T_LineChart.qml b/example/qml/chart/T_LineChart.qml index 88e92b3e..45837660 100644 --- a/example/qml/chart/T_LineChart.qml +++ b/example/qml/chart/T_LineChart.qml @@ -7,7 +7,9 @@ import "../component" FluScrollablePage{ + id: root title: qsTr("Line Chart") + property var data : [] FluFrame{ Layout.preferredWidth: 500 @@ -15,13 +17,14 @@ FluScrollablePage{ padding: 10 Layout.topMargin: 20 FluChart{ + id: chart anchors.fill: parent chartType: 'line' chartData: { return { labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'], datasets: [{ label: 'My First Dataset', - data: [65, 59, 80, 81, 56, 55, 40], + data: root.data, fill: false, borderColor: 'rgb(75, 192, 192)', tension: 0.1 @@ -41,5 +44,20 @@ FluScrollablePage{ } } } + Timer{ + id: timer + interval: 300 + repeat: true + onTriggered: { + root.data.push(Math.random()*100) + if(root.data.length>7){ + root.data.shift() + } + chart.animateToNewData() + } + } + Component.onCompleted: { + timer.restart() + } } } diff --git a/example/qml/page/T_Icons.qml b/example/qml/page/T_Icons.qml index 8cf6a584..cb078ce5 100644 --- a/example/qml/page/T_Icons.qml +++ b/example/qml/page/T_Icons.qml @@ -14,17 +14,8 @@ FluContentPage { anchors{ top: parent.top } - } - - FluFilledButton{ - text: qsTr("Search") - anchors{ - left: text_box.right - verticalCenter: text_box.verticalCenter - leftMargin: 14 - } - onClicked: { - grid_view.model = FluApp.iconDatas(text_box.text) + onTextChanged: { + grid_view.model = FluApp.iconData(text_box.text) } } GridView{ @@ -33,7 +24,7 @@ FluContentPage { cellHeight: 110 clip: true boundsBehavior: GridView.StopAtBounds - model: FluApp.iconDatas() + model: FluApp.iconData() ScrollBar.vertical: FluScrollBar {} anchors{ topMargin: 10 diff --git a/example/qml/page/T_RadioButton.qml b/example/qml/page/T_RadioButton.qml index 2f08e329..a8ab4dcb 100644 --- a/example/qml/page/T_RadioButton.qml +++ b/example/qml/page/T_RadioButton.qml @@ -53,21 +53,20 @@ FluScrollablePage{ Layout.topMargin: 20 FluRadioButtons{ spacing: 8 + disabled: radio_button_switch2.checked anchors.verticalCenter: parent.verticalCenter anchors{ verticalCenter: parent.verticalCenter left: parent.left } + currentIndex: 1 FluRadioButton{ - disabled: radio_button_switch2.checked text: qsTr("Radio Button_1") } FluRadioButton{ - disabled: radio_button_switch2.checked text: qsTr("Radio Button_2") } FluRadioButton{ - disabled: radio_button_switch2.checked text: qsTr("Radio Button_3") } } @@ -97,4 +96,106 @@ FluScrollablePage{ }' } + FluFrame{ + Layout.fillWidth: true + Layout.preferredHeight: 60 + padding: 10 + Layout.topMargin: 20 + FluRadioButtons{ + spacing: 8 + anchors.verticalCenter: parent.verticalCenter + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + disabled: radio_button_switch3.checked + orientation: Qt.Horizontal + currentIndex: 1 + FluRadioButton{ + text: qsTr("Radio Button_1") + } + FluRadioButton{ + text: qsTr("Radio Button_2") + } + FluRadioButton{ + text: qsTr("Radio Button_3") + } + } + FluToggleSwitch{ + id: radio_button_switch3 + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text: qsTr("Disabled") + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -6 + code:'FluRadioButtons{ + spacing: 8 + orientation: Qt.Horizontal + FluRadioButton{ + text:"Radio Button_1" + } + FluRadioButton{ + text:"Radio Button_2" + } + FluRadioButton{ + text:"Radio Button_3" + } +}' + } + + FluFrame{ + Layout.fillWidth: true + Layout.preferredHeight: 100 + padding: 10 + Layout.topMargin: 20 + FluRadioButtons{ + spacing: 8 + anchors.verticalCenter: parent.verticalCenter + anchors{ + verticalCenter: parent.verticalCenter + left: parent.left + } + disabled: radio_button_switch4.checked + currentIndex: -1 + FluCheckBox{ + text: qsTr("Radio Button_1") + } + FluCheckBox{ + text: qsTr("Radio Button_2") + } + FluCheckBox{ + text: qsTr("Radio Button_3") + } + } + FluToggleSwitch{ + id: radio_button_switch4 + anchors{ + right: parent.right + verticalCenter: parent.verticalCenter + } + text: qsTr("Disabled") + } + } + CodeExpander{ + Layout.fillWidth: true + Layout.topMargin: -6 + code:'FluRadioButtons{ + spacing: 8 + FluCheckBox{ + text:"Radio Button_1" + } + FluCheckBox{ + text:"Radio Button_2" + } + FluCheckBox{ + text:"Radio Button_3" + } +}' + } + } diff --git a/example/qml/page/T_ShortcutPicker.qml b/example/qml/page/T_ShortcutPicker.qml index d82edeb3..fce03284 100644 --- a/example/qml/page/T_ShortcutPicker.qml +++ b/example/qml/page/T_ShortcutPicker.qml @@ -11,12 +11,26 @@ FluScrollablePage{ FluFrame{ Layout.fillWidth: true - Layout.preferredHeight: 100 - padding: 10 - FluShortcutPicker{ + Layout.preferredHeight: childrenRect.height + ColumnLayout{ anchors.verticalCenter: parent.verticalCenter + Item{ + Layout.preferredHeight: 15 + } + Repeater{ + model: FluApp.launcher.hotkeys.children + delegate: FluShortcutPicker{ + text: model.name + syncHotkey: FluApp.launcher.hotkeys.children[index] + Layout.leftMargin: 15 + } + } + Item{ + Layout.preferredHeight: 15 + } } } + CodeExpander{ Layout.fillWidth: true Layout.topMargin: -6 @@ -26,5 +40,3 @@ FluScrollablePage{ } } - - diff --git a/example/qml/page/T_TableView.qml b/example/qml/page/T_TableView.qml index b309c369..dc4ae88a 100644 --- a/example/qml/page/T_TableView.qml +++ b/example/qml/page/T_TableView.qml @@ -11,7 +11,6 @@ FluContentPage{ title: qsTr("TableView") signal checkBoxChanged - property var dataSource : [] property int sortType: 0 property bool selectedAll: true property string nameKeyword: "" @@ -244,7 +243,9 @@ FluContentPage{ clickListener: function(){ root.selectedAll = !root.selectedAll var checked = root.selectedAll - itemModel.display = table_view.customItem(com_column_checbox,{"checked":checked}) + var columnModel = model.display + columnModel.title = table_view.customItem(com_column_checbox,{"checked":checked}) + model.display = columnModel for(var i =0;i< table_view.rows ;i++){ var rowData = table_view.getRow(i) rowData.checkbox = table_view.customItem(com_checbox,{"checked":checked}) @@ -271,7 +272,8 @@ FluContentPage{ } Component.onCompleted: { currentIndex=["100","300","500","1000"].findIndex((element) => element === display) - selectAll() + textBox.forceActiveFocus() + textBox.selectAll() } onCommit: { editTextChaged(editText) @@ -293,6 +295,8 @@ FluContentPage{ }); items = result textbox.text= String(display) + forceActiveFocus() + selectAll() } onCommit: { editTextChaged(textbox.text) @@ -344,7 +348,9 @@ FluContentPage{ cursorShape: Qt.PointingHandCursor onClicked: { custom_update_dialog.showDialog(options.title,function(text){ - itemModel.display = table_view.customItem(com_column_update_title,{"title":text}) + var columnModel = model.display + columnModel.title = table_view.customItem(com_column_update_title,{"title":text}) + model.display = columnModel }) } } @@ -442,15 +448,15 @@ FluContentPage{ var data = [] var rows = [] for (var i = 0; i < table_view.rows; i++) { - var item = table_view.getRow(i); + var item = table_view.getRow(i) rows.push(item) if (!item.checkbox.options.checked) { data.push(item); } } - var sourceModel = table_view.sourceModel; + var sourceModel = table_view.sourceModel for (i = 0; i < sourceModel.rowCount; i++) { - var sourceItem = sourceModel.getRow(i); + var sourceItem = sourceModel.getRow(i) const foundItem = rows.find(item=> item._key === sourceItem._key) if (!foundItem) { data.push(sourceItem); @@ -459,7 +465,6 @@ FluContentPage{ table_view.dataSource = data } } - FluButton{ text: qsTr("Add a row of Data") onClicked: { @@ -469,18 +474,15 @@ FluContentPage{ FluButton{ text: qsTr("Insert a Row") onClicked: { - if(typeof table_view.current !== 'undefined'){ - var newLine = genTestObject() - var currentLine = dataSource.findIndex(obj => obj._key === table_view.current._key) - root.dataSource.splice(currentLine, 0, newLine); - table_view.dataSource = root.dataSource + var index = table_view.currentIndex() + if(index !== -1){ + var testObj = genTestObject() + table_view.insertRow(index,testObj) }else{ showWarning(qsTr("Focus not acquired: Please click any item in the form as the target for insertion!")) } - } } - } } @@ -500,20 +502,19 @@ FluContentPage{ { title: table_view.customItem(com_column_checbox,{checked:true}), dataIndex: 'checkbox', - width:100, - minimumWidth:100, - maximumWidth:100 - }, - { - title: table_view.customItem(com_column_update_title,{title:qsTr("Avatar")}), - dataIndex: 'avatar', - width:100 + frozen: true }, { title: table_view.customItem(com_column_filter_name,{title:qsTr("Name")}), dataIndex: 'name', readOnly:true }, + { + title: table_view.customItem(com_column_update_title,{title:qsTr("Avatar")}), + dataIndex: 'avatar', + width:100, + frozen:true + }, { title: table_view.customItem(com_column_sort_age,{sort:0}), dataIndex: 'age', @@ -549,8 +550,7 @@ FluContentPage{ title: qsTr("Options"), dataIndex: 'action', width:160, - minimumWidth:160, - maximumWidth:160 + frozen:true } ] } @@ -620,7 +620,6 @@ FluContentPage{ for(var i=0;i { diff --git a/example/qml/window/HotkeyWindow.qml b/example/qml/window/HotkeyWindow.qml new file mode 100644 index 00000000..8c31d266 --- /dev/null +++ b/example/qml/window/HotkeyWindow.qml @@ -0,0 +1,26 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import FluentUI 1.0 +import "../component" + +FluWindow { + + id: window + property string sequence: "" + title: qsTr("Hotkey") + width: 250 + height: 250 + fixSize: true + launchMode: FluWindowType.SingleInstance + onInitArgument: + (argument)=>{ + window.sequence = argument.sequence + } + FluText{ + anchors.centerIn: parent + color: FluTheme.primaryColor + font: FluTextStyle.Title + text: window.sequence + } +} diff --git a/example/qml/window/MainWindow.qml b/example/qml/window/MainWindow.qml index 8b92bedc..9e2fa681 100644 --- a/example/qml/window/MainWindow.qml +++ b/example/qml/window/MainWindow.qml @@ -14,9 +14,9 @@ FluWindow { id:window title: "FluentUI" width: 1000 - height: 680 - minimumWidth: 680 - minimumHeight: 200 + height: 668 + minimumWidth: 668 + minimumHeight: 320 launchMode: FluWindowType.SingleTask fitsAppBarWindows: true appBar: FluAppBar { diff --git a/example/src/component/OpenGLItem.cpp b/example/src/component/OpenGLItem.cpp index 58e86d78..4e0f687a 100644 --- a/example/src/component/OpenGLItem.cpp +++ b/example/src/component/OpenGLItem.cpp @@ -2,6 +2,7 @@ #include #include +#include class FBORenderer : public QQuickFramebufferObject::Renderer, protected QOpenGLFunctions { public: @@ -47,6 +48,7 @@ QOpenGLFramebufferObject *FBORenderer::createFramebufferObject(const QSize &size } void FBORenderer::render() { + auto pixelRatio = item->window()->devicePixelRatio(); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glEnable(GL_DEPTH_TEST); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -61,7 +63,7 @@ void FBORenderer::render() { glBindBuffer(GL_ARRAY_BUFFER, 0); program.setAttributeArray(0, GL_FLOAT, values, 2); program.setUniformValue("t", (float) item->t()); - glViewport(0, 0, qRound(item->width()), qRound(item->height())); + glViewport(0, 0, qRound(item->width()*pixelRatio), qRound(item->height()*pixelRatio)); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 70040b5e..591a0eb9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -11,6 +11,13 @@ list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/.cmake/) #配置通用编译 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +if (QT_VERSION VERSION_GREATER_EQUAL "6.3") + qt_standard_project_setup() +else () + set(CMAKE_AUTOMOC ON) + set(CMAKE_AUTORCC ON) + set(CMAKE_INCLUDE_CURRENT_DIR ON) +endif () #设置版本号 add_definitions(-DFLUENTUI_VERSION=1,7,5,0) @@ -35,14 +42,6 @@ if (NOT FLUENTUI_QML_PLUGIN_DIRECTORY) set(FLUENTUI_QML_PLUGIN_DIRECTORY ${QT_SDK_DIR}/qml/FluentUI) endif () -if (QT_VERSION VERSION_GREATER_EQUAL "6.3") - qt_standard_project_setup() -else () - set(CMAKE_AUTOMOC ON) - set(CMAKE_AUTORCC ON) - set(CMAKE_INCLUDE_CURRENT_DIR ON) -endif () - #国际化 find_program(QT_LUPDATE NAMES lupdate) find_program(QT_LRELEASE NAMES lrelease) @@ -62,9 +61,28 @@ file(COPY ${QM_FILE_PATHS} DESTINATION "${CMAKE_CURRENT_SOURCE_DIR}/Qt${QT_VERSI file(GLOB_RECURSE CPP_FILES *.cpp *.h *.cxx) foreach (filepath ${CPP_FILES}) string(REPLACE "${CMAKE_CURRENT_SOURCE_DIR}/" "" filename ${filepath}) + message(${filename}) list(APPEND sources_files ${filename}) endforeach (filepath) +list(REMOVE_ITEM sources_files qhotkey/qhotkey_mac.cpp qhotkey/qhotkey_win.cpp qhotkey/qhotkey_x11.cpp) + +if (WIN32) + list(APPEND sources_files qhotkey/qhotkey_win.cpp) +elseif (APPLE) + list(APPEND sources_files qhotkey/qhotkey_mac.cpp) +elseif (UNIX) + list(APPEND sources_files qhotkey/qhotkey_x11.cpp) +endif() + +if (WIN32) + set(FLUENTUI_VERSION_RC_PATH ${CMAKE_CURRENT_BINARY_DIR}/version_${PROJECT_NAME}.rc) + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/.cmake/version_dll.rc.in + ${FLUENTUI_VERSION_RC_PATH} + ) +endif () + if (QT_VERSION VERSION_GREATER_EQUAL "6.2") #删除fluentuiplugin.cpp与fluentuiplugin.h,这些只要Qt5使用,Qt6不需要 list(REMOVE_ITEM sources_files fluentuiplugin.h fluentuiplugin.cpp) @@ -179,6 +197,34 @@ target_link_libraries(${PROJECT_NAME} PUBLIC Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Qml ) +if(APPLE) + find_library(CARBON_LIBRARY Carbon) + target_link_libraries(${PROJECT_NAME} PRIVATE ${CARBON_LIBRARY}) +elseif(WIN32) + target_link_libraries(${PROJECT_NAME} PRIVATE user32) +elseif(UNIX) + if(QT_VERSION_MAJOR STREQUAL "6") + if(QT_VERSION VERSION_LESS "6.2.0") + message(FATAL_ERROR "Qt 6.2.0 or greater is required when using Qt6") + endif() + else() + if(QT_VERSION_MAJOR LESS "6") + find_package(Qt5 REQUIRED COMPONENTS X11Extras) + target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::X11Extras) + endif() + endif() + target_link_libraries(${PROJECT_NAME} PRIVATE X11) +endif() +if ((${QT_VERSION_MAJOR} LESS_EQUAL 6) AND (CMAKE_BUILD_TYPE MATCHES "Release")) + find_program(QML_PLUGIN_DUMP NAMES qmlplugindump) + add_custom_target(Script-Generate-QmlTypes + COMMAND ${QML_PLUGIN_DUMP} -nonrelocatable FluentUI 1.0 ${CMAKE_CURRENT_BINARY_DIR} > ${CMAKE_CURRENT_SOURCE_DIR}/Qt5/imports/FluentUI/plugins.qmltypes + COMMENT "Generate qmltypes........." + SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/Qt5/imports/FluentUI/plugins.qmltypes + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) +endif() + #安装 install(DIRECTORY ${FLUENTUI_QML_PLUGIN_DIRECTORY} DESTINATION ${CMAKE_INSTALL_PREFIX}/imports) diff --git a/src/FluApp.cpp b/src/FluApp.cpp index 36bfd3fa..da3ab8d6 100644 --- a/src/FluApp.cpp +++ b/src/FluApp.cpp @@ -1,6 +1,5 @@ #include "FluApp.h" -#include #include #include #include @@ -17,9 +16,10 @@ FluApp::FluApp(QObject *parent) : QObject{parent} { FluApp::~FluApp() = default; -void FluApp::init(QObject *target, QLocale locale) { +void FluApp::init(QObject *launcher, QLocale locale) { + this->launcher(launcher); _locale = std::move(locale); - _engine = qmlEngine(target); + _engine = qmlEngine(launcher); _translator = new QTranslator(this); QGuiApplication::installTranslator(_translator); const QStringList uiLanguages = _locale.uiLanguages(); @@ -32,9 +32,9 @@ void FluApp::init(QObject *target, QLocale locale) { } } -[[maybe_unused]] QJsonArray FluApp::iconDatas(const QString &keyword) { +[[maybe_unused]] QJsonArray FluApp::iconData(const QString &keyword) { QJsonArray arr; - QMetaEnum enumType = Fluent_Icons::staticMetaObject.enumerator(Fluent_Icons::staticMetaObject.indexOfEnumerator("Fluent_IconType")); + QMetaEnum enumType = FluentIcons::staticMetaObject.enumerator(FluentIcons::staticMetaObject.indexOfEnumerator("Type")); for (int i = 0; i <= enumType.keyCount() - 1; ++i) { QString name = enumType.key(i); int icon = enumType.value(i); diff --git a/src/FluApp.h b/src/FluApp.h index 89934208..f3d47ab7 100644 --- a/src/FluApp.h +++ b/src/FluApp.h @@ -21,6 +21,7 @@ Q_OBJECT Q_PROPERTY_AUTO(bool, useSystemAppBar) Q_PROPERTY_AUTO(QString, windowIcon) Q_PROPERTY_AUTO(QLocale, locale) +Q_PROPERTY_AUTO_P(QObject*,launcher) QML_NAMED_ELEMENT(FluApp) QML_SINGLETON @@ -34,9 +35,9 @@ SINGLETON(FluApp) static FluApp *create(QQmlEngine *, QJSEngine *) { return getInstance(); } - Q_INVOKABLE void init(QObject *target, QLocale locale = QLocale::system()); + Q_INVOKABLE void init(QObject *launcher, QLocale locale = QLocale::system()); - [[maybe_unused]] Q_INVOKABLE static QJsonArray iconDatas(const QString &keyword = ""); + [[maybe_unused]] Q_INVOKABLE static QJsonArray iconData(const QString &keyword = ""); private: QQmlEngine *_engine{}; diff --git a/src/FluFrameless.cpp b/src/FluFrameless.cpp index 6f92e2c7..90ccf947 100644 --- a/src/FluFrameless.cpp +++ b/src/FluFrameless.cpp @@ -7,6 +7,7 @@ #include "FluTools.h" #ifdef Q_OS_WIN + #pragma comment (lib, "user32.lib") #pragma comment (lib, "dwmapi.lib") @@ -14,7 +15,6 @@ #include #include - static inline QByteArray qtNativeEventType() { static const auto result = "windows_generic_MSG"; return result; @@ -35,15 +35,28 @@ static inline bool isCompositionEnabled() { return false; } +static inline void setShadow(HWND hwnd) { + const MARGINS shadow = {1, 0, 0, 0}; + typedef HRESULT (WINAPI *DwmExtendFrameIntoClientAreaPtr)(HWND hWnd, const MARGINS *pMarInset); + HMODULE module = LoadLibraryW(L"dwmapi.dll"); + if (module) { + DwmExtendFrameIntoClientAreaPtr dwm_extendframe_into_client_area_; + dwm_extendframe_into_client_area_ = reinterpret_cast(GetProcAddress(module, "DwmExtendFrameIntoClientArea")); + if (dwm_extendframe_into_client_area_) { + dwm_extendframe_into_client_area_(hwnd, &shadow); + } + } +} + #endif bool containsCursorToItem(QQuickItem *item) { if (!item || !item->isVisible()) { return false; } - auto point = QCursor::pos(); - auto rect = QRectF(item->mapToGlobal(QPoint(0, 0)), item->size()); - if (point.x() > rect.x() && point.x() < (rect.x() + rect.width()) && point.y() > rect.y() && point.y() < (rect.y() + rect.height())) { + auto point = item->window()->mapFromGlobal(QCursor::pos()); + auto rect = QRectF(item->mapToItem(item->window()->contentItem(), QPointF(0, 0)), item->size()); + if (rect.contains(point)) { return true; } return false; @@ -73,7 +86,7 @@ void FluFrameless::componentComplete() { int w = window()->width(); int h = window()->height(); _current = window()->winId(); - window()->setFlags((window()->flags()) | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint); + window()->setFlags((window()->flags()) | Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::FramelessWindowHint); if (!_fixSize) { window()->setFlag(Qt::WindowMaximizeButtonHint); } @@ -92,25 +105,40 @@ void FluFrameless::componentComplete() { HWND hwnd = reinterpret_cast(window()->winId()); DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE); if (_fixSize) { - ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_CAPTION); +#if (QT_VERSION == QT_VERSION_CHECK(6, 5, 3) || QT_VERSION == QT_VERSION_CHECK(6, 6, 0)) + ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME);; +#else + ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_THICKFRAME | WS_CAPTION); +#endif for (int i = 0; i <= QGuiApplication::screens().count() - 1; ++i) { connect(QGuiApplication::screens().at(i), &QScreen::logicalDotsPerInchChanged, this, [=] { SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_FRAMECHANGED); }); } } else { - ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_CAPTION); +#if (QT_VERSION == QT_VERSION_CHECK(6, 5, 3) || QT_VERSION == QT_VERSION_CHECK(6, 6, 0)) + ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME); +#else + ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION); +#endif } SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); connect(window(), &QQuickWindow::screenChanged, this, [hwnd] { ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOOWNERZORDER); ::RedrawWindow(hwnd, nullptr, nullptr, RDW_INVALIDATE | RDW_UPDATENOW); }); + if (!window()->property("_hideShadow").toBool()) { + setShadow(hwnd); + } #endif - h = qRound(h + _appbar->height()); + auto appBarHeight = _appbar->height(); + h = qRound(h + appBarHeight); if (_fixSize) { window()->setMaximumSize(QSize(w, h)); window()->setMinimumSize(QSize(w, h)); + } else { + window()->setMinimumHeight(window()->minimumHeight() + appBarHeight); + window()->setMaximumHeight(window()->maximumHeight() + appBarHeight); } window()->resize(QSize(w, h)); connect(this, &FluFrameless::topmostChanged, this, [this] { @@ -144,24 +172,39 @@ void FluFrameless::componentComplete() { return true; } return false; - } else if (uMsg == WM_NCCALCSIZE) { - const auto clientRect = ((wParam == FALSE) ? reinterpret_cast(lParam) : &(reinterpret_cast(lParam))->rgrc[0]); - bool isMaximum = ::IsZoomed(hwnd); - if (!isMaximum){ - if (clientRect->top != 0) - { - clientRect->top -= 1; - clientRect->bottom -= 1; - } - } else{ - const LONG originalTop = clientRect->top; - const LRESULT hitTestResult = ::DefWindowProcW(hwnd, WM_NCCALCSIZE, wParam, lParam); - if ((hitTestResult != HTERROR) && (hitTestResult != HTNOWHERE)) { - *result = static_cast(hitTestResult); - return true; - } - clientRect->top = originalTop-originalTop; + } else if (uMsg == WM_NCCALCSIZE && wParam == TRUE) { + const auto clientRect = &(reinterpret_cast(lParam))->rgrc[0]; + const LONG originalTop = clientRect->top; + const LONG originalLeft = clientRect->left; + const LONG originalBottom = clientRect->bottom; + const LONG originalRight = clientRect->right; + const LRESULT hitTestResult = ::DefWindowProcW(hwnd, WM_NCCALCSIZE, wParam, lParam); + if ((hitTestResult != HTERROR) && (hitTestResult != HTNOWHERE)) { + *result = static_cast(hitTestResult); + return true; } +#if (QT_VERSION == QT_VERSION_CHECK(6, 5, 3) || QT_VERSION == QT_VERSION_CHECK(6, 6, 0)) + clientRect->top = originalTop; + clientRect->bottom = originalBottom; + clientRect->left = originalLeft; + clientRect->right = originalRight; +#else + bool isMaximum = ::IsZoomed(hwnd); + if (isMaximum) { + auto geometry = window()->screen()->geometry(); + auto offsetX = qAbs(geometry.left() - originalLeft); + auto offsetY = qAbs(geometry.top() - originalTop); + clientRect->top = originalTop + offsetY; + clientRect->bottom = originalBottom - offsetY; + clientRect->left = originalLeft + offsetX; + clientRect->right = originalRight - offsetX; + } else { + clientRect->top = originalTop; + clientRect->bottom = originalBottom; + clientRect->left = originalLeft; + clientRect->right = originalRight; + } +#endif _setMaximizeHovered(false); *result = WVR_REDRAW; return true; @@ -219,17 +262,32 @@ void FluFrameless::componentComplete() { *result = HTCLIENT; return true; } else if (uMsg == WM_NCPAINT) { - if(isCompositionEnabled()){ +#if (QT_VERSION == QT_VERSION_CHECK(6, 5, 3) || QT_VERSION == QT_VERSION_CHECK(6, 6, 0)) + *result = FALSE; + return true; +#else + if (isCompositionEnabled()) { return false; } *result = FALSE; return true; +#endif } else if (uMsg == WM_NCACTIVATE) { - if(isCompositionEnabled()){ - return false; - } *result = TRUE; return true; + } else if (uMsg == WM_GETMINMAXINFO) { +#if (QT_VERSION == QT_VERSION_CHECK(6, 5, 3) || QT_VERSION == QT_VERSION_CHECK(6, 6, 0)) + auto *minmaxInfo = reinterpret_cast(lParam); + auto pixelRatio = window()->devicePixelRatio(); + auto geometry = window()->screen()->availableGeometry(); + RECT rect; + SystemParametersInfo(SPI_GETWORKAREA, 0, &rect, 0); + minmaxInfo->ptMaxPosition.x = rect.left; + minmaxInfo->ptMaxPosition.y = rect.top; + minmaxInfo->ptMaxSize.x = qRound(geometry.width() * pixelRatio); + minmaxInfo->ptMaxSize.y = qRound(geometry.height() * pixelRatio); +#endif + return false; } else if (_isWindows11OrGreater && (uMsg == WM_NCLBUTTONDBLCLK || uMsg == WM_NCLBUTTONDOWN)) { if (_hitMaximizeButton()) { QMouseEvent event = QMouseEvent(QEvent::MouseButtonPress, QPoint(), QPoint(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); @@ -244,11 +302,11 @@ void FluFrameless::componentComplete() { _setMaximizePressed(false); return true; } - } else if (uMsg == WM_ERASEBKGND) { - return true; } else if (uMsg == WM_NCRBUTTONDOWN) { if (wParam == HTCAPTION) { - _showSystemMenu(QCursor::pos()); + auto pos = window()->position(); + auto offset = window()->mapFromGlobal(QCursor::pos()); + _showSystemMenu(QPoint(pos.x() + offset.x(), pos.y() + offset.y())); } } else if (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN) { const bool altPressed = ((wParam == VK_MENU) || (::GetKeyState(VK_MENU) < 0)); @@ -286,9 +344,16 @@ bool FluFrameless::_isFullScreen() { void FluFrameless::_showSystemMenu(QPoint point) { #ifdef Q_OS_WIN + QScreen *screen = window()->screen(); + if (!screen) { + screen = QGuiApplication::primaryScreen(); + } + if (!screen) { + return; + } + const QPoint origin = screen->geometry().topLeft(); + auto nativePos = QPointF(QPointF(point - origin) * window()->devicePixelRatio()).toPoint() + origin; HWND hwnd = reinterpret_cast(window()->winId()); - DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE); - ::SetWindowLongPtr(hwnd, GWL_STYLE, style | WS_SYSMENU); auto hMenu = ::GetSystemMenu(hwnd, FALSE); if (_isMaximized() || _isFullScreen()) { ::EnableMenuItem(hMenu, SC_MOVE, MFS_DISABLED); @@ -304,12 +369,11 @@ void FluFrameless::_showSystemMenu(QPoint point) { ::EnableMenuItem(hMenu, SC_SIZE, MFS_DISABLED); ::EnableMenuItem(hMenu, SC_MAXIMIZE, MFS_DISABLED); } - const int result = ::TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), qRound(point.x() * window()->devicePixelRatio()), - qRound(point.y() * window()->devicePixelRatio()), 0, hwnd, nullptr); - if (result != FALSE) { + const int result = ::TrackPopupMenu(hMenu, (TPM_RETURNCMD | (QGuiApplication::isRightToLeft() ? TPM_RIGHTALIGN : TPM_LEFTALIGN)), nativePos.x(), + nativePos.y(), 0, hwnd, nullptr); + if (result) { ::PostMessageW(hwnd, WM_SYSCOMMAND, result, 0); } - ::SetWindowLongPtr(hwnd, GWL_STYLE, style & ~WS_SYSMENU); #endif } @@ -334,36 +398,40 @@ bool FluFrameless::_hitMaximizeButton() { } void FluFrameless::_setMaximizePressed(bool val) { - _maximizeButton->setProperty("down", val); + if (_maximizeButton) { + _maximizeButton->setProperty("down", val); + } } void FluFrameless::_setMaximizeHovered(bool val) { - _maximizeButton->setProperty("hover", val); + if (_maximizeButton) { + _maximizeButton->setProperty("hover", val); + } } void FluFrameless::_updateCursor(int edges) { switch (edges) { - case 0: - window()->setCursor(Qt::ArrowCursor); - break; - case Qt::LeftEdge: - case Qt::RightEdge: - window()->setCursor(Qt::SizeHorCursor); - break; - case Qt::TopEdge: - case Qt::BottomEdge: - window()->setCursor(Qt::SizeVerCursor); - break; - case Qt::LeftEdge | Qt::TopEdge: - case Qt::RightEdge | Qt::BottomEdge: - window()->setCursor(Qt::SizeFDiagCursor); - break; - case Qt::RightEdge | Qt::TopEdge: - case Qt::LeftEdge | Qt::BottomEdge: - window()->setCursor(Qt::SizeBDiagCursor); - break; - default: - break; + case 0: + window()->setCursor(Qt::ArrowCursor); + break; + case Qt::LeftEdge: + case Qt::RightEdge: + window()->setCursor(Qt::SizeHorCursor); + break; + case Qt::TopEdge: + case Qt::BottomEdge: + window()->setCursor(Qt::SizeVerCursor); + break; + case Qt::LeftEdge | Qt::TopEdge: + case Qt::RightEdge | Qt::BottomEdge: + window()->setCursor(Qt::SizeFDiagCursor); + break; + case Qt::RightEdge | Qt::TopEdge: + case Qt::LeftEdge | Qt::BottomEdge: + window()->setCursor(Qt::SizeBDiagCursor); + break; + default: + break; } } diff --git a/src/FluHotkey.cpp b/src/FluHotkey.cpp new file mode 100644 index 00000000..9744b573 --- /dev/null +++ b/src/FluHotkey.cpp @@ -0,0 +1,32 @@ +#include "FluHotkey.h" + + +#include "QGuiApplication" + +FluHotkey::FluHotkey(QObject *parent) + : QObject{parent} +{ + _sequence = ""; + _isRegistered = false; + connect(this,&FluHotkey::sequenceChanged,this,[=]{ + if(_hotkey){ + delete _hotkey; + _hotkey = nullptr; + } + _hotkey = new QHotkey(QKeySequence(_sequence), true, qApp); + this->isRegistered(_hotkey->isRegistered()); + QObject::connect(_hotkey, &QHotkey::activated, qApp, [=](){ + Q_EMIT this->activated(); + }); + QObject::connect(_hotkey, &QHotkey::registeredChanged, qApp, [=](){ + this->isRegistered(_hotkey->isRegistered()); + }); + }); +} + +FluHotkey::~FluHotkey(){ + if(_hotkey){ + delete _hotkey; + _hotkey = nullptr; + } +} diff --git a/src/FluHotkey.h b/src/FluHotkey.h new file mode 100644 index 00000000..56816abf --- /dev/null +++ b/src/FluHotkey.h @@ -0,0 +1,24 @@ +#ifndef FLUHOTKEY_H +#define FLUHOTKEY_H + +#include +#include +#include "qhotkey/qhotkey.h" +#include "stdafx.h" + +class FluHotkey : public QObject +{ + Q_OBJECT + Q_PROPERTY_AUTO(QString,sequence) + Q_PROPERTY_AUTO(QString,name) + Q_PROPERTY_READONLY_AUTO(bool,isRegistered) + QML_NAMED_ELEMENT(FluHotkey) +public: + explicit FluHotkey(QObject *parent = nullptr); + ~FluHotkey(); + Q_SIGNAL void activated(); +private: + QHotkey* _hotkey = nullptr; +}; + +#endif // FLUHOTKEY_H diff --git a/src/FluTableModel.cpp b/src/FluTableModel.cpp new file mode 100644 index 00000000..eb22de64 --- /dev/null +++ b/src/FluTableModel.cpp @@ -0,0 +1,63 @@ +#include "FluTableModel.h" + +FluTableModel::FluTableModel(QObject *parent) : QAbstractTableModel{parent} { + +} + +int FluTableModel::rowCount(const QModelIndex &parent) const { + return _rows.count(); +} + +int FluTableModel::columnCount(const QModelIndex &parent) const { + return this->_columnSource.size(); +} + +QVariant FluTableModel::data(const QModelIndex &index, int role) const { + switch (role) { + case FluTableModel::RowModel: + return QVariant::fromValue(_rows.at(index.row())); + case FluTableModel::ColumnModel: + return QVariant::fromValue(_columnSource.at(index.column())); + default: + break; + } + return {}; +} + +QHash FluTableModel::roleNames() const { + return { + {FluTableModel::RowModel, "rowModel"}, + {FluTableModel::ColumnModel, "columnModel"} + }; +} + +void FluTableModel::clear() { + beginResetModel(); + this->_rows.clear(); + endResetModel(); +} + +QVariant FluTableModel::getRow(int rowIndex) { + return _rows.at(rowIndex); +} + +void FluTableModel::setRow(int rowIndex, QVariant row) { + _rows.replace(rowIndex, row.toMap()); + Q_EMIT dataChanged(index(rowIndex, 0), index(rowIndex, columnCount() - 1)); +} + +void FluTableModel::insertRow(int rowIndex, QVariant row) { + beginInsertRows(QModelIndex(), rowIndex, rowIndex); + _rows.insert(rowIndex, row.toMap()); + endInsertRows(); +} + +void FluTableModel::removeRow(int rowIndex, int rows) { + beginRemoveRows(QModelIndex(), rowIndex, rowIndex + rows - 1); + _rows = _rows.mid(0, rowIndex) + _rows.mid(rowIndex + rows); + endRemoveRows(); +} + +void FluTableModel::appendRow(QVariant row) { + insertRow(rowCount(), row); +} diff --git a/src/FluTableModel.h b/src/FluTableModel.h new file mode 100644 index 00000000..a734aa80 --- /dev/null +++ b/src/FluTableModel.h @@ -0,0 +1,46 @@ +#ifndef FLUTABLEMODEL_H +#define FLUTABLEMODEL_H + +#include +#include +#include +#include "stdafx.h" + +class FluTableModel : public QAbstractTableModel { +Q_OBJECT +Q_PROPERTY_AUTO(QList, columnSource) +Q_PROPERTY_AUTO(QList, rows) + Q_PROPERTY(int rowCount READ rowCount CONSTANT) + QML_NAMED_ELEMENT(FluTableModel) +public: + enum TableModelRoles { + RowModel = 0x0101, + ColumnModel = 0x0102 + }; + + explicit FluTableModel(QObject *parent = nullptr); + + [[nodiscard]] int rowCount(const QModelIndex &parent = {}) const override; + + [[nodiscard]] int columnCount(const QModelIndex &parent = {}) const override; + + [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + [[nodiscard]] QHash roleNames() const override; + + Q_INVOKABLE void clear(); + + Q_INVOKABLE QVariant getRow(int rowIndex); + + Q_INVOKABLE void setRow(int rowIndex, QVariant row); + + Q_INVOKABLE void insertRow(int rowIndex, QVariant row); + + Q_INVOKABLE void removeRow(int rowIndex, int rows = 1); + + Q_INVOKABLE void appendRow(QVariant row); + +}; + + +#endif // FLUTABLEMODEL_H diff --git a/src/FluTableSortProxyModel.cpp b/src/FluTableSortProxyModel.cpp index 644f652a..796c8113 100644 --- a/src/FluTableSortProxyModel.cpp +++ b/src/FluTableSortProxyModel.cpp @@ -3,9 +3,8 @@ #include FluTableSortProxyModel::FluTableSortProxyModel(QSortFilterProxyModel *parent) : QSortFilterProxyModel{parent} { - _model = nullptr; connect(this, &FluTableSortProxyModel::modelChanged, this, [=] { - setSourceModel(this->model()); + setSourceModel(this->model().value()); }); } @@ -59,15 +58,18 @@ bool FluTableSortProxyModel::lessThan(const QModelIndex &source_left, const QMod [[maybe_unused]] QVariant FluTableSortProxyModel::getRow(int rowIndex) { QVariant result; - QMetaObject::invokeMethod(_model, "getRow", Q_RETURN_ARG(QVariant, result), Q_ARG(int, mapToSource(index(rowIndex, 0)).row())); + QMetaObject::invokeMethod(_model.value(), "getRow", Q_RETURN_ARG(QVariant, result), Q_ARG(int, mapToSource(index(rowIndex, 0)).row())); return result; } [[maybe_unused]] void FluTableSortProxyModel::setRow(int rowIndex, const QVariant &val) { - QMetaObject::invokeMethod(_model, "setRow", Q_ARG(int, mapToSource(index(rowIndex, 0)).row()), Q_ARG(QVariant, val)); + QMetaObject::invokeMethod(_model.value(), "setRow", Q_ARG(int, mapToSource(index(rowIndex, 0)).row()), Q_ARG(QVariant, val)); +} + +[[maybe_unused]] void FluTableSortProxyModel::insertRow(int rowIndex, const QVariant &val) { + QMetaObject::invokeMethod(_model.value(), "insertRow", Q_ARG(int, mapToSource(index(rowIndex, 0)).row()), Q_ARG(QVariant, val)); } [[maybe_unused]] void FluTableSortProxyModel::removeRow(int rowIndex, int rows) { - QMetaObject::invokeMethod(_model, "removeRow", Q_ARG(int, mapToSource(index(rowIndex, 0)).row()), Q_ARG(int, rows)); + QMetaObject::invokeMethod(_model.value(), "removeRow", Q_ARG(int, mapToSource(index(rowIndex, 0)).row()), Q_ARG(int, rows)); } - diff --git a/src/FluTableSortProxyModel.h b/src/FluTableSortProxyModel.h index 7a959f41..319dc118 100644 --- a/src/FluTableSortProxyModel.h +++ b/src/FluTableSortProxyModel.h @@ -8,7 +8,7 @@ class FluTableSortProxyModel : public QSortFilterProxyModel { Q_OBJECT -Q_PROPERTY_AUTO_P(QAbstractTableModel*, model) +Q_PROPERTY_AUTO_P(QVariant, model) QML_NAMED_ELEMENT(FluTableSortProxyModel) public: explicit FluTableSortProxyModel(QSortFilterProxyModel *parent = nullptr); @@ -23,6 +23,8 @@ public: [[maybe_unused]] Q_INVOKABLE void setRow(int rowIndex, const QVariant &val); + [[maybe_unused]] Q_INVOKABLE void insertRow(int rowIndex, const QVariant &val); + [[maybe_unused]] Q_INVOKABLE void removeRow(int rowIndex, int rows); [[maybe_unused]] Q_INVOKABLE void setComparator(const QJSValue &comparator); diff --git a/src/FluTools.h b/src/FluTools.h index 2ba82887..a044c5d5 100644 --- a/src/FluTools.h +++ b/src/FluTools.h @@ -95,5 +95,5 @@ SINGLETON(FluTools) Q_INVOKABLE QString getWallpaperFilePath(); - Q_INVOKABLE QColor imageMainColor(const QImage& image, double bright = 1); + Q_INVOKABLE QColor imageMainColor(const QImage &image, double bright = 1); }; diff --git a/src/FluTreeModel.cpp b/src/FluTreeModel.cpp index 020e8af8..f080a034 100644 --- a/src/FluTreeModel.cpp +++ b/src/FluTreeModel.cpp @@ -1,25 +1,14 @@ #include "FluTreeModel.h" #include -#include FluTreeNode::FluTreeNode(QObject *parent) : QObject{parent} { } -FluTreeModel::FluTreeModel(QObject *parent) : QAbstractItemModel{parent} { +FluTreeModel::FluTreeModel(QObject *parent) : QAbstractTableModel{parent} { _dataSourceSize = 0; } -QModelIndex FluTreeModel::parent(const QModelIndex &child) const { - return {}; -} - -QModelIndex FluTreeModel::index(int row, int column, const QModelIndex &parent) const { - if (!hasIndex(row, column, parent) || parent.isValid()) - return {}; - return createIndex(row, column, _rows.at(row)); -} - int FluTreeModel::rowCount(const QModelIndex &parent) const { return _rows.count(); } @@ -265,12 +254,12 @@ void FluTreeModel::allCollapse() { endResetModel(); } -QVariant FluTreeModel::selectionModel(){ +QVariant FluTreeModel::selectionModel() { QList data; - foreach (auto item, _dataSource) { - if (item->checked()) { - data.append(item); + foreach (auto item, _dataSource) { + if (item->checked()) { + data.append(item); + } } - } return QVariant::fromValue(data); } diff --git a/src/FluTreeModel.h b/src/FluTreeModel.h index d8eda4f5..e9d5f48b 100644 --- a/src/FluTreeModel.h +++ b/src/FluTreeModel.h @@ -50,7 +50,7 @@ public: } return true; }; - + Q_INVOKABLE bool hideLineFooter() { if (_parent) { auto childIndex = _parent->_children.indexOf(this); @@ -86,12 +86,11 @@ public: FluTreeNode *_parent = nullptr; }; -class FluTreeModel : public QAbstractItemModel { +class FluTreeModel : public QAbstractTableModel { Q_OBJECT Q_PROPERTY_AUTO(int, dataSourceSize) Q_PROPERTY_AUTO(QList, columnSource) QML_NAMED_ELEMENT(FluTreeModel) - QML_ADDED_IN_MINOR_VERSION(1) public: enum TreeModelRoles { RowModel = 0x0101, @@ -108,10 +107,6 @@ public: [[nodiscard]] QHash roleNames() const override; - [[nodiscard]] QModelIndex parent(const QModelIndex &child) const override; - - [[nodiscard]] QModelIndex index(int row, int column, const QModelIndex &parent = {}) const override; - Q_INVOKABLE void removeRows(int row, int count); Q_INVOKABLE void insertRows(int row, const QList &data); diff --git a/src/FluentIconDef.h b/src/FluentIconDef.h index 300d23bd..5fd61afd 100644 --- a/src/FluentIconDef.h +++ b/src/FluentIconDef.h @@ -3,9 +3,9 @@ #include #include -namespace Fluent_Icons { +namespace FluentIcons { Q_NAMESPACE - enum class Fluent_IconType { + enum class Type { GlobalNavButton = 0xe700, Wifi = 0xe701, Bluetooth = 0xe702, @@ -1411,7 +1411,7 @@ namespace Fluent_Icons { ClickedOutLoudSolidBold = 0xf8b3 }; - Q_ENUM_NS(Fluent_IconType) + Q_ENUM_NS(Type) QML_NAMED_ELEMENT(FluentIcons) } diff --git a/src/FluentUI.cpp b/src/FluentUI.cpp index a1704d39..28a97aca 100644 --- a/src/FluentUI.cpp +++ b/src/FluentUI.cpp @@ -15,6 +15,8 @@ #include "FluQrCodeItem.h" #include "FluTableSortProxyModel.h" #include "FluFrameless.h" +#include "FluTableModel.h" +#include "FluHotkey.h" void FluentUI::registerTypes(QQmlEngine *engine) { initializeEngine(engine, _uri); @@ -32,8 +34,10 @@ void FluentUI::registerTypes(const char *uri) const { qmlRegisterType(uri, major, minor, "FluWatermark"); qmlRegisterType(uri, major, minor, "FluAccentColor"); qmlRegisterType(uri, major, minor, "FluTreeModel"); + qmlRegisterType(uri, major, minor, "FluTableModel"); qmlRegisterType(uri, major, minor, "FluRectangle"); qmlRegisterType(uri, major, minor, "FluFrameless"); + qmlRegisterType(uri, major, minor, "FluHotkey"); qmlRegisterType(uri, major, minor, "FluTableSortProxyModel"); qmlRegisterType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluAcrylic.qml"), uri, major, minor, "FluAcrylic"); @@ -132,7 +136,7 @@ void FluentUI::registerTypes(const char *uri) const { qmlRegisterSingletonType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluRouter.qml"), uri, major, minor, "FluRouter"); qmlRegisterSingletonType(QUrl("qrc:/qt/qml/FluentUI/Controls/FluEventBus.qml"), uri, major, minor, "FluEventBus"); - qmlRegisterUncreatableMetaObject(Fluent_Icons::staticMetaObject, uri, major, minor, "FluentIcons", "Access to enums & flags only"); + qmlRegisterUncreatableMetaObject(FluentIcons::staticMetaObject, uri, major, minor, "FluentIcons", "Access to enums & flags only"); qmlRegisterUncreatableMetaObject(FluThemeType::staticMetaObject, uri, major, minor, "FluThemeType", "Access to enums & flags only"); qmlRegisterUncreatableMetaObject(FluPageType::staticMetaObject, uri, major, minor, "FluPageType", "Access to enums & flags only"); qmlRegisterUncreatableMetaObject(FluWindowType::staticMetaObject, uri, major, minor, "FluWindowType", "Access to enums & flags only"); @@ -146,14 +150,35 @@ void FluentUI::registerTypes(const char *uri) const { qmlRegisterUncreatableMetaObject(FluTimelineType::staticMetaObject, uri, major, minor, "FluTimelineType", "Access to enums & flags only"); qmlRegisterUncreatableMetaObject(FluSheetType::staticMetaObject, uri, major, minor, "FluSheetType", "Access to enums & flags only"); + qmlRegisterSingletonType(uri, major, minor, "FluApp", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QJSValue { + Q_UNUSED(engine) + return scriptEngine->newQObject(FluApp::getInstance()); + }); + qmlRegisterSingletonType(uri, major, minor, "FluColors", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QJSValue { + Q_UNUSED(engine) + return scriptEngine->newQObject(FluColors::getInstance()); + }); + qmlRegisterSingletonType(uri, major, minor, "FluTheme", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QJSValue { + Q_UNUSED(engine) + return scriptEngine->newQObject(FluTheme::getInstance()); + }); + qmlRegisterSingletonType(uri, major, minor, "FluTools", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QJSValue { + Q_UNUSED(engine) + return scriptEngine->newQObject(FluTools::getInstance()); + }); + qmlRegisterSingletonType(uri, major, minor, "FluTextStyle", [](QQmlEngine *engine, QJSEngine *scriptEngine) -> QJSValue { + Q_UNUSED(engine) + return scriptEngine->newQObject(FluTextStyle::getInstance()); + }); +// qmlRegisterSingletonInstance(uri, major, minor, "FluApp", FluApp::getInstance()); +// qmlRegisterSingletonInstance(uri, major, minor, "FluColors", FluColors::getInstance()); +// qmlRegisterSingletonInstance(uri, major, minor, "FluTheme", FluTheme::getInstance()); +// qmlRegisterSingletonInstance(uri, major, minor, "FluTools", FluTools::getInstance()); +// qmlRegisterSingletonInstance(uri, major, minor, "FluTextStyle", FluTextStyle::getInstance()); qmlRegisterModule(uri, major, minor); #endif } void FluentUI::initializeEngine(QQmlEngine *engine, [[maybe_unused]] const char *uri) { - engine->rootContext()->setContextProperty("FluApp", FluApp::getInstance()); - engine->rootContext()->setContextProperty("FluColors", FluColors::getInstance()); - engine->rootContext()->setContextProperty("FluTheme", FluTheme::getInstance()); - engine->rootContext()->setContextProperty("FluTools", FluTools::getInstance()); - engine->rootContext()->setContextProperty("FluTextStyle", FluTextStyle::getInstance()); + Q_UNUSED(engine) } diff --git a/src/Qt5/imports/FluentUI/Controls/FluComboBox.qml b/src/Qt5/imports/FluentUI/Controls/FluComboBox.qml index 4618f2fd..279fb0f3 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluComboBox.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluComboBox.qml @@ -11,6 +11,7 @@ T.ComboBox { property color normalColor: FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(254/255,254/255,254/255,1) property color hoverColor: FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1) property color disableColor: FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1) + property alias textBox: text_field implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, @@ -39,6 +40,7 @@ T.ComboBox { opacity: enabled ? 1 : 0.3 } contentItem: T.TextField { + id: text_field property bool disabled: !control.editable leftPadding: !control.mirrored ? 10 : control.editable && activeFocus ? 3 : 1 rightPadding: control.mirrored ? 10 : control.editable && activeFocus ? 3 : 1 diff --git a/src/Qt5/imports/FluentUI/Controls/FluInfoBar.qml b/src/Qt5/imports/FluentUI/Controls/FluInfoBar.qml index ecaaa37d..3177a8aa 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluInfoBar.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluInfoBar.qml @@ -3,45 +3,40 @@ import QtQuick.Controls 2.15 import FluentUI 1.0 FluObject { - property var root; + property var root property int layoutY: 75 id:control FluObject{ id:mcontrol - property string const_success: "success"; - property string const_info: "info"; - property string const_warning: "warning"; - property string const_error: "error"; - property int maxWidth: 300; - property var screenLayout: null; + property string const_success: "success" + property string const_info: "info" + property string const_warning: "warning" + property string const_error: "error" + property int maxWidth: 300 + property var screenLayout: null function create(type,text,duration,moremsg){ if(screenLayout){ - var last = screenLayout.getLastloader(); + var last = screenLayout.getLastloader() if(last.type === type && last.text === text && moremsg === last.moremsg){ last.duration = duration - if (duration > 0) last.restart(); - return last; + if (duration > 0) last.restart() + return last } } - initScreenLayout(); - return contentComponent.createObject(screenLayout,{ - type:type, - text:text, - duration:duration, - moremsg:moremsg, - }); + initScreenLayout() + return contentComponent.createObject(screenLayout,{type:type,text:text,duration:duration,moremsg:moremsg,}) } function createCustom(itemcomponent,duration){ - initScreenLayout(); + initScreenLayout() if(itemcomponent){ - return contentComponent.createObject(screenLayout,{itemcomponent:itemcomponent,duration:duration}); + return contentComponent.createObject(screenLayout,{itemcomponent:itemcomponent,duration:duration}) } } function initScreenLayout(){ if(screenLayout == null){ - screenLayout = screenlayoutComponent.createObject(root); - screenLayout.y = control.layoutY; - screenLayout.z = 100000; + screenLayout = screenlayoutComponent.createObject(root) + screenLayout.y = control.layoutY + screenLayout.z = 100000 } } Component{ @@ -58,44 +53,44 @@ FluObject { duration: FluTheme.animationEnabled ? 333 : 0 } } - onChildrenChanged: if(children.length === 0) destroy(); + onChildrenChanged: if(children.length === 0) destroy() function getLastloader(){ if(children.length > 0){ - return children[children.length - 1]; + return children[children.length - 1] } - return null; + return null } } } Component{ id:contentComponent Item{ - id:content; + id:content property int duration: 1500 property var itemcomponent property string type property string text property string moremsg - width: parent.width; - height: loader.height; + width: parent.width + height: loader.height function close(){ - content.destroy(); + content.destroy() } function restart(){ - delayTimer.restart(); + delayTimer.restart() } Timer { id:delayTimer - interval: duration; - running: duration > 0; + interval: duration + running: duration > 0 repeat: duration > 0 - onTriggered: content.close(); + onTriggered: content.close() } FluLoader{ - id:loader; - x:(parent.width - width) / 2; - property var _super: content; - scale: item ? 1 : 0; + id:loader + x:(parent.width - width) / 2 + property var _super: content + scale: item ? 1 : 0 asynchronous: true Behavior on scale { enabled: FluTheme.animationEnabled @@ -104,30 +99,30 @@ FluObject { duration: 167 } } - sourceComponent:itemcomponent ? itemcomponent : mcontrol.fluent_sytle; + sourceComponent:itemcomponent ? itemcomponent : mcontrol.fluent_sytle } } } property Component fluent_sytle: Rectangle{ - width: rowlayout.width + (btn_close.visible ? 30 : 48); - height: rowlayout.height + 20; + width: rowlayout.width + (btn_close.visible ? 30 : 48) + height: rowlayout.height + 20 color: { if(FluTheme.dark){ switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(57/255,61/255,27/255,1); - case mcontrol.const_warning: return Qt.rgba(67/255,53/255,25/255,1); - case mcontrol.const_info: return Qt.rgba(39/255,39/255,39/255,1); - case mcontrol.const_error: return Qt.rgba(68/255,39/255,38/255,1); + case mcontrol.const_success: return Qt.rgba(57/255,61/255,27/255,1) + case mcontrol.const_warning: return Qt.rgba(67/255,53/255,25/255,1) + case mcontrol.const_info: return Qt.rgba(39/255,39/255,39/255,1) + case mcontrol.const_error: return Qt.rgba(68/255,39/255,38/255,1) } - return Qt.rgba(255,255,255,1) + return Qt.rgba(1,1,1,1) }else{ switch(_super.type){ - case mcontrol.const_success: return "#dff6dd"; - case mcontrol.const_warning: return "#fff4ce"; - case mcontrol.const_info: return "#f4f4f4"; - case mcontrol.const_error: return "#fde7e9"; + case mcontrol.const_success: return Qt.rgba(223/255,246/255,221/255,1) + case mcontrol.const_warning: return Qt.rgba(255/255,244/255,206/255,1) + case mcontrol.const_info: return Qt.rgba(244/255,244/255,244/255,1) + case mcontrol.const_error: return Qt.rgba(253/255,231/255,233/255,1) } - return "#FFFFFF" + return Qt.rgba(1,1,1,1) } } FluShadow{ @@ -138,34 +133,34 @@ FluObject { border.color: { if(FluTheme.dark){ switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(56/255,61/255,27/255,1); - case mcontrol.const_warning: return Qt.rgba(66/255,53/255,25/255,1); - case mcontrol.const_info: return Qt.rgba(38/255,39/255,39/255,1); - case mcontrol.const_error: return Qt.rgba(67/255,39/255,38/255,1); + case mcontrol.const_success: return Qt.rgba(56/255,61/255,27/255,1) + case mcontrol.const_warning: return Qt.rgba(66/255,53/255,25/255,1) + case mcontrol.const_info: return Qt.rgba(38/255,39/255,39/255,1) + case mcontrol.const_error: return Qt.rgba(67/255,39/255,38/255,1) } - return "#FFFFFF" + return Qt.rgba(1,1,1,1) }else{ switch(_super.type){ - case mcontrol.const_success: return "#d2e8d0"; - case mcontrol.const_warning: return "#f0e6c2"; - case mcontrol.const_info: return "#e6e6e6"; - case mcontrol.const_error: return "#eed9db"; + case mcontrol.const_success: return Qt.rgba(210/255,232/255,208/255,1) + case mcontrol.const_warning: return Qt.rgba(240/255,230/255,194/255,1) + case mcontrol.const_info: return Qt.rgba(230/255,230/255,230/255,1) + case mcontrol.const_error: return Qt.rgba(238/255,217/255,219/255,1) } - return "#FFFFFF" + return Qt.rgba(1,1,1,1) } } Row{ id:rowlayout - x:20; - y:(parent.height - height) / 2; + x:20 + y:(parent.height - height) / 2 spacing: 10 FluIcon{ iconSource:{ switch(_super.type){ - case mcontrol.const_success: return FluentIcons.CompletedSolid; - case mcontrol.const_warning: return FluentIcons.InfoSolid; - case mcontrol.const_info: return FluentIcons.InfoSolid; - case mcontrol.const_error: return FluentIcons.StatusErrorFull; + case mcontrol.const_success: return FluentIcons.CompletedSolid + case mcontrol.const_warning: return FluentIcons.InfoSolid + case mcontrol.const_info: return FluentIcons.InfoSolid + case mcontrol.const_error: return FluentIcons.StatusErrorFull }FluentIcons.StatusErrorFull return FluentIcons.FA_info_circle } @@ -173,20 +168,20 @@ FluObject { iconColor: { if(FluTheme.dark){ switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1); - case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1); - case mcontrol.const_info: return FluTheme.primaryColor; - case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1); + case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1) + case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1) + case mcontrol.const_info: return FluTheme.primaryColor + case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1) } - return "#FFFFFF" + return Qt.rgba(1,1,1,1) }else{ switch(_super.type){ - case mcontrol.const_success: return "#0f7b0f"; - case mcontrol.const_warning: return "#9d5d00"; - case mcontrol.const_info: return "#0066b4"; - case mcontrol.const_error: return "#c42b1c"; + case mcontrol.const_success: return Qt.rgba(15/255,123/255,15/255,1) + case mcontrol.const_warning: return Qt.rgba(157/255,93/255,0/255,1) + case mcontrol.const_info: return Qt.rgba(0/255,102/255,180/255,1) + case mcontrol.const_error: return Qt.rgba(196/255,43/255,28/255,1) } - return "#FFFFFF" + return Qt.rgba(1,1,1,1) } } } @@ -211,46 +206,32 @@ FluObject { id:btn_close iconSource: FluentIcons.ChromeClose iconSize: 10 - y:5 + verticalPadding: 0 + horizontalPadding: 0 + width: 30 + height: 20 visible: _super.duration<=0 - iconColor: { - if(FluTheme.dark){ - switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1); - case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1); - case mcontrol.const_info: return FluTheme.primaryColor; - case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1); - } - return "#FFFFFF" - }else{ - switch(_super.type){ - case mcontrol.const_success: return "#0f7b0f"; - case mcontrol.const_warning: return "#9d5d00"; - case mcontrol.const_info: return "#0066b4"; - case mcontrol.const_error: return "#c42b1c"; - } - return "#FFFFFF" - } - } + anchors.verticalCenter: parent.verticalCenter + iconColor: FluTheme.dark ? Qt.rgba(222/255,222/255,222/255,1) : Qt.rgba(97/255,97/255,97/255,1) onClicked: _super.close() } } } } function showSuccess(text,duration=1000,moremsg){ - return mcontrol.create(mcontrol.const_success,text,duration,moremsg ? moremsg : ""); + return mcontrol.create(mcontrol.const_success,text,duration,moremsg ? moremsg : "") } function showInfo(text,duration=1000,moremsg){ - return mcontrol.create(mcontrol.const_info,text,duration,moremsg ? moremsg : ""); + return mcontrol.create(mcontrol.const_info,text,duration,moremsg ? moremsg : "") } function showWarning(text,duration=1000,moremsg){ - return mcontrol.create(mcontrol.const_warning,text,duration,moremsg ? moremsg : ""); + return mcontrol.create(mcontrol.const_warning,text,duration,moremsg ? moremsg : "") } function showError(text,duration=1000,moremsg){ - return mcontrol.create(mcontrol.const_error,text,duration,moremsg ? moremsg : ""); + return mcontrol.create(mcontrol.const_error,text,duration,moremsg ? moremsg : "") } function showCustom(itemcomponent,duration=1000){ - return mcontrol.createCustom(itemcomponent,duration); + return mcontrol.createCustom(itemcomponent,duration) } function clearAllInfo(){ if(mcontrol.screenLayout != null) { diff --git a/src/Qt5/imports/FluentUI/Controls/FluMenu.qml b/src/Qt5/imports/FluentUI/Controls/FluMenu.qml index c0c7cf1b..2e90ec89 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluMenu.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluMenu.qml @@ -39,7 +39,7 @@ T.Menu { : false clip: true currentIndex: control.currentIndex - ScrollIndicator.vertical: ScrollIndicator {} + ScrollBar.vertical: FluScrollBar{} } background: Rectangle { implicitWidth: 150 diff --git a/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml b/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml index 7ea83d06..3d06ad29 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluNavigationView.qml @@ -1120,7 +1120,8 @@ Item { } padding: 0 focus: true - contentItem: Item{ + contentItem: FluClip{ + radius: [5,5,5,5] ListView{ id:list_view anchors.fill: parent @@ -1145,7 +1146,6 @@ Item { visible: item_button.activeFocus radius:4 } - FluLoader{ id:item_dot_loader anchors{ @@ -1160,7 +1160,6 @@ Item { return undefined } } - } contentItem: FluText{ text:modelData.title @@ -1187,13 +1186,13 @@ Item { } } } - background: FluRectangle{ + background: Rectangle{ implicitWidth: 180 - radius: [4,4,4,4] - FluShadow{ - radius: 4 - } - color: FluTheme.dark ? Qt.rgba(51/255,48/255,48/255,1) : Qt.rgba(248/255,250/255,253/255,1) + color:FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(252/255,252/255,252/255,1) + border.color: FluTheme.dark ? Qt.rgba(26/255,26/255,26/255,1) : Qt.rgba(191/255,191/255,191/255,1) + border.width: 1 + radius: 5 + FluShadow{} } function showPopup(pos,height,model){ background.implicitHeight = height diff --git a/src/Qt5/imports/FluentUI/Controls/FluPagination.qml b/src/Qt5/imports/FluentUI/Controls/FluPagination.qml index acf2d796..496410b1 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluPagination.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluPagination.qml @@ -38,7 +38,6 @@ Item { } Row { spacing: 5 - FluToggleButton { property int pageNumber: 1 visible: control.pageCount > 0 @@ -98,7 +97,6 @@ Item { sourceComponent: footer } } - function calcNewPage(page) { if (!page) return @@ -108,5 +106,4 @@ Item { control.pageCurrent = page_num control.requestPage(page_num, control.__itemPerPage) } - } diff --git a/src/Qt5/imports/FluentUI/Controls/FluRadioButtons.qml b/src/Qt5/imports/FluentUI/Controls/FluRadioButtons.qml index b725950a..24c55a65 100644 --- a/src/Qt5/imports/FluentUI/Controls/FluRadioButtons.qml +++ b/src/Qt5/imports/FluentUI/Controls/FluRadioButtons.qml @@ -3,44 +3,87 @@ import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import FluentUI 1.0 -ColumnLayout { - default property alias buttons: control.data +Item{ + id:control + default property list buttons property int currentIndex : -1 + property int spacing: 8 + property int orientation: Qt.Vertical property bool disabled: false property bool manuallyDisabled: false - id:control - onCurrentIndexChanged: { - for(var i = 0;i=0 && currentIndex{ - d.current = rowObject + d.current = rowModel control.closeEditor() event.accepted = true } } FluLoader{ - property var model: itemModel - property var display: itemModel.display - property int row: item_table.position.y - property int column: item_table.position.x + id: item_table_loader + property var model: item_table_mouse._model + property var display: rowModel[columnModel.dataIndex] + property var rowModel : model.rowModel + property var columnModel : model.columnModel + property int row : model.row + property int column: model.column property bool isObject: typeof(display) == "object" property var options: { if(isObject){ @@ -304,15 +367,53 @@ Rectangle { } anchors.fill: parent sourceComponent: { - if(isObject){ - return display.comId + if(item_table_mouse.visible){ + if(isObject){ + return display.comId + } + return com_text } - return com_text + return undefined } } + FluLoader{ + id: loader_edit + property var tableView: control + property var display + property int column: { + if(d.editPosition){ + return d.editPosition.column + } + return 0 + } + property int row: { + if(d.editPosition){ + return d.editPosition.row + } + return 0 + } + anchors{ + fill: parent + margins: 1 + } + signal editTextChaged(string text) + sourceComponent: { + if(item_table_mouse.visible && d.editPosition && d.editPosition.column === model.column && d.editPosition.row === model.row){ + return d.editDelegate + } + return undefined + } + onEditTextChaged: + (text)=>{ + var obj = control.getRow(row) + obj[control.columnSource[column].dataIndex] = text + control.setRow(row,obj) + } + z:999 + } Item{ anchors.fill: parent - visible: item_table.isRowSelected + visible: item_table_mouse.isRowSelected Rectangle{ width: 1 height: parent.height @@ -343,6 +444,11 @@ Rectangle { } } } + + onWidthChanged:{ + table_view.forceLayout() + } + MouseArea{ id:layout_mouse_table hoverEnabled: true @@ -364,30 +470,8 @@ Rectangle { anchors.fill: parent ScrollBar.horizontal:scroll_bar_h ScrollBar.vertical:scroll_bar_v - columnWidthProvider: function(column) { - var columnObject = d.columns_data[column] - var width = columnObject.width - if(width){ - return width - } - var minimumWidth = columnObject.minimumWidth - if(minimumWidth){ - return minimumWidth - } - return d.defaultItemWidth - } - rowHeightProvider: function(row) { - var rowObject = control.getRow(row) - var height = rowObject.height - if(height){ - return height - } - var minimumHeight = rowObject._minimumHeight - if(minimumHeight){ - return minimumHeight - } - return d.defaultItemHeight - } + columnWidthProvider: control.columnWidthProvider + rowHeightProvider: control.rowHeightProvider model: table_sort_model clip: true onRowsChanged: { @@ -395,70 +479,51 @@ Rectangle { table_view.flick(0,1) } delegate: com_table_delegate - FluLoader{ - id:loader_edit - property var tableView: control - property var display - property int column: { - if(d.editPosition){ - return d.editPosition.column - } - return 0 - } - property int row: { - if(d.editPosition){ - return d.editPosition.row - } - return 0 - } - signal editTextChaged(string text) - sourceComponent: d.editPosition ? d.editDelegate : undefined - onEditTextChaged: - (text)=>{ - var obj = control.getRow(row) - obj[d.columns_data[column].dataIndex] = text - control.setRow(row,obj) - } - width: { - if(d.editPosition){ - return d.editPosition.width - } - return 0 - } - height: { - if(d.editPosition){ - return d.editPosition.height - } - return 0 - } - x:{ - if(d.editPosition){ - return d.editPosition.x - } - return 0 - } - y:{ - if(d.editPosition){ - return d.editPosition.y - } - return 0 - } - z:999 - } } } + Component{ - id:com_column_header_delegate + id: com_column_header_delegate Rectangle{ - id:column_item_control + id: column_item_control + property var currentTableView : TableView.view readonly property real cellPadding: 8 property bool canceled: false - property int columnIndex: column - readonly property var columnObject : d.columns_data[column] - implicitWidth: { - return (item_column_loader.item && item_column_loader.item.implicitWidth) + (cellPadding * 2) + property var _model: model + readonly property var columnModel : control.columnSource[_index] + readonly property int _index : { + const isDataIndex = (element) => { + return element.dataIndex === display.dataIndex + } + return control.columnSource.findIndex(isDataIndex) + } + readonly property bool isHeaderHorizontal: TableView.view == header_horizontal + readonly property bool isHide: { + if(isHeaderHorizontal){ + return false + } + if(!isHeaderHorizontal){ + if(currentTableView.dataIndex !== columnModel.dataIndex) + return true + } + return false + } + visible: !isHide + implicitWidth: { + if(isHide){ + return Number.MIN_VALUE + } + if(column_item_control.isHeaderHorizontal){ + return (item_column_loader.item && item_column_loader.item.implicitWidth) + (cellPadding * 2) + } + return Math.max(TableView.view.width,Number.MIN_VALUE) + } + implicitHeight: { + if(column_item_control.isHeaderHorizontal){ + return Math.max(36, (item_column_loader.item&&item_column_loader.item.implicitHeight) + (cellPadding * 2)) + } + return Math.max(TableView.view.height,Number.MIN_VALUE) } - implicitHeight: Math.max(36, (item_column_loader.item&&item_column_loader.item.implicitHeight) + (cellPadding * 2)) color: FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(247/255,247/255,247/255,1) Rectangle{ border.color: control.borderColor @@ -479,7 +544,7 @@ Rectangle { width: 1 height: parent.height anchors.left: parent.left - visible: column !== 0 + visible: column_item_control._index !== 0 color:"#00000000" } Rectangle{ @@ -488,7 +553,7 @@ Rectangle { height: parent.height anchors.right: parent.right color:"#00000000" - visible: column === table_view.columns - 1 + visible: column_item_control._index === table_view.columns - 1 } MouseArea{ id:column_item_control_mouse @@ -510,22 +575,23 @@ Rectangle { } FluLoader{ id:item_column_loader - property var itemModel: model - property var modelData: model.display + property var model: column_item_control._model + property var display: model.display.title property var tableView: table_view - property var tableModel: table_model + property var sourceModel: control.sourceModel + property bool isObject: typeof(display) == "object" property var options:{ - if(typeof(modelData) == "object"){ - return modelData.options + if(isObject){ + return display.options } return {} } - property int column: column_item_control.columnIndex + property int column: column_item_control._index width: parent.width height: parent.height sourceComponent: { - if(typeof(modelData) == "object"){ - return modelData.comId + if(isObject){ + return display.comId } return com_column_text } @@ -537,7 +603,7 @@ Rectangle { anchors.right: parent.right acceptedButtons: Qt.LeftButton hoverEnabled: true - visible: !(columnObject.width === columnObject.minimumWidth && columnObject.width === columnObject.maximumWidth && columnObject.width) + visible: !columnModel.frozen && !(columnModel.width === columnModel.minimumWidth && columnModel.width === columnModel.maximumWidth && columnModel.width) cursorShape: Qt.SplitHCursor preventStealing: true onPressed : @@ -557,9 +623,9 @@ Rectangle { return } var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y) - var minimumWidth = columnObject.minimumWidth - var maximumWidth = columnObject.maximumWidth - var w = columnObject.width + var minimumWidth = columnModel.minimumWidth + var maximumWidth = columnModel.maximumWidth + var w = columnModel.width if(!w){ w = d.defaultItemWidth } @@ -569,7 +635,7 @@ Rectangle { if(!maximumWidth){ maximumWidth = 65535 } - columnObject.width = Math.min(Math.max(minimumWidth, w + delta.x),maximumWidth) + columnModel.width = Math.min(Math.max(minimumWidth, w + delta.x),maximumWidth) table_view.forceLayout() header_horizontal.forceLayout() } @@ -582,7 +648,7 @@ Rectangle { id:item_control readonly property real cellPadding: 8 property bool canceled: false - property var rowObject: control.getRow(row) + property var rowModel: control.getRow(row) implicitWidth: Math.max(30, row_text.implicitWidth + (cellPadding * 2)) implicitHeight: row_text.implicitHeight + (cellPadding * 2) color: FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(247/255,247/255,247/255,1) @@ -648,9 +714,9 @@ Rectangle { cursorShape: Qt.SplitVCursor preventStealing: true visible: { - if(rowObject === null) + if(rowModel === null) return false - return !(rowObject.height === rowObject._minimumHeight && rowObject.height === rowObject._maximumHeight && rowObject.height) + return !(rowModel.height === rowModel._minimumHeight && rowModel.height === rowModel._maximumHeight && rowModel.height) } onPressed : (mouse)=>{ @@ -668,11 +734,11 @@ Rectangle { if(!pressed){ return } - var rowObject = control.getRow(row) + var rowModel = control.getRow(row) var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y) - var minimumHeight = rowObject._minimumHeight - var maximumHeight = rowObject._maximumHeight - var h = rowObject.height + var minimumHeight = rowModel._minimumHeight + var maximumHeight = rowModel._maximumHeight + var h = rowModel.height if(!h){ h = d.defaultItemHeight } @@ -682,8 +748,8 @@ Rectangle { if(!maximumHeight){ maximumHeight = 65535 } - rowObject.height = Math.min(Math.max(minimumHeight, h + delta.y),maximumHeight) - control.setRow(row,rowObject) + rowModel.height = Math.min(Math.max(minimumHeight, h + delta.y),maximumHeight) + control.setRow(row,rowModel) table_view.forceLayout() } } @@ -693,7 +759,7 @@ Rectangle { id:com_column_text FluText { id: column_text - text: modelData + text: String(display) anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -796,6 +862,138 @@ Rectangle { } } } + Item{ + anchors{ + left: header_vertical.right + top: parent.top + bottom: parent.bottom + right: parent.right + } + Component{ + id: com_table_frozen + Rectangle{ + id: item_layout_frozen + anchors.fill: parent + color: { + if(Window.active){ + return FluTheme.dark ? Qt.rgba(48/255,48/255,48/255,1) :Qt.rgba(1,1,1,1) + } + return FluTheme.dark ? Qt.rgba(56/255,56/255,56/255,1) :Qt.rgba(243/255,243/255,243/255,1) + } + visible: table_view.rows !== 0 + Rectangle{ + z:99 + anchors.fill: parent + border.color: FluTheme.dark ? Qt.rgba(26/255,26/255,26/255,0.6) : Qt.rgba(191/255,191/255,191/255,0.3) + FluShadow{ + radius: 0 + anchors.fill: parent + } + color: "#00000000" + } + TableView{ + property string dataIndex: columnModel.dataIndex + id: item_table_frozen + interactive: false + clip: true + anchors{ + left: parent.left + right: parent.right + } + contentWidth: width + height: table_view.height + y: header_horizontal.height + boundsBehavior: TableView.StopAtBounds + model: table_view.model + delegate: table_view.delegate + syncDirection: Qt.Vertical + syncView: table_view + } + TableView { + property string dataIndex: columnModel.dataIndex + id:item_table_frozen_header + model: header_column_model + boundsBehavior: Flickable.StopAtBounds + interactive: false + clip: true + contentWidth: width + anchors{ + left: parent.left + right: parent.right + top: parent.top + bottom: item_table_frozen.top + } + delegate: com_column_header_delegate + Component.onCompleted: { + item_table_frozen_header.forceLayout() + } + } + Connections{ + target: table_view + function onWidthChanged() { + item_table_frozen_header.forceLayout() + } + + } + } + } + Repeater{ + Component.onCompleted: { + model = control.columnSource + } + delegate: FluLoader{ + id: item_layout_frozen + readonly property int _index : model.index + readonly property var columnModel : control.columnSource[_index] + readonly property bool isHide:{ + if(columnModel.frozen){ + return false + } + return true + } + Connections{ + target: d + function onTableItemLayout(column){ + if(item_layout_frozen._index === column){ + updateLayout() + } + } + } + Connections{ + target: table_view + function onContentXChanged(){ + updateLayout() + } + } + function updateLayout(){ + width = table_view.columnWidthProvider(_index) + x = Qt.binding(function(){ + var minX = 0 + var maxX = table_view.width-width + for(var i=0;i<_index;i++){ + var item = control.columnSource[i] + if(item.frozen){ + minX = minX + table_view.columnWidthProvider(i) + } + } + for(i=_index+1;i=0 && rowIndex" } + Property { name: "rows"; type: "QList" } + Property { name: "rowCount"; type: "int"; isReadonly: true } + Method { name: "clear" } Method { name: "getRow" type: "QVariant" @@ -278,6 +296,46 @@ Module { Method { name: "setRow" Parameter { name: "rowIndex"; type: "int" } + Parameter { name: "row"; type: "QVariant" } + } + Method { + name: "insertRow" + Parameter { name: "rowIndex"; type: "int" } + Parameter { name: "row"; type: "QVariant" } + } + Method { + name: "removeRow" + Parameter { name: "rowIndex"; type: "int" } + Parameter { name: "rows"; type: "int" } + } + Method { + name: "removeRow" + Parameter { name: "rowIndex"; type: "int" } + } + Method { + name: "appendRow" + Parameter { name: "row"; type: "QVariant" } + } + } + Component { + name: "FluTableSortProxyModel" + prototype: "QSortFilterProxyModel" + exports: ["FluentUI/FluTableSortProxyModel 1.0"] + exportMetaObjectRevisions: [0] + Property { name: "model"; type: "QVariant" } + Method { + name: "getRow" + type: "QVariant" + Parameter { name: "rowIndex"; type: "int" } + } + Method { + name: "setRow" + Parameter { name: "rowIndex"; type: "int" } + Parameter { name: "val"; type: "QVariant" } + } + Method { + name: "insertRow" + Parameter { name: "rowIndex"; type: "int" } Parameter { name: "val"; type: "QVariant" } } Method { @@ -298,6 +356,8 @@ Module { name: "FluTextStyle" prototype: "QObject" exports: ["FluentUI/FluTextStyle 1.0"] + isCreatable: false + isSingleton: true exportMetaObjectRevisions: [0] Property { name: "family"; type: "string" } Property { name: "Caption"; type: "QFont" } @@ -312,6 +372,8 @@ Module { name: "FluTheme" prototype: "QObject" exports: ["FluentUI/FluTheme 1.0"] + isCreatable: false + isSingleton: true exportMetaObjectRevisions: [0] Property { name: "dark"; type: "bool"; isReadonly: true } Property { name: "accentColor"; type: "FluAccentColor"; isPointer: true } @@ -380,6 +442,8 @@ Module { name: "FluTools" prototype: "QObject" exports: ["FluentUI/FluTools 1.0"] + isCreatable: false + isSingleton: true exportMetaObjectRevisions: [0] Method { name: "qtMajor"; type: "int" } Method { name: "qtMinor"; type: "int" } @@ -499,7 +563,7 @@ Module { } Component { name: "FluTreeModel" - prototype: "QAbstractItemModel" + prototype: "QAbstractTableModel" exports: ["FluentUI/FluTreeModel 1.0"] exportMetaObjectRevisions: [0] Property { name: "dataSourceSize"; type: "int" } @@ -2316,6 +2380,7 @@ Module { Parameter { name: "selection"; type: "QItemSelection" } } } + Component { name: "QAbstractTableModel"; prototype: "QAbstractItemModel" } Component { name: "QSortFilterProxyModel" prototype: "QAbstractProxyModel" @@ -2442,37 +2507,37 @@ Module { Property { name: "darkClickListener"; type: "QVariant" } Property { name: "buttonStayTop" - type: "FluIconButton_QMLTYPE_20" + type: "FluIconButton_QMLTYPE_18" isReadonly: true isPointer: true } Property { name: "buttonMinimize" - type: "FluIconButton_QMLTYPE_20" + type: "FluIconButton_QMLTYPE_18" isReadonly: true isPointer: true } Property { name: "buttonMaximize" - type: "FluIconButton_QMLTYPE_20" + type: "FluIconButton_QMLTYPE_18" isReadonly: true isPointer: true } Property { name: "buttonClose" - type: "FluIconButton_QMLTYPE_20" + type: "FluIconButton_QMLTYPE_18" isReadonly: true isPointer: true } Property { name: "buttonDark" - type: "FluIconButton_QMLTYPE_20" + type: "FluIconButton_QMLTYPE_18" isReadonly: true isPointer: true } Property { name: "layoutMacosButtons" - type: "FluLoader_QMLTYPE_14" + type: "FluLoader_QMLTYPE_16" isReadonly: true isPointer: true } @@ -2493,6 +2558,7 @@ Module { Property { name: "items"; type: "QVariant" } Property { name: "emptyText"; type: "string" } Property { name: "autoSuggestBoxReplacement"; type: "int" } + Property { name: "textRole"; type: "string" } Property { name: "filter"; type: "QVariant" } Signal { name: "itemClicked" @@ -2695,6 +2761,7 @@ Module { Property { name: "normalColor"; type: "QColor" } Property { name: "hoverColor"; type: "QColor" } Property { name: "disableColor"; type: "QColor" } + Property { name: "textBox"; type: "QQuickTextField"; isReadonly: true; isPointer: true } Signal { name: "commit" Parameter { name: "text"; type: "string" } @@ -3043,6 +3110,7 @@ Module { Parameter { name: "itemcomponent"; type: "QVariant" } Parameter { name: "duration"; type: "QVariant" } } + Method { name: "clearAllInfo"; type: "QVariant" } Property { name: "children"; type: "QObject"; isList: true; isReadonly: true } } Component { @@ -3162,15 +3230,15 @@ Module { defaultProperty: "data" Property { name: "logo"; type: "QUrl" } Property { name: "title"; type: "string" } - Property { name: "items"; type: "FluObject_QMLTYPE_172"; isPointer: true } - Property { name: "footerItems"; type: "FluObject_QMLTYPE_172"; isPointer: true } + Property { name: "items"; type: "FluObject_QMLTYPE_164"; isPointer: true } + Property { name: "footerItems"; type: "FluObject_QMLTYPE_164"; isPointer: true } Property { name: "displayMode"; type: "int" } Property { name: "autoSuggestBox"; type: "QQmlComponent"; isPointer: true } Property { name: "actionItem"; type: "QQmlComponent"; isPointer: true } Property { name: "topPadding"; type: "int" } Property { name: "pageMode"; type: "int" } - Property { name: "navItemRightMenu"; type: "FluMenu_QMLTYPE_33"; isPointer: true } - Property { name: "navItemExpanderRightMenu"; type: "FluMenu_QMLTYPE_33"; isPointer: true } + Property { name: "navItemRightMenu"; type: "FluMenu_QMLTYPE_36"; isPointer: true } + Property { name: "navItemExpanderRightMenu"; type: "FluMenu_QMLTYPE_36"; isPointer: true } Property { name: "navCompactWidth"; type: "int" } Property { name: "navTopMargin"; type: "int" } Property { name: "cellHeight"; type: "int" } @@ -3178,13 +3246,13 @@ Module { Property { name: "hideNavAppBar"; type: "bool" } Property { name: "buttonMenu" - type: "FluIconButton_QMLTYPE_20" + type: "FluIconButton_QMLTYPE_18" isReadonly: true isPointer: true } Property { name: "buttonBack" - type: "FluIconButton_QMLTYPE_20" + type: "FluIconButton_QMLTYPE_18" isReadonly: true isPointer: true } @@ -3246,6 +3314,8 @@ Module { Property { name: "pageCount"; type: "int" } Property { name: "__itemPerPage"; type: "int" } Property { name: "__pageButtonHalf"; type: "int" } + Property { name: "header"; type: "QQmlComponent"; isPointer: true } + Property { name: "footer"; type: "QQmlComponent"; isPointer: true } Signal { name: "requestPage" Parameter { name: "page"; type: "int" } @@ -3470,14 +3540,18 @@ Module { Property { name: "textColor"; type: "QColor" } } Component { - prototype: "QQuickColumnLayout" + prototype: "QQuickItem" name: "FluentUI/FluRadioButtons 1.0" exports: ["FluentUI/FluRadioButtons 1.0"] exportMetaObjectRevisions: [0] isComposite: true defaultProperty: "buttons" - Property { name: "currentIndex"; type: "int" } Property { name: "buttons"; type: "QObject"; isList: true; isReadonly: true } + Property { name: "currentIndex"; type: "int" } + Property { name: "spacing"; type: "int" } + Property { name: "orientation"; type: "int" } + Property { name: "disabled"; type: "bool" } + Property { name: "manuallyDisabled"; type: "bool" } } Component { prototype: "QQuickRangeSlider" @@ -3632,6 +3706,9 @@ Module { Property { name: "positiveText"; type: "string" } Property { name: "neutralText"; type: "string" } Property { name: "negativeText"; type: "string" } + Property { name: "registered"; type: "bool" } + Property { name: "errorColor"; type: "QColor" } + Property { name: "syncHotkey"; type: "FluHotkey"; isPointer: true } Signal { name: "accepted" } Property { name: "iconSize"; type: "int" } Property { name: "iconSource"; type: "int" } @@ -3757,6 +3834,7 @@ Module { exportMetaObjectRevisions: [0] isComposite: true defaultProperty: "data" + Property { name: "sourceModel"; type: "QVariant" } Property { name: "columnSource"; type: "QVariant" } Property { name: "dataSource"; type: "QVariant" } Property { name: "borderColor"; type: "QColor" } @@ -3764,10 +3842,12 @@ Module { Property { name: "verticalHeaderVisible"; type: "bool" } Property { name: "selectedBorderColor"; type: "QColor" } Property { name: "selectedColor"; type: "QColor" } + Property { name: "columnWidthProvider"; type: "QVariant" } + Property { name: "rowHeightProvider"; type: "QVariant" } Property { name: "rows"; type: "int"; isReadonly: true } Property { name: "columns"; type: "int"; isReadonly: true } Property { name: "current"; type: "QVariant"; isReadonly: true } - Property { name: "sourceModel"; type: "QQmlTableModel"; isReadonly: true; isPointer: true } + Property { name: "view"; type: "QQuickTableView"; isReadonly: true; isPointer: true } Method { name: "closeEditor"; type: "QVariant" } Method { name: "resetPosition"; type: "QVariant" } Method { @@ -3803,6 +3883,13 @@ Module { Parameter { name: "rowIndex"; type: "QVariant" } Parameter { name: "rows"; type: "QVariant" } } + Method { + name: "insertRow" + type: "QVariant" + Parameter { name: "rowIndex"; type: "QVariant" } + Parameter { name: "obj"; type: "QVariant" } + } + Method { name: "currentIndex"; type: "QVariant" } Method { name: "appendRow" type: "QVariant" @@ -4013,6 +4100,7 @@ Module { Property { name: "selectedBorderColor"; type: "QColor" } Property { name: "selectedColor"; type: "QColor" } Property { name: "current"; type: "QVariant"; isReadonly: true } + Property { name: "view"; type: "QQuickTableView"; isReadonly: true; isPointer: true } Method { name: "count"; type: "QVariant" } Method { name: "visibleCount"; type: "QVariant" } Method { @@ -4068,6 +4156,7 @@ Module { Property { name: "closeListener"; type: "QVariant" } Property { name: "_windowRegister"; type: "QVariant" } Property { name: "_route"; type: "string" } + Property { name: "_hideShadow"; type: "bool" } Property { name: "contentData"; type: "QObject"; isList: true; isReadonly: true } Signal { name: "initArgument" @@ -4103,6 +4192,7 @@ Module { Parameter { name: "duration"; type: "QVariant" } Parameter { name: "moremsg"; type: "QVariant" } } + Method { name: "clearAllInfo"; type: "QVariant" } Method { name: "moveWindowToDesktopCenter"; type: "QVariant" } Method { name: "fixWindowSize"; type: "QVariant" } Method { @@ -4116,6 +4206,8 @@ Module { Parameter { name: "data"; type: "QVariant" } } Method { name: "showMaximized"; type: "QVariant" } + Method { name: "showMinimized"; type: "QVariant" } + Method { name: "showNormal"; type: "QVariant" } Method { name: "showLoading" type: "QVariant" @@ -4136,7 +4228,12 @@ Module { isComposite: true defaultProperty: "contentData" Property { name: "contentDelegate"; type: "QQmlComponent"; isPointer: true } - Method { name: "showDialog"; type: "QVariant" } + Method { + name: "showDialog" + type: "QVariant" + Parameter { name: "offsetX"; type: "QVariant" } + Parameter { name: "offsetY"; type: "QVariant" } + } Property { name: "windowIcon"; type: "string" } Property { name: "launchMode"; type: "int" } Property { name: "argument"; type: "QVariant" } @@ -4162,6 +4259,7 @@ Module { Property { name: "closeListener"; type: "QVariant" } Property { name: "_windowRegister"; type: "QVariant" } Property { name: "_route"; type: "string" } + Property { name: "_hideShadow"; type: "bool" } Property { name: "contentData"; type: "QObject"; isList: true; isReadonly: true } Signal { name: "initArgument" @@ -4197,6 +4295,7 @@ Module { Parameter { name: "duration"; type: "QVariant" } Parameter { name: "moremsg"; type: "QVariant" } } + Method { name: "clearAllInfo"; type: "QVariant" } Method { name: "moveWindowToDesktopCenter"; type: "QVariant" } Method { name: "fixWindowSize"; type: "QVariant" } Method { @@ -4210,6 +4309,8 @@ Module { Parameter { name: "data"; type: "QVariant" } } Method { name: "showMaximized"; type: "QVariant" } + Method { name: "showMinimized"; type: "QVariant" } + Method { name: "showNormal"; type: "QVariant" } Method { name: "showLoading" type: "QVariant" diff --git a/src/Qt6/imports/FluentUI/Controls/FluColorPicker.qml b/src/Qt6/imports/FluentUI/Controls/FluColorPicker.qml index c22eb090..59798006 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluColorPicker.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluColorPicker.qml @@ -1,5 +1,6 @@ import QtQuick import QtQuick.Controls +import QtQuick.Controls.Basic import QtQuick.Layouts import QtQuick.Window import FluentUI diff --git a/src/Qt6/imports/FluentUI/Controls/FluComboBox.qml b/src/Qt6/imports/FluentUI/Controls/FluComboBox.qml index 6a89782b..70e01e81 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluComboBox.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluComboBox.qml @@ -11,6 +11,7 @@ T.ComboBox { property color normalColor: FluTheme.dark ? Qt.rgba(62/255,62/255,62/255,1) : Qt.rgba(254/255,254/255,254/255,1) property color hoverColor: FluTheme.dark ? Qt.rgba(68/255,68/255,68/255,1) : Qt.rgba(251/255,251/255,251/255,1) property color disableColor: FluTheme.dark ? Qt.rgba(59/255,59/255,59/255,1) : Qt.rgba(252/255,252/255,252/255,1) + property alias textBox: text_field implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, implicitContentWidth + leftPadding + rightPadding) implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, @@ -39,6 +40,7 @@ T.ComboBox { opacity: enabled ? 1 : 0.3 } contentItem: T.TextField { + id: text_field property bool disabled: !control.editable leftPadding: !control.mirrored ? 10 : control.editable && activeFocus ? 3 : 1 rightPadding: control.mirrored ? 10 : control.editable && activeFocus ? 3 : 1 diff --git a/src/Qt6/imports/FluentUI/Controls/FluInfoBar.qml b/src/Qt6/imports/FluentUI/Controls/FluInfoBar.qml index ecaaa37d..6fac2e08 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluInfoBar.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluInfoBar.qml @@ -1,47 +1,42 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import FluentUI 1.0 +import QtQuick +import QtQuick.Controls +import FluentUI FluObject { - property var root; + property var root property int layoutY: 75 id:control FluObject{ id:mcontrol - property string const_success: "success"; - property string const_info: "info"; - property string const_warning: "warning"; - property string const_error: "error"; - property int maxWidth: 300; - property var screenLayout: null; + property string const_success: "success" + property string const_info: "info" + property string const_warning: "warning" + property string const_error: "error" + property int maxWidth: 300 + property var screenLayout: null function create(type,text,duration,moremsg){ if(screenLayout){ - var last = screenLayout.getLastloader(); + var last = screenLayout.getLastloader() if(last.type === type && last.text === text && moremsg === last.moremsg){ last.duration = duration - if (duration > 0) last.restart(); - return last; + if (duration > 0) last.restart() + return last } } - initScreenLayout(); - return contentComponent.createObject(screenLayout,{ - type:type, - text:text, - duration:duration, - moremsg:moremsg, - }); + initScreenLayout() + return contentComponent.createObject(screenLayout,{type:type,text:text,duration:duration,moremsg:moremsg,}) } function createCustom(itemcomponent,duration){ - initScreenLayout(); + initScreenLayout() if(itemcomponent){ - return contentComponent.createObject(screenLayout,{itemcomponent:itemcomponent,duration:duration}); + return contentComponent.createObject(screenLayout,{itemcomponent:itemcomponent,duration:duration}) } } function initScreenLayout(){ if(screenLayout == null){ - screenLayout = screenlayoutComponent.createObject(root); - screenLayout.y = control.layoutY; - screenLayout.z = 100000; + screenLayout = screenlayoutComponent.createObject(root) + screenLayout.y = control.layoutY + screenLayout.z = 100000 } } Component{ @@ -58,44 +53,44 @@ FluObject { duration: FluTheme.animationEnabled ? 333 : 0 } } - onChildrenChanged: if(children.length === 0) destroy(); + onChildrenChanged: if(children.length === 0) destroy() function getLastloader(){ if(children.length > 0){ - return children[children.length - 1]; + return children[children.length - 1] } - return null; + return null } } } Component{ id:contentComponent Item{ - id:content; + id:content property int duration: 1500 property var itemcomponent property string type property string text property string moremsg - width: parent.width; - height: loader.height; + width: parent.width + height: loader.height function close(){ - content.destroy(); + content.destroy() } function restart(){ - delayTimer.restart(); + delayTimer.restart() } Timer { id:delayTimer - interval: duration; - running: duration > 0; + interval: duration + running: duration > 0 repeat: duration > 0 - onTriggered: content.close(); + onTriggered: content.close() } FluLoader{ - id:loader; - x:(parent.width - width) / 2; - property var _super: content; - scale: item ? 1 : 0; + id:loader + x:(parent.width - width) / 2 + property var _super: content + scale: item ? 1 : 0 asynchronous: true Behavior on scale { enabled: FluTheme.animationEnabled @@ -104,30 +99,30 @@ FluObject { duration: 167 } } - sourceComponent:itemcomponent ? itemcomponent : mcontrol.fluent_sytle; + sourceComponent:itemcomponent ? itemcomponent : mcontrol.fluent_sytle } } } property Component fluent_sytle: Rectangle{ - width: rowlayout.width + (btn_close.visible ? 30 : 48); - height: rowlayout.height + 20; + width: rowlayout.width + (btn_close.visible ? 30 : 48) + height: rowlayout.height + 20 color: { if(FluTheme.dark){ switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(57/255,61/255,27/255,1); - case mcontrol.const_warning: return Qt.rgba(67/255,53/255,25/255,1); - case mcontrol.const_info: return Qt.rgba(39/255,39/255,39/255,1); - case mcontrol.const_error: return Qt.rgba(68/255,39/255,38/255,1); + case mcontrol.const_success: return Qt.rgba(57/255,61/255,27/255,1) + case mcontrol.const_warning: return Qt.rgba(67/255,53/255,25/255,1) + case mcontrol.const_info: return Qt.rgba(39/255,39/255,39/255,1) + case mcontrol.const_error: return Qt.rgba(68/255,39/255,38/255,1) } - return Qt.rgba(255,255,255,1) + return Qt.rgba(1,1,1,1) }else{ switch(_super.type){ - case mcontrol.const_success: return "#dff6dd"; - case mcontrol.const_warning: return "#fff4ce"; - case mcontrol.const_info: return "#f4f4f4"; - case mcontrol.const_error: return "#fde7e9"; + case mcontrol.const_success: return Qt.rgba(223/255,246/255,221/255,1) + case mcontrol.const_warning: return Qt.rgba(255/255,244/255,206/255,1) + case mcontrol.const_info: return Qt.rgba(244/255,244/255,244/255,1) + case mcontrol.const_error: return Qt.rgba(253/255,231/255,233/255,1) } - return "#FFFFFF" + return Qt.rgba(1,1,1,1) } } FluShadow{ @@ -138,34 +133,34 @@ FluObject { border.color: { if(FluTheme.dark){ switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(56/255,61/255,27/255,1); - case mcontrol.const_warning: return Qt.rgba(66/255,53/255,25/255,1); - case mcontrol.const_info: return Qt.rgba(38/255,39/255,39/255,1); - case mcontrol.const_error: return Qt.rgba(67/255,39/255,38/255,1); + case mcontrol.const_success: return Qt.rgba(56/255,61/255,27/255,1) + case mcontrol.const_warning: return Qt.rgba(66/255,53/255,25/255,1) + case mcontrol.const_info: return Qt.rgba(38/255,39/255,39/255,1) + case mcontrol.const_error: return Qt.rgba(67/255,39/255,38/255,1) } - return "#FFFFFF" + return Qt.rgba(1,1,1,1) }else{ switch(_super.type){ - case mcontrol.const_success: return "#d2e8d0"; - case mcontrol.const_warning: return "#f0e6c2"; - case mcontrol.const_info: return "#e6e6e6"; - case mcontrol.const_error: return "#eed9db"; + case mcontrol.const_success: return Qt.rgba(210/255,232/255,208/255,1) + case mcontrol.const_warning: return Qt.rgba(240/255,230/255,194/255,1) + case mcontrol.const_info: return Qt.rgba(230/255,230/255,230/255,1) + case mcontrol.const_error: return Qt.rgba(238/255,217/255,219/255,1) } - return "#FFFFFF" + return Qt.rgba(1,1,1,1) } } Row{ id:rowlayout - x:20; - y:(parent.height - height) / 2; + x:20 + y:(parent.height - height) / 2 spacing: 10 FluIcon{ iconSource:{ switch(_super.type){ - case mcontrol.const_success: return FluentIcons.CompletedSolid; - case mcontrol.const_warning: return FluentIcons.InfoSolid; - case mcontrol.const_info: return FluentIcons.InfoSolid; - case mcontrol.const_error: return FluentIcons.StatusErrorFull; + case mcontrol.const_success: return FluentIcons.CompletedSolid + case mcontrol.const_warning: return FluentIcons.InfoSolid + case mcontrol.const_info: return FluentIcons.InfoSolid + case mcontrol.const_error: return FluentIcons.StatusErrorFull }FluentIcons.StatusErrorFull return FluentIcons.FA_info_circle } @@ -173,20 +168,20 @@ FluObject { iconColor: { if(FluTheme.dark){ switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1); - case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1); - case mcontrol.const_info: return FluTheme.primaryColor; - case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1); + case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1) + case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1) + case mcontrol.const_info: return FluTheme.primaryColor + case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1) } - return "#FFFFFF" + return Qt.rgba(1,1,1,1) }else{ switch(_super.type){ - case mcontrol.const_success: return "#0f7b0f"; - case mcontrol.const_warning: return "#9d5d00"; - case mcontrol.const_info: return "#0066b4"; - case mcontrol.const_error: return "#c42b1c"; + case mcontrol.const_success: return Qt.rgba(15/255,123/255,15/255,1) + case mcontrol.const_warning: return Qt.rgba(157/255,93/255,0/255,1) + case mcontrol.const_info: return Qt.rgba(0/255,102/255,180/255,1) + case mcontrol.const_error: return Qt.rgba(196/255,43/255,28/255,1) } - return "#FFFFFF" + return Qt.rgba(1,1,1,1) } } } @@ -211,46 +206,32 @@ FluObject { id:btn_close iconSource: FluentIcons.ChromeClose iconSize: 10 - y:5 + verticalPadding: 0 + horizontalPadding: 0 + width: 30 + height: 20 visible: _super.duration<=0 - iconColor: { - if(FluTheme.dark){ - switch(_super.type){ - case mcontrol.const_success: return Qt.rgba(108/255,203/255,95/255,1); - case mcontrol.const_warning: return Qt.rgba(252/255,225/255,0/255,1); - case mcontrol.const_info: return FluTheme.primaryColor; - case mcontrol.const_error: return Qt.rgba(255/255,153/255,164/255,1); - } - return "#FFFFFF" - }else{ - switch(_super.type){ - case mcontrol.const_success: return "#0f7b0f"; - case mcontrol.const_warning: return "#9d5d00"; - case mcontrol.const_info: return "#0066b4"; - case mcontrol.const_error: return "#c42b1c"; - } - return "#FFFFFF" - } - } + anchors.verticalCenter: parent.verticalCenter + iconColor: FluTheme.dark ? Qt.rgba(222/255,222/255,222/255,1) : Qt.rgba(97/255,97/255,97/255,1) onClicked: _super.close() } } } } function showSuccess(text,duration=1000,moremsg){ - return mcontrol.create(mcontrol.const_success,text,duration,moremsg ? moremsg : ""); + return mcontrol.create(mcontrol.const_success,text,duration,moremsg ? moremsg : "") } function showInfo(text,duration=1000,moremsg){ - return mcontrol.create(mcontrol.const_info,text,duration,moremsg ? moremsg : ""); + return mcontrol.create(mcontrol.const_info,text,duration,moremsg ? moremsg : "") } function showWarning(text,duration=1000,moremsg){ - return mcontrol.create(mcontrol.const_warning,text,duration,moremsg ? moremsg : ""); + return mcontrol.create(mcontrol.const_warning,text,duration,moremsg ? moremsg : "") } function showError(text,duration=1000,moremsg){ - return mcontrol.create(mcontrol.const_error,text,duration,moremsg ? moremsg : ""); + return mcontrol.create(mcontrol.const_error,text,duration,moremsg ? moremsg : "") } function showCustom(itemcomponent,duration=1000){ - return mcontrol.createCustom(itemcomponent,duration); + return mcontrol.createCustom(itemcomponent,duration) } function clearAllInfo(){ if(mcontrol.screenLayout != null) { diff --git a/src/Qt6/imports/FluentUI/Controls/FluMenu.qml b/src/Qt6/imports/FluentUI/Controls/FluMenu.qml index 4298d725..da363c65 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluMenu.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluMenu.qml @@ -39,7 +39,7 @@ T.Menu { : false clip: true currentIndex: control.currentIndex - ScrollIndicator.vertical: ScrollIndicator {} + ScrollBar.vertical: FluScrollBar{} } background: Rectangle { implicitWidth: 150 diff --git a/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml b/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml index ad836a3b..b9b2a229 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluNavigationView.qml @@ -1121,7 +1121,8 @@ Item { } padding: 0 focus: true - contentItem: Item{ + contentItem: FluClip{ + radius: [5,5,5,5] ListView{ id:list_view anchors.fill: parent @@ -1146,7 +1147,6 @@ Item { visible: item_button.activeFocus radius:4 } - FluLoader{ id:item_dot_loader anchors{ @@ -1161,7 +1161,6 @@ Item { return undefined } } - } contentItem: FluText{ text:modelData.title @@ -1188,13 +1187,13 @@ Item { } } } - background: FluRectangle{ + background: Rectangle{ implicitWidth: 180 - radius: [4,4,4,4] - FluShadow{ - radius: 4 - } - color: FluTheme.dark ? Qt.rgba(51/255,48/255,48/255,1) : Qt.rgba(248/255,250/255,253/255,1) + color:FluTheme.dark ? Qt.rgba(45/255,45/255,45/255,1) : Qt.rgba(252/255,252/255,252/255,1) + border.color: FluTheme.dark ? Qt.rgba(26/255,26/255,26/255,1) : Qt.rgba(191/255,191/255,191/255,1) + border.width: 1 + radius: 5 + FluShadow{} } function showPopup(pos,height,model){ background.implicitHeight = height diff --git a/src/Qt6/imports/FluentUI/Controls/FluPagination.qml b/src/Qt6/imports/FluentUI/Controls/FluPagination.qml index 91bff6e7..2c9550ce 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluPagination.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluPagination.qml @@ -1,7 +1,7 @@ -import QtQuick 2.15 -import QtQuick.Controls 2.15 -import QtQuick.Layouts 1.15 -import FluentUI 1.0 +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import FluentUI Item { signal requestPage(int page, int count) @@ -37,7 +37,6 @@ Item { } Row { spacing: 5 - FluToggleButton { property int pageNumber: 1 visible: control.pageCount > 0 @@ -97,7 +96,6 @@ Item { sourceComponent: footer } } - function calcNewPage(page) { if (!page) return @@ -107,5 +105,4 @@ Item { control.pageCurrent = page_num control.requestPage(page_num, control.__itemPerPage) } - } diff --git a/src/Qt6/imports/FluentUI/Controls/FluRadioButtons.qml b/src/Qt6/imports/FluentUI/Controls/FluRadioButtons.qml index a22b0795..7bb82e69 100644 --- a/src/Qt6/imports/FluentUI/Controls/FluRadioButtons.qml +++ b/src/Qt6/imports/FluentUI/Controls/FluRadioButtons.qml @@ -4,44 +4,87 @@ import QtQuick.Controls.Basic import QtQuick.Layouts import FluentUI -ColumnLayout { - default property alias buttons: control.data +Item{ + id:control + default property list buttons property int currentIndex : -1 + property int spacing: 8 + property int orientation: Qt.Vertical property bool disabled: false property bool manuallyDisabled: false - id:control - onCurrentIndexChanged: { - for(var i = 0;i=0 && currentIndex{ - d.current = rowObject + d.current = rowModel control.closeEditor() event.accepted = true } } FluLoader{ - property var model: itemModel - property var display: itemModel.display - property int row: item_table.position.y - property int column: item_table.position.x + id: item_table_loader + property var model: item_table_mouse._model + property var display: rowModel[columnModel.dataIndex] + property var rowModel : model.rowModel + property var columnModel : model.columnModel + property int row : model.row + property int column: model.column property bool isObject: typeof(display) == "object" property var options: { if(isObject){ @@ -304,15 +367,53 @@ Rectangle { } anchors.fill: parent sourceComponent: { - if(isObject){ - return display.comId + if(item_table_mouse.visible){ + if(isObject){ + return display.comId + } + return com_text } - return com_text + return undefined } } + FluLoader{ + id: loader_edit + property var tableView: control + property var display + property int column: { + if(d.editPosition){ + return d.editPosition.column + } + return 0 + } + property int row: { + if(d.editPosition){ + return d.editPosition.row + } + return 0 + } + anchors{ + fill: parent + margins: 1 + } + signal editTextChaged(string text) + sourceComponent: { + if(item_table_mouse.visible && d.editPosition && d.editPosition.column === model.column && d.editPosition.row === model.row){ + return d.editDelegate + } + return undefined + } + onEditTextChaged: + (text)=>{ + var obj = control.getRow(row) + obj[control.columnSource[column].dataIndex] = text + control.setRow(row,obj) + } + z:999 + } Item{ anchors.fill: parent - visible: item_table.isRowSelected + visible: item_table_mouse.isRowSelected Rectangle{ width: 1 height: parent.height @@ -343,6 +444,11 @@ Rectangle { } } } + + onWidthChanged:{ + table_view.forceLayout() + } + MouseArea{ id:layout_mouse_table hoverEnabled: true @@ -364,30 +470,8 @@ Rectangle { anchors.fill: parent ScrollBar.horizontal:scroll_bar_h ScrollBar.vertical:scroll_bar_v - columnWidthProvider: function(column) { - var columnObject = d.columns_data[column] - var width = columnObject.width - if(width){ - return width - } - var minimumWidth = columnObject.minimumWidth - if(minimumWidth){ - return minimumWidth - } - return d.defaultItemWidth - } - rowHeightProvider: function(row) { - var rowObject = control.getRow(row) - var height = rowObject.height - if(height){ - return height - } - var minimumHeight = rowObject._minimumHeight - if(minimumHeight){ - return minimumHeight - } - return d.defaultItemHeight - } + columnWidthProvider: control.columnWidthProvider + rowHeightProvider: control.rowHeightProvider model: table_sort_model clip: true onRowsChanged: { @@ -395,70 +479,51 @@ Rectangle { table_view.flick(0,1) } delegate: com_table_delegate - FluLoader{ - id:loader_edit - property var tableView: control - property var display - property int column: { - if(d.editPosition){ - return d.editPosition.column - } - return 0 - } - property int row: { - if(d.editPosition){ - return d.editPosition.row - } - return 0 - } - signal editTextChaged(string text) - sourceComponent: d.editPosition ? d.editDelegate : undefined - onEditTextChaged: - (text)=>{ - var obj = control.getRow(row) - obj[d.columns_data[column].dataIndex] = text - control.setRow(row,obj) - } - width: { - if(d.editPosition){ - return d.editPosition.width - } - return 0 - } - height: { - if(d.editPosition){ - return d.editPosition.height - } - return 0 - } - x:{ - if(d.editPosition){ - return d.editPosition.x - } - return 0 - } - y:{ - if(d.editPosition){ - return d.editPosition.y - } - return 0 - } - z:999 - } } } + Component{ - id:com_column_header_delegate + id: com_column_header_delegate Rectangle{ - id:column_item_control + id: column_item_control + property var currentTableView : TableView.view readonly property real cellPadding: 8 property bool canceled: false - property int columnIndex: column - readonly property var columnObject : d.columns_data[column] - implicitWidth: { - return (item_column_loader.item && item_column_loader.item.implicitWidth) + (cellPadding * 2) + property var _model: model + readonly property var columnModel : control.columnSource[_index] + readonly property int _index : { + const isDataIndex = (element) => { + return element.dataIndex === display.dataIndex + } + return control.columnSource.findIndex(isDataIndex) + } + readonly property bool isHeaderHorizontal: TableView.view == header_horizontal + readonly property bool isHide: { + if(isHeaderHorizontal){ + return false + } + if(!isHeaderHorizontal){ + if(currentTableView.dataIndex !== columnModel.dataIndex) + return true + } + return false + } + visible: !isHide + implicitWidth: { + if(isHide){ + return Number.MIN_VALUE + } + if(column_item_control.isHeaderHorizontal){ + return (item_column_loader.item && item_column_loader.item.implicitWidth) + (cellPadding * 2) + } + return Math.max(TableView.view.width,Number.MIN_VALUE) + } + implicitHeight: { + if(column_item_control.isHeaderHorizontal){ + return Math.max(36, (item_column_loader.item&&item_column_loader.item.implicitHeight) + (cellPadding * 2)) + } + return Math.max(TableView.view.height,Number.MIN_VALUE) } - implicitHeight: Math.max(36, (item_column_loader.item&&item_column_loader.item.implicitHeight) + (cellPadding * 2)) color: FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(247/255,247/255,247/255,1) Rectangle{ border.color: control.borderColor @@ -479,7 +544,7 @@ Rectangle { width: 1 height: parent.height anchors.left: parent.left - visible: column !== 0 + visible: column_item_control._index !== 0 color:"#00000000" } Rectangle{ @@ -488,7 +553,7 @@ Rectangle { height: parent.height anchors.right: parent.right color:"#00000000" - visible: column === table_view.columns - 1 + visible: column_item_control._index === table_view.columns - 1 } MouseArea{ id:column_item_control_mouse @@ -510,22 +575,23 @@ Rectangle { } FluLoader{ id:item_column_loader - property var itemModel: model - property var modelData: model.display + property var model: column_item_control._model + property var display: model.display.title property var tableView: table_view - property var tableModel: table_model + property var sourceModel: control.sourceModel + property bool isObject: typeof(display) == "object" property var options:{ - if(typeof(modelData) == "object"){ - return modelData.options + if(isObject){ + return display.options } return {} } - property int column: column_item_control.columnIndex + property int column: column_item_control._index width: parent.width height: parent.height sourceComponent: { - if(typeof(modelData) == "object"){ - return modelData.comId + if(isObject){ + return display.comId } return com_column_text } @@ -537,7 +603,7 @@ Rectangle { anchors.right: parent.right acceptedButtons: Qt.LeftButton hoverEnabled: true - visible: !(columnObject.width === columnObject.minimumWidth && columnObject.width === columnObject.maximumWidth && columnObject.width) + visible: !columnModel.frozen && !(columnModel.width === columnModel.minimumWidth && columnModel.width === columnModel.maximumWidth && columnModel.width) cursorShape: Qt.SplitHCursor preventStealing: true onPressed : @@ -557,9 +623,9 @@ Rectangle { return } var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y) - var minimumWidth = columnObject.minimumWidth - var maximumWidth = columnObject.maximumWidth - var w = columnObject.width + var minimumWidth = columnModel.minimumWidth + var maximumWidth = columnModel.maximumWidth + var w = columnModel.width if(!w){ w = d.defaultItemWidth } @@ -569,7 +635,7 @@ Rectangle { if(!maximumWidth){ maximumWidth = 65535 } - columnObject.width = Math.min(Math.max(minimumWidth, w + delta.x),maximumWidth) + columnModel.width = Math.min(Math.max(minimumWidth, w + delta.x),maximumWidth) table_view.forceLayout() header_horizontal.forceLayout() } @@ -582,7 +648,7 @@ Rectangle { id:item_control readonly property real cellPadding: 8 property bool canceled: false - property var rowObject: control.getRow(row) + property var rowModel: control.getRow(row) implicitWidth: Math.max(30, row_text.implicitWidth + (cellPadding * 2)) implicitHeight: row_text.implicitHeight + (cellPadding * 2) color: FluTheme.dark ? Qt.rgba(50/255,50/255,50/255,1) : Qt.rgba(247/255,247/255,247/255,1) @@ -648,9 +714,9 @@ Rectangle { cursorShape: Qt.SplitVCursor preventStealing: true visible: { - if(rowObject === null) + if(rowModel === null) return false - return !(rowObject.height === rowObject._minimumHeight && rowObject.height === rowObject._maximumHeight && rowObject.height) + return !(rowModel.height === rowModel._minimumHeight && rowModel.height === rowModel._maximumHeight && rowModel.height) } onPressed : (mouse)=>{ @@ -668,11 +734,11 @@ Rectangle { if(!pressed){ return } - var rowObject = control.getRow(row) + var rowModel = control.getRow(row) var delta = Qt.point(mouse.x - clickPos.x, mouse.y - clickPos.y) - var minimumHeight = rowObject._minimumHeight - var maximumHeight = rowObject._maximumHeight - var h = rowObject.height + var minimumHeight = rowModel._minimumHeight + var maximumHeight = rowModel._maximumHeight + var h = rowModel.height if(!h){ h = d.defaultItemHeight } @@ -682,8 +748,8 @@ Rectangle { if(!maximumHeight){ maximumHeight = 65535 } - rowObject.height = Math.min(Math.max(minimumHeight, h + delta.y),maximumHeight) - control.setRow(row,rowObject) + rowModel.height = Math.min(Math.max(minimumHeight, h + delta.y),maximumHeight) + control.setRow(row,rowModel) table_view.forceLayout() } } @@ -693,7 +759,7 @@ Rectangle { id:com_column_text FluText { id: column_text - text: modelData + text: String(display) anchors.fill: parent horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter @@ -796,6 +862,138 @@ Rectangle { } } } + Item{ + anchors{ + left: header_vertical.right + top: parent.top + bottom: parent.bottom + right: parent.right + } + Component{ + id: com_table_frozen + Rectangle{ + id: item_layout_frozen + anchors.fill: parent + color: { + if(Window.active){ + return FluTheme.dark ? Qt.rgba(48/255,48/255,48/255,1) :Qt.rgba(1,1,1,1) + } + return FluTheme.dark ? Qt.rgba(56/255,56/255,56/255,1) :Qt.rgba(243/255,243/255,243/255,1) + } + visible: table_view.rows !== 0 + Rectangle{ + z:99 + anchors.fill: parent + border.color: FluTheme.dark ? Qt.rgba(26/255,26/255,26/255,0.6) : Qt.rgba(191/255,191/255,191/255,0.3) + FluShadow{ + radius: 0 + anchors.fill: parent + } + color: "#00000000" + } + TableView{ + property string dataIndex: columnModel.dataIndex + id: item_table_frozen + interactive: false + clip: true + anchors{ + left: parent.left + right: parent.right + } + contentWidth: width + height: table_view.height + y: header_horizontal.height + boundsBehavior: TableView.StopAtBounds + model: table_view.model + delegate: table_view.delegate + syncDirection: Qt.Vertical + syncView: table_view + } + TableView { + property string dataIndex: columnModel.dataIndex + id:item_table_frozen_header + model: header_column_model + boundsBehavior: Flickable.StopAtBounds + interactive: false + clip: true + contentWidth: width + anchors{ + left: parent.left + right: parent.right + top: parent.top + bottom: item_table_frozen.top + } + delegate: com_column_header_delegate + Component.onCompleted: { + item_table_frozen_header.forceLayout() + } + } + Connections{ + target: table_view + function onWidthChanged() { + item_table_frozen_header.forceLayout() + } + + } + } + } + Repeater{ + Component.onCompleted: { + model = control.columnSource + } + delegate: FluLoader{ + id: item_layout_frozen + readonly property int _index : model.index + readonly property var columnModel : control.columnSource[_index] + readonly property bool isHide:{ + if(columnModel.frozen){ + return false + } + return true + } + Connections{ + target: d + function onTableItemLayout(column){ + if(item_layout_frozen._index === column){ + updateLayout() + } + } + } + Connections{ + target: table_view + function onContentXChanged(){ + updateLayout() + } + } + function updateLayout(){ + width = table_view.columnWidthProvider(_index) + x = Qt.binding(function(){ + var minX = 0 + var maxX = table_view.width-width + for(var i=0;i<_index;i++){ + var item = control.columnSource[i] + if(item.frozen){ + minX = minX + table_view.columnWidthProvider(i) + } + } + for(i=_index+1;i=0 && rowIndexFluColorPicker - + Cancel - + OK - + Color Picker - + Edit Color - + Red - + Green - + Blue - + Opacity @@ -187,14 +187,14 @@ FluPagination - - + + <Previous - - + + Next> @@ -231,6 +231,11 @@ Reset + + + Conflict + + FluStatusLayout @@ -349,10 +354,23 @@ FluWindow - - + + Loading... + + QHotkey + + + Failed to register %1. Error: %2 + + + + + Failed to unregister %1. Error: %2 + + + diff --git a/src/fluentui_zh_CN.ts b/src/fluentui_zh_CN.ts index 4cd85268..36bc136b 100644 --- a/src/fluentui_zh_CN.ts +++ b/src/fluentui_zh_CN.ts @@ -74,49 +74,49 @@ FluColorPicker - + Cancel 取消 - + OK 确定 - + Color Picker 颜色选择器 - + Edit Color 编辑颜色 - + Red 红色 - + Green 绿色 - + Blue 蓝色 - + Opacity 透明度 @@ -187,14 +187,14 @@ FluPagination - - + + <Previous <上一页 - - + + Next> 下一页> @@ -231,6 +231,11 @@ Reset 重置 + + + Conflict + 冲突 + FluStatusLayout @@ -349,10 +354,23 @@ FluWindow - - + + Loading... 加载中... + + QHotkey + + + Failed to register %1. Error: %2 + + + + + Failed to unregister %1. Error: %2 + + + diff --git a/src/qhotkey/qhotkey.cpp b/src/qhotkey/qhotkey.cpp new file mode 100644 index 00000000..3b76d9c6 --- /dev/null +++ b/src/qhotkey/qhotkey.cpp @@ -0,0 +1,377 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" +#include +#include +#include +#include +#include + +Q_LOGGING_CATEGORY(logQHotkey, "QHotkey") + +void QHotkey::addGlobalMapping(const QKeySequence &shortcut, QHotkey::NativeShortcut nativeShortcut) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const int key = shortcut[0].toCombined(); +#else + const int key = shortcut[0]; +#endif + + QMetaObject::invokeMethod(QHotkeyPrivate::instance(), "addMappingInvoked", Qt::QueuedConnection, + Q_ARG(Qt::Key, Qt::Key(key & ~Qt::KeyboardModifierMask)), + Q_ARG(Qt::KeyboardModifiers, Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)), + Q_ARG(QHotkey::NativeShortcut, nativeShortcut)); +} + +bool QHotkey::isPlatformSupported() +{ + return QHotkeyPrivate::isPlatformSupported(); +} + +QHotkey::QHotkey(QObject *parent) : + QObject(parent), + _keyCode(Qt::Key_unknown), + _modifiers(Qt::NoModifier), + _registered(false) +{} + +QHotkey::QHotkey(const QKeySequence &shortcut, bool autoRegister, QObject *parent) : + QHotkey(parent) +{ + setShortcut(shortcut, autoRegister); +} + +QHotkey::QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister, QObject *parent) : + QHotkey(parent) +{ + setShortcut(keyCode, modifiers, autoRegister); +} + +QHotkey::QHotkey(QHotkey::NativeShortcut shortcut, bool autoRegister, QObject *parent) : + QHotkey(parent) +{ + setNativeShortcut(shortcut, autoRegister); +} + +QHotkey::~QHotkey() +{ + if(_registered) + QHotkeyPrivate::instance()->removeShortcut(this); +} + +QKeySequence QHotkey::shortcut() const +{ + if(_keyCode == Qt::Key_unknown) + return QKeySequence(); + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QKeySequence((_keyCode | _modifiers).toCombined()); +#else + return QKeySequence(static_cast(_keyCode | _modifiers)); +#endif +} + +Qt::Key QHotkey::keyCode() const +{ + return _keyCode; +} + +Qt::KeyboardModifiers QHotkey::modifiers() const +{ + return _modifiers; +} + +QHotkey::NativeShortcut QHotkey::currentNativeShortcut() const +{ + return _nativeShortcut; +} + +bool QHotkey::isRegistered() const +{ + return _registered; +} + +bool QHotkey::setShortcut(const QKeySequence &shortcut, bool autoRegister) +{ + if(shortcut.isEmpty()) + return resetShortcut(); + if(shortcut.count() > 1) { + qCWarning(logQHotkey, "Keysequences with multiple shortcuts are not allowed! " + "Only the first shortcut will be used!"); + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + const int key = shortcut[0].toCombined(); +#else + const int key = shortcut[0]; +#endif + + return setShortcut(Qt::Key(key & ~Qt::KeyboardModifierMask), + Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask), + autoRegister); +} + +bool QHotkey::setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister) +{ + if(_registered) { + if(autoRegister) { + if(!QHotkeyPrivate::instance()->removeShortcut(this)) + return false; + } else + return false; + } + + if(keyCode == Qt::Key_unknown) { + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return true; + } + + _keyCode = keyCode; + _modifiers = modifiers; + _nativeShortcut = QHotkeyPrivate::instance()->nativeShortcut(keyCode, modifiers); + if(_nativeShortcut.isValid()) { + if(autoRegister) + return QHotkeyPrivate::instance()->addShortcut(this); + return true; + } + + qCWarning(logQHotkey) << "Unable to map shortcut to native keys. Key:" << keyCode << "Modifiers:" << modifiers; + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return false; +} + +bool QHotkey::resetShortcut() +{ + if(_registered && + !QHotkeyPrivate::instance()->removeShortcut(this)) { + return false; + } + + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return true; +} + +bool QHotkey::setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister) +{ + if(_registered) { + if(autoRegister) { + if(!QHotkeyPrivate::instance()->removeShortcut(this)) + return false; + } else + return false; + } + + if(nativeShortcut.isValid()) { + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = nativeShortcut; + if(autoRegister) + return QHotkeyPrivate::instance()->addShortcut(this); + return true; + } + + _keyCode = Qt::Key_unknown; + _modifiers = Qt::NoModifier; + _nativeShortcut = NativeShortcut(); + return true; +} + +bool QHotkey::setRegistered(bool registered) +{ + if(_registered && !registered) + return QHotkeyPrivate::instance()->removeShortcut(this); + if(!_registered && registered) { + if(!_nativeShortcut.isValid()) + return false; + return QHotkeyPrivate::instance()->addShortcut(this); + } + return true; +} + + + +// ---------- QHotkeyPrivate implementation ---------- + +QHotkeyPrivate::QHotkeyPrivate() +{ + Q_ASSERT_X(qApp, Q_FUNC_INFO, "QHotkey requires QCoreApplication to be instantiated"); + qApp->eventDispatcher()->installNativeEventFilter(this); +} + +QHotkeyPrivate::~QHotkeyPrivate() +{ + if(!shortcuts.isEmpty()) + qCWarning(logQHotkey) << "QHotkeyPrivate destroyed with registered shortcuts!"; + if(qApp && qApp->eventDispatcher()) + qApp->eventDispatcher()->removeNativeEventFilter(this); +} + +QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers) +{ + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? + Qt::DirectConnection : + Qt::BlockingQueuedConnection); + QHotkey::NativeShortcut res; + if(!QMetaObject::invokeMethod(this, "nativeShortcutInvoked", conType, + Q_RETURN_ARG(QHotkey::NativeShortcut, res), + Q_ARG(Qt::Key, keycode), + Q_ARG(Qt::KeyboardModifiers, modifiers))) { + return QHotkey::NativeShortcut(); + } + return res; +} + +bool QHotkeyPrivate::addShortcut(QHotkey *hotkey) +{ + if(hotkey->_registered) + return false; + + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? + Qt::DirectConnection : + Qt::BlockingQueuedConnection); + bool res = false; + if(!QMetaObject::invokeMethod(this, "addShortcutInvoked", conType, + Q_RETURN_ARG(bool, res), + Q_ARG(QHotkey*, hotkey))) { + return false; + } + + if(res) + emit hotkey->registeredChanged(true); + return res; +} + +bool QHotkeyPrivate::removeShortcut(QHotkey *hotkey) +{ + if(!hotkey->_registered) + return false; + + Qt::ConnectionType conType = (QThread::currentThread() == thread() ? + Qt::DirectConnection : + Qt::BlockingQueuedConnection); + bool res = false; + if(!QMetaObject::invokeMethod(this, "removeShortcutInvoked", conType, + Q_RETURN_ARG(bool, res), + Q_ARG(QHotkey*, hotkey))) { + return false; + } + + if(res) + emit hotkey->registeredChanged(false); + return res; +} + +void QHotkeyPrivate::activateShortcut(QHotkey::NativeShortcut shortcut) +{ + QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::activated); + for(QHotkey *hkey : shortcuts.values(shortcut)) + signal.invoke(hkey, Qt::QueuedConnection); +} + +void QHotkeyPrivate::releaseShortcut(QHotkey::NativeShortcut shortcut) +{ + QMetaMethod signal = QMetaMethod::fromSignal(&QHotkey::released); + for(QHotkey *hkey : shortcuts.values(shortcut)) + signal.invoke(hkey, Qt::QueuedConnection); +} + +void QHotkeyPrivate::addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, QHotkey::NativeShortcut nativeShortcut) +{ + mapping.insert({keycode, modifiers}, nativeShortcut); +} + +bool QHotkeyPrivate::addShortcutInvoked(QHotkey *hotkey) +{ + QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; + + if(!shortcuts.contains(shortcut)) { + if(!registerShortcut(shortcut)) { + qCWarning(logQHotkey) << QHotkey::tr("Failed to register %1. Error: %2").arg(hotkey->shortcut().toString(), error); + return false; + } + } + + shortcuts.insert(shortcut, hotkey); + hotkey->_registered = true; + return true; +} + +bool QHotkeyPrivate::removeShortcutInvoked(QHotkey *hotkey) +{ + QHotkey::NativeShortcut shortcut = hotkey->_nativeShortcut; + + if(shortcuts.remove(shortcut, hotkey) == 0) + return false; + hotkey->_registered = false; + emit hotkey->registeredChanged(true); + if(shortcuts.count(shortcut) == 0) { + if (!unregisterShortcut(shortcut)) { + qCWarning(logQHotkey) << QHotkey::tr("Failed to unregister %1. Error: %2").arg(hotkey->shortcut().toString(), error); + return false; + } + return true; + } + return true; +} + +QHotkey::NativeShortcut QHotkeyPrivate::nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers) +{ + if(mapping.contains({keycode, modifiers})) + return mapping.value({keycode, modifiers}); + + bool ok1 = false; + auto k = nativeKeycode(keycode, ok1); + bool ok2 = false; + auto m = nativeModifiers(modifiers, ok2); + if(ok1 && ok2) + return {k, m}; + return {}; +} + + + +QHotkey::NativeShortcut::NativeShortcut() : + key(), + modifier(), + valid(false) +{} + +QHotkey::NativeShortcut::NativeShortcut(quint32 key, quint32 modifier) : + key(key), + modifier(modifier), + valid(true) +{} + +bool QHotkey::NativeShortcut::isValid() const +{ + return valid; +} + +bool QHotkey::NativeShortcut::operator ==(QHotkey::NativeShortcut other) const +{ + return (key == other.key) && + (modifier == other.modifier) && + valid == other.valid; +} + +bool QHotkey::NativeShortcut::operator !=(QHotkey::NativeShortcut other) const +{ + return (key != other.key) || + (modifier != other.modifier) || + valid != other.valid; +} + +QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key) +{ + return qHash(key.key) ^ qHash(key.modifier); +} + +QHOTKEY_HASH_SEED qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed) +{ + return qHash(key.key, seed) ^ qHash(key.modifier, seed); +} diff --git a/src/qhotkey/qhotkey.h b/src/qhotkey/qhotkey.h new file mode 100644 index 00000000..3697c8e3 --- /dev/null +++ b/src/qhotkey/qhotkey.h @@ -0,0 +1,130 @@ +#ifndef QHOTKEY_H +#define QHOTKEY_H + +#include +#include +#include +#include + +#ifdef QHOTKEY_SHARED +# ifdef QHOTKEY_LIBRARY +# define QHOTKEY_EXPORT Q_DECL_EXPORT +# else +# define QHOTKEY_EXPORT Q_DECL_IMPORT +# endif +#else +# define QHOTKEY_EXPORT +#endif + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + #define QHOTKEY_HASH_SEED size_t +#else + #define QHOTKEY_HASH_SEED uint +#endif + +//! A class to define global, systemwide Hotkeys +class QHOTKEY_EXPORT QHotkey : public QObject +{ + Q_OBJECT + //! @private + friend class QHotkeyPrivate; + + //! Specifies whether this hotkey is currently registered or not + Q_PROPERTY(bool registered READ isRegistered WRITE setRegistered NOTIFY registeredChanged) + //! Holds the shortcut this hotkey will be triggered on + Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut RESET resetShortcut) + +public: + //! Defines shortcut with native keycodes + class QHOTKEY_EXPORT NativeShortcut { + public: + //! The native keycode + quint32 key; + //! The native modifiers + quint32 modifier; + + //! Creates an invalid native shortcut + NativeShortcut(); + //! Creates a valid native shortcut, with the given key and modifiers + NativeShortcut(quint32 key, quint32 modifier = 0); + + //! Checks, whether this shortcut is valid or not + bool isValid() const; + + //! Equality operator + bool operator ==(NativeShortcut other) const; + //! Inequality operator + bool operator !=(NativeShortcut other) const; + + private: + bool valid; + }; + + //! Adds a global mapping of a key sequence to a replacement native shortcut + static void addGlobalMapping(const QKeySequence &shortcut, NativeShortcut nativeShortcut); + + //! Checks if global shortcuts are supported by the current platform + static bool isPlatformSupported(); + + //! Default Constructor + explicit QHotkey(QObject *parent = nullptr); + //! Constructs a hotkey with a shortcut and optionally registers it + explicit QHotkey(const QKeySequence &shortcut, bool autoRegister = false, QObject *parent = nullptr); + //! Constructs a hotkey with a key and modifiers and optionally registers it + explicit QHotkey(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister = false, QObject *parent = nullptr); + //! Constructs a hotkey from a native shortcut and optionally registers it + explicit QHotkey(NativeShortcut shortcut, bool autoRegister = false, QObject *parent = nullptr); + ~QHotkey() override; + + //! @readAcFn{QHotkey::registered} + bool isRegistered() const; + //! @readAcFn{QHotkey::shortcut} + QKeySequence shortcut() const; + //! @readAcFn{QHotkey::shortcut} - the key only + Qt::Key keyCode() const; + //! @readAcFn{QHotkey::shortcut} - the modifiers only + Qt::KeyboardModifiers modifiers() const; + + //! Get the current native shortcut + NativeShortcut currentNativeShortcut() const; + +public slots: + //! @writeAcFn{QHotkey::registered} + bool setRegistered(bool registered); + + //! @writeAcFn{QHotkey::shortcut} + bool setShortcut(const QKeySequence &shortcut, bool autoRegister = false); + //! @writeAcFn{QHotkey::shortcut} + bool setShortcut(Qt::Key keyCode, Qt::KeyboardModifiers modifiers, bool autoRegister = false); + //! @resetAcFn{QHotkey::shortcut} + bool resetShortcut(); + + //! Set this hotkey to a native shortcut + bool setNativeShortcut(QHotkey::NativeShortcut nativeShortcut, bool autoRegister = false); + +signals: + //! Will be emitted if the shortcut is pressed + void activated(QPrivateSignal); + + //! Will be emitted if the shortcut press is released + void released(QPrivateSignal); + + //! @notifyAcFn{QHotkey::registered} + void registeredChanged(bool registered); + +private: + Qt::Key _keyCode; + Qt::KeyboardModifiers _modifiers; + + NativeShortcut _nativeShortcut; + bool _registered; +}; + +QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key); +QHOTKEY_HASH_SEED QHOTKEY_EXPORT qHash(QHotkey::NativeShortcut key, QHOTKEY_HASH_SEED seed); + +QHOTKEY_EXPORT Q_DECLARE_LOGGING_CATEGORY(logQHotkey) + +Q_DECLARE_METATYPE(QHotkey::NativeShortcut) + +#endif // QHOTKEY_H diff --git a/src/qhotkey/qhotkey_mac.cpp b/src/qhotkey/qhotkey_mac.cpp new file mode 100644 index 00000000..799f5153 --- /dev/null +++ b/src/qhotkey/qhotkey_mac.cpp @@ -0,0 +1,291 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" +#include +#include + +class QHotkeyPrivateMac : public QHotkeyPrivate +{ +public: + // QAbstractNativeEventFilter interface + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; + + static OSStatus hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); + static OSStatus hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data); + +protected: + // QHotkeyPrivate interface + quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; + quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; + bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + +private: + static bool isHotkeyHandlerRegistered; + static QHash hotkeyRefs; +}; +NATIVE_INSTANCE(QHotkeyPrivateMac) + +bool QHotkeyPrivate::isPlatformSupported() +{ + return true; +} + +bool QHotkeyPrivateMac::isHotkeyHandlerRegistered = false; +QHash QHotkeyPrivateMac::hotkeyRefs; + +bool QHotkeyPrivateMac::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(message) + Q_UNUSED(result) + return false; +} + +quint32 QHotkeyPrivateMac::nativeKeycode(Qt::Key keycode, bool &ok) +{ + // Constants found in NSEvent.h from AppKit.framework + ok = true; + switch (keycode) { + case Qt::Key_Return: + return kVK_Return; + case Qt::Key_Enter: + return kVK_ANSI_KeypadEnter; + case Qt::Key_Tab: + return kVK_Tab; + case Qt::Key_Space: + return kVK_Space; + case Qt::Key_Backspace: + return kVK_Delete; + case Qt::Key_Escape: + return kVK_Escape; + case Qt::Key_CapsLock: + return kVK_CapsLock; + case Qt::Key_Option: + return kVK_Option; + case Qt::Key_F17: + return kVK_F17; + case Qt::Key_VolumeUp: + return kVK_VolumeUp; + case Qt::Key_VolumeDown: + return kVK_VolumeDown; + case Qt::Key_F18: + return kVK_F18; + case Qt::Key_F19: + return kVK_F19; + case Qt::Key_F20: + return kVK_F20; + case Qt::Key_F5: + return kVK_F5; + case Qt::Key_F6: + return kVK_F6; + case Qt::Key_F7: + return kVK_F7; + case Qt::Key_F3: + return kVK_F3; + case Qt::Key_F8: + return kVK_F8; + case Qt::Key_F9: + return kVK_F9; + case Qt::Key_F11: + return kVK_F11; + case Qt::Key_F13: + return kVK_F13; + case Qt::Key_F16: + return kVK_F16; + case Qt::Key_F14: + return kVK_F14; + case Qt::Key_F10: + return kVK_F10; + case Qt::Key_F12: + return kVK_F12; + case Qt::Key_F15: + return kVK_F15; + case Qt::Key_Help: + return kVK_Help; + case Qt::Key_Home: + return kVK_Home; + case Qt::Key_PageUp: + return kVK_PageUp; + case Qt::Key_Delete: + return kVK_ForwardDelete; + case Qt::Key_F4: + return kVK_F4; + case Qt::Key_End: + return kVK_End; + case Qt::Key_F2: + return kVK_F2; + case Qt::Key_PageDown: + return kVK_PageDown; + case Qt::Key_F1: + return kVK_F1; + case Qt::Key_Left: + return kVK_LeftArrow; + case Qt::Key_Right: + return kVK_RightArrow; + case Qt::Key_Down: + return kVK_DownArrow; + case Qt::Key_Up: + return kVK_UpArrow; + default: + ok = false; + break; + } + + UTF16Char ch = keycode; + + CFDataRef currentLayoutData; + TISInputSourceRef currentKeyboard = TISCopyCurrentASCIICapableKeyboardLayoutInputSource(); + + if (currentKeyboard == NULL) + return 0; + + currentLayoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData); + CFRelease(currentKeyboard); + if (currentLayoutData == NULL) + return 0; + + UCKeyboardLayout* header = (UCKeyboardLayout*)CFDataGetBytePtr(currentLayoutData); + UCKeyboardTypeHeader* table = header->keyboardTypeList; + + uint8_t *data = (uint8_t*)header; + for (quint32 i=0; i < header->keyboardTypeCount; i++) { + UCKeyStateRecordsIndex* stateRec = 0; + if (table[i].keyStateRecordsIndexOffset != 0) { + stateRec = reinterpret_cast(data + table[i].keyStateRecordsIndexOffset); + if (stateRec->keyStateRecordsIndexFormat != kUCKeyStateRecordsIndexFormat) stateRec = 0; + } + + UCKeyToCharTableIndex* charTable = reinterpret_cast(data + table[i].keyToCharTableIndexOffset); + if (charTable->keyToCharTableIndexFormat != kUCKeyToCharTableIndexFormat) continue; + + for (quint32 j=0; j < charTable->keyToCharTableCount; j++) { + UCKeyOutput* keyToChar = reinterpret_cast(data + charTable->keyToCharTableOffsets[j]); + for (quint32 k=0; k < charTable->keyToCharTableSize; k++) { + if (keyToChar[k] & kUCKeyOutputTestForIndexMask) { + long idx = keyToChar[k] & kUCKeyOutputGetIndexMask; + if (stateRec && idx < stateRec->keyStateRecordCount) { + UCKeyStateRecord* rec = reinterpret_cast(data + stateRec->keyStateRecordOffsets[idx]); + if (rec->stateZeroCharData == ch) { + ok = true; + return k; + } + } + } + else if (!(keyToChar[k] & kUCKeyOutputSequenceIndexMask) && keyToChar[k] < 0xFFFE) { + if (keyToChar[k] == ch) { + ok = true; + return k; + } + } + } + } + } + return 0; +} + +quint32 QHotkeyPrivateMac::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) +{ + quint32 nMods = 0; + if (modifiers & Qt::ShiftModifier) + nMods |= shiftKey; + if (modifiers & Qt::ControlModifier) + nMods |= cmdKey; + if (modifiers & Qt::AltModifier) + nMods |= optionKey; + if (modifiers & Qt::MetaModifier) + nMods |= controlKey; + if (modifiers & Qt::KeypadModifier) + nMods |= kEventKeyModifierNumLockMask; + ok = true; + return nMods; +} + +bool QHotkeyPrivateMac::registerShortcut(QHotkey::NativeShortcut shortcut) +{ + if (!this->isHotkeyHandlerRegistered) + { + EventTypeSpec pressEventSpec; + pressEventSpec.eventClass = kEventClassKeyboard; + pressEventSpec.eventKind = kEventHotKeyPressed; + InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyPressEventHandler, 1, &pressEventSpec, NULL, NULL); + + EventTypeSpec releaseEventSpec; + releaseEventSpec.eventClass = kEventClassKeyboard; + releaseEventSpec.eventKind = kEventHotKeyReleased; + InstallApplicationEventHandler(&QHotkeyPrivateMac::hotkeyReleaseEventHandler, 1, &releaseEventSpec, NULL, NULL); + } + + EventHotKeyID hkeyID; + hkeyID.signature = shortcut.key; + hkeyID.id = shortcut.modifier; + + EventHotKeyRef eventRef = 0; + OSStatus status = RegisterEventHotKey(shortcut.key, + shortcut.modifier, + hkeyID, + GetApplicationEventTarget(), + 0, + &eventRef); + if (status != noErr) { + error = QString::number(status); + return false; + } else { + this->hotkeyRefs.insert(shortcut, eventRef); + return true; + } +} + +bool QHotkeyPrivateMac::unregisterShortcut(QHotkey::NativeShortcut shortcut) +{ + EventHotKeyRef eventRef = QHotkeyPrivateMac::hotkeyRefs.value(shortcut); + OSStatus status = UnregisterEventHotKey(eventRef); + if (status != noErr) { + error = QString::number(status); + return false; + } else { + this->hotkeyRefs.remove(shortcut); + return true; + } +} + +OSStatus QHotkeyPrivateMac::hotkeyPressEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data) +{ + Q_UNUSED(nextHandler); + Q_UNUSED(data); + + if (GetEventClass(event) == kEventClassKeyboard && + GetEventKind(event) == kEventHotKeyPressed) { + EventHotKeyID hkeyID; + GetEventParameter(event, + kEventParamDirectObject, + typeEventHotKeyID, + NULL, + sizeof(EventHotKeyID), + NULL, + &hkeyID); + hotkeyPrivate->activateShortcut({hkeyID.signature, hkeyID.id}); + } + + return noErr; +} + +OSStatus QHotkeyPrivateMac::hotkeyReleaseEventHandler(EventHandlerCallRef nextHandler, EventRef event, void* data) +{ + Q_UNUSED(nextHandler); + Q_UNUSED(data); + + if (GetEventClass(event) == kEventClassKeyboard && + GetEventKind(event) == kEventHotKeyReleased) { + EventHotKeyID hkeyID; + GetEventParameter(event, + kEventParamDirectObject, + typeEventHotKeyID, + NULL, + sizeof(EventHotKeyID), + NULL, + &hkeyID); + hotkeyPrivate->releaseShortcut({hkeyID.signature, hkeyID.id}); + } + + return noErr; +} diff --git a/src/qhotkey/qhotkey_p.h b/src/qhotkey/qhotkey_p.h new file mode 100644 index 00000000..8bc5ab64 --- /dev/null +++ b/src/qhotkey/qhotkey_p.h @@ -0,0 +1,62 @@ +#ifndef QHOTKEY_P_H +#define QHOTKEY_P_H + +#include "qhotkey.h" +#include +#include +#include +#include + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + #define _NATIVE_EVENT_RESULT qintptr +#else + #define _NATIVE_EVENT_RESULT long +#endif + +class QHOTKEY_EXPORT QHotkeyPrivate : public QObject, public QAbstractNativeEventFilter +{ + Q_OBJECT + +public: + QHotkeyPrivate();//singleton!!! + ~QHotkeyPrivate(); + + static QHotkeyPrivate *instance(); + static bool isPlatformSupported(); + + QHotkey::NativeShortcut nativeShortcut(Qt::Key keycode, Qt::KeyboardModifiers modifiers); + + bool addShortcut(QHotkey *hotkey); + bool removeShortcut(QHotkey *hotkey); + +protected: + void activateShortcut(QHotkey::NativeShortcut shortcut); + void releaseShortcut(QHotkey::NativeShortcut shortcut); + + virtual quint32 nativeKeycode(Qt::Key keycode, bool &ok) = 0;//platform implement + virtual quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) = 0;//platform implement + + virtual bool registerShortcut(QHotkey::NativeShortcut shortcut) = 0;//platform implement + virtual bool unregisterShortcut(QHotkey::NativeShortcut shortcut) = 0;//platform implement + + QString error; + +private: + QHash, QHotkey::NativeShortcut> mapping; + QMultiHash shortcuts; + + Q_INVOKABLE void addMappingInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers, QHotkey::NativeShortcut nativeShortcut); + Q_INVOKABLE bool addShortcutInvoked(QHotkey *hotkey); + Q_INVOKABLE bool removeShortcutInvoked(QHotkey *hotkey); + Q_INVOKABLE QHotkey::NativeShortcut nativeShortcutInvoked(Qt::Key keycode, Qt::KeyboardModifiers modifiers); +}; + +#define NATIVE_INSTANCE(ClassName) \ + Q_GLOBAL_STATIC(ClassName, hotkeyPrivate) \ + \ + QHotkeyPrivate *QHotkeyPrivate::instance()\ + {\ + return hotkeyPrivate;\ + } + +#endif // QHOTKEY_P_H diff --git a/src/qhotkey/qhotkey_win.cpp b/src/qhotkey/qhotkey_win.cpp new file mode 100644 index 00000000..949d87a6 --- /dev/null +++ b/src/qhotkey/qhotkey_win.cpp @@ -0,0 +1,309 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" +#include +#include +#include +#include +#include + +#define HKEY_ID(nativeShortcut) (((nativeShortcut.key ^ (nativeShortcut.modifier << 8)) & 0x0FFF) | 0x7000) + +#if !defined(MOD_NOREPEAT) +#define MOD_NOREPEAT 0x4000 +#endif + +class QHotkeyPrivateWin : public QHotkeyPrivate +{ +public: + QHotkeyPrivateWin(); + // QAbstractNativeEventFilter interface + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; + +protected: + void pollForHotkeyRelease(); + // QHotkeyPrivate interface + quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; + quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; + bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + +private: + static QString formatWinError(DWORD winError); + QTimer pollTimer; + QList polledShortcuts; +}; +NATIVE_INSTANCE(QHotkeyPrivateWin) + +QHotkeyPrivateWin::QHotkeyPrivateWin(){ + pollTimer.setInterval(50); + connect(&pollTimer, &QTimer::timeout, this, &QHotkeyPrivateWin::pollForHotkeyRelease); +} + +bool QHotkeyPrivate::isPlatformSupported() +{ + return true; +} + +bool QHotkeyPrivateWin::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(result) + + MSG* msg = static_cast(message); + if(msg->message == WM_HOTKEY) { + QHotkey::NativeShortcut shortcut = {HIWORD(msg->lParam), LOWORD(msg->lParam)}; + this->activateShortcut(shortcut); + if (this->polledShortcuts.empty()) + this->pollTimer.start(); + this->polledShortcuts.append(shortcut); + } + + return false; +} + +void QHotkeyPrivateWin::pollForHotkeyRelease() +{ + auto it = std::remove_if(this->polledShortcuts.begin(), this->polledShortcuts.end(), [this](const QHotkey::NativeShortcut &shortcut) { + bool pressed = (GetAsyncKeyState(shortcut.key) & (1 << 15)) != 0; + if (!pressed) + this->releaseShortcut(shortcut); + return !pressed; + }); + this->polledShortcuts.erase(it, this->polledShortcuts.end()); + if (this->polledShortcuts.empty()) + this->pollTimer.stop(); +} + +quint32 QHotkeyPrivateWin::nativeKeycode(Qt::Key keycode, bool &ok) +{ + ok = true; + if(keycode <= 0xFFFF) {//Try to obtain the key from it's "character" + const SHORT vKey = VkKeyScanW(static_cast(keycode)); + if(vKey > -1) + return LOBYTE(vKey); + } + + //find key from switch/case --> Only finds a very small subset of keys + switch (keycode) + { + case Qt::Key_Escape: + return VK_ESCAPE; + case Qt::Key_Tab: + case Qt::Key_Backtab: + return VK_TAB; + case Qt::Key_Backspace: + return VK_BACK; + case Qt::Key_Return: + case Qt::Key_Enter: + return VK_RETURN; + case Qt::Key_Insert: + return VK_INSERT; + case Qt::Key_Delete: + return VK_DELETE; + case Qt::Key_Pause: + return VK_PAUSE; + case Qt::Key_Print: + return VK_PRINT; + case Qt::Key_Clear: + return VK_CLEAR; + case Qt::Key_Home: + return VK_HOME; + case Qt::Key_End: + return VK_END; + case Qt::Key_Left: + return VK_LEFT; + case Qt::Key_Up: + return VK_UP; + case Qt::Key_Right: + return VK_RIGHT; + case Qt::Key_Down: + return VK_DOWN; + case Qt::Key_PageUp: + return VK_PRIOR; + case Qt::Key_PageDown: + return VK_NEXT; + case Qt::Key_CapsLock: + return VK_CAPITAL; + case Qt::Key_NumLock: + return VK_NUMLOCK; + case Qt::Key_ScrollLock: + return VK_SCROLL; + + case Qt::Key_F1: + return VK_F1; + case Qt::Key_F2: + return VK_F2; + case Qt::Key_F3: + return VK_F3; + case Qt::Key_F4: + return VK_F4; + case Qt::Key_F5: + return VK_F5; + case Qt::Key_F6: + return VK_F6; + case Qt::Key_F7: + return VK_F7; + case Qt::Key_F8: + return VK_F8; + case Qt::Key_F9: + return VK_F9; + case Qt::Key_F10: + return VK_F10; + case Qt::Key_F11: + return VK_F11; + case Qt::Key_F12: + return VK_F12; + case Qt::Key_F13: + return VK_F13; + case Qt::Key_F14: + return VK_F14; + case Qt::Key_F15: + return VK_F15; + case Qt::Key_F16: + return VK_F16; + case Qt::Key_F17: + return VK_F17; + case Qt::Key_F18: + return VK_F18; + case Qt::Key_F19: + return VK_F19; + case Qt::Key_F20: + return VK_F20; + case Qt::Key_F21: + return VK_F21; + case Qt::Key_F22: + return VK_F22; + case Qt::Key_F23: + return VK_F23; + case Qt::Key_F24: + return VK_F24; + + case Qt::Key_Menu: + return VK_APPS; + case Qt::Key_Help: + return VK_HELP; + case Qt::Key_MediaNext: + return VK_MEDIA_NEXT_TRACK; + case Qt::Key_MediaPrevious: + return VK_MEDIA_PREV_TRACK; + case Qt::Key_MediaPlay: + return VK_MEDIA_PLAY_PAUSE; + case Qt::Key_MediaStop: + return VK_MEDIA_STOP; + case Qt::Key_VolumeDown: + return VK_VOLUME_DOWN; + case Qt::Key_VolumeUp: + return VK_VOLUME_UP; + case Qt::Key_VolumeMute: + return VK_VOLUME_MUTE; + case Qt::Key_Mode_switch: + return VK_MODECHANGE; + case Qt::Key_Select: + return VK_SELECT; + case Qt::Key_Printer: + return VK_PRINT; + case Qt::Key_Execute: + return VK_EXECUTE; + case Qt::Key_Sleep: + return VK_SLEEP; + case Qt::Key_Period: + return VK_DECIMAL; + case Qt::Key_Play: + return VK_PLAY; + case Qt::Key_Cancel: + return VK_CANCEL; + + case Qt::Key_Forward: + return VK_BROWSER_FORWARD; + case Qt::Key_Refresh: + return VK_BROWSER_REFRESH; + case Qt::Key_Stop: + return VK_BROWSER_STOP; + case Qt::Key_Search: + return VK_BROWSER_SEARCH; + case Qt::Key_Favorites: + return VK_BROWSER_FAVORITES; + case Qt::Key_HomePage: + return VK_BROWSER_HOME; + + case Qt::Key_LaunchMail: + return VK_LAUNCH_MAIL; + case Qt::Key_LaunchMedia: + return VK_LAUNCH_MEDIA_SELECT; + case Qt::Key_Launch0: + return VK_LAUNCH_APP1; + case Qt::Key_Launch1: + return VK_LAUNCH_APP2; + + case Qt::Key_Massyo: + return VK_OEM_FJ_MASSHOU; + case Qt::Key_Touroku: + return VK_OEM_FJ_TOUROKU; + + default: + if(keycode <= 0xFFFF) + return static_cast(keycode); + else { + ok = false; + return 0; + } + } +} + +quint32 QHotkeyPrivateWin::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) +{ + quint32 nMods = 0; + if (modifiers & Qt::ShiftModifier) + nMods |= MOD_SHIFT; + if (modifiers & Qt::ControlModifier) + nMods |= MOD_CONTROL; + if (modifiers & Qt::AltModifier) + nMods |= MOD_ALT; + if (modifiers & Qt::MetaModifier) + nMods |= MOD_WIN; + ok = true; + return nMods; +} + +bool QHotkeyPrivateWin::registerShortcut(QHotkey::NativeShortcut shortcut) +{ + BOOL ok = RegisterHotKey(NULL, + HKEY_ID(shortcut), + shortcut.modifier + MOD_NOREPEAT, + shortcut.key); + if(ok) + return true; + else { + error = QHotkeyPrivateWin::formatWinError(::GetLastError()); + return false; + } +} + +bool QHotkeyPrivateWin::unregisterShortcut(QHotkey::NativeShortcut shortcut) +{ + BOOL ok = UnregisterHotKey(NULL, HKEY_ID(shortcut)); + if(ok) + return true; + else { + error = QHotkeyPrivateWin::formatWinError(::GetLastError()); + return false; + } +} + +QString QHotkeyPrivateWin::formatWinError(DWORD winError) +{ + wchar_t *buffer = NULL; + DWORD num = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + winError, + 0, + (LPWSTR)&buffer, + 0, + NULL); + if(buffer) { + QString res = QString::fromWCharArray(buffer, num); + LocalFree(buffer); + return res; + } else + return QString(); +} diff --git a/src/qhotkey/qhotkey_x11.cpp b/src/qhotkey/qhotkey_x11.cpp new file mode 100644 index 00000000..d3ac1d15 --- /dev/null +++ b/src/qhotkey/qhotkey_x11.cpp @@ -0,0 +1,268 @@ +#include "qhotkey.h" +#include "qhotkey_p.h" + +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + #include +#else + #include + #include +#endif + +#include +#include +#include +#include + +//compatibility to pre Qt 5.8 +#ifndef Q_FALLTHROUGH +#define Q_FALLTHROUGH() (void)0 +#endif + +class QHotkeyPrivateX11 : public QHotkeyPrivate +{ +public: + // QAbstractNativeEventFilter interface + bool nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) override; + +protected: + // QHotkeyPrivate interface + quint32 nativeKeycode(Qt::Key keycode, bool &ok) Q_DECL_OVERRIDE; + quint32 nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) Q_DECL_OVERRIDE; + static QString getX11String(Qt::Key keycode); + bool registerShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + bool unregisterShortcut(QHotkey::NativeShortcut shortcut) Q_DECL_OVERRIDE; + +private: + static const QVector specialModifiers; + static const quint32 validModsMask; + xcb_key_press_event_t prevHandledEvent; + xcb_key_press_event_t prevEvent; + + static QString formatX11Error(Display *display, int errorCode); + + class HotkeyErrorHandler { + public: + HotkeyErrorHandler(); + ~HotkeyErrorHandler(); + + static bool hasError; + static QString errorString; + + private: + XErrorHandler prevHandler; + + static int handleError(Display *display, XErrorEvent *error); + }; +}; +NATIVE_INSTANCE(QHotkeyPrivateX11) + +bool QHotkeyPrivate::isPlatformSupported() +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + return qGuiApp->nativeInterface(); +#else + return QX11Info::isPlatformX11(); +#endif +} + +const QVector QHotkeyPrivateX11::specialModifiers = {0, Mod2Mask, LockMask, (Mod2Mask | LockMask)}; +const quint32 QHotkeyPrivateX11::validModsMask = ShiftMask | ControlMask | Mod1Mask | Mod4Mask; + +bool QHotkeyPrivateX11::nativeEventFilter(const QByteArray &eventType, void *message, _NATIVE_EVENT_RESULT *result) +{ + Q_UNUSED(eventType) + Q_UNUSED(result) + + auto *genericEvent = static_cast(message); + if (genericEvent->response_type == XCB_KEY_PRESS) { + xcb_key_press_event_t keyEvent = *static_cast(message); + this->prevEvent = keyEvent; + if (this->prevHandledEvent.response_type == XCB_KEY_RELEASE) { + if(this->prevHandledEvent.time == keyEvent.time) return false; + } + this->activateShortcut({keyEvent.detail, keyEvent.state & QHotkeyPrivateX11::validModsMask}); + } else if (genericEvent->response_type == XCB_KEY_RELEASE) { + xcb_key_release_event_t keyEvent = *static_cast(message); + this->prevEvent = keyEvent; + QTimer::singleShot(50, [this, keyEvent] { + if(this->prevEvent.time == keyEvent.time && this->prevEvent.response_type == keyEvent.response_type && this->prevEvent.detail == keyEvent.detail){ + this->releaseShortcut({keyEvent.detail, keyEvent.state & QHotkeyPrivateX11::validModsMask}); + } + }); + this->prevHandledEvent = keyEvent; + } + + return false; +} + +QString QHotkeyPrivateX11::getX11String(Qt::Key keycode) +{ + switch(keycode){ + + case Qt::Key_MediaLast : + case Qt::Key_MediaPrevious : + return QStringLiteral("XF86AudioPrev"); + case Qt::Key_MediaNext : + return QStringLiteral("XF86AudioNext"); + case Qt::Key_MediaPause : + case Qt::Key_MediaPlay : + case Qt::Key_MediaTogglePlayPause : + return QStringLiteral("XF86AudioPlay"); + case Qt::Key_MediaRecord : + return QStringLiteral("XF86AudioRecord"); + case Qt::Key_MediaStop : + return QStringLiteral("XF86AudioStop"); + default : + return QKeySequence(keycode).toString(QKeySequence::NativeText); + } +} + +quint32 QHotkeyPrivateX11::nativeKeycode(Qt::Key keycode, bool &ok) +{ + QString keyString = getX11String(keycode); + + KeySym keysym = XStringToKeysym(keyString.toLatin1().constData()); + if (keysym == NoSymbol) { + //not found -> just use the key + if(keycode <= 0xFFFF) + keysym = keycode; + else + return 0; + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface(); + Display *display = x11Interface->display(); +#else + const bool x11Interface = QX11Info::isPlatformX11(); + Display *display = QX11Info::display(); +#endif + + if(x11Interface) { + auto res = XKeysymToKeycode(display, keysym); + if(res != 0) + ok = true; + return res; + } + return 0; +} + +quint32 QHotkeyPrivateX11::nativeModifiers(Qt::KeyboardModifiers modifiers, bool &ok) +{ + quint32 nMods = 0; + if (modifiers & Qt::ShiftModifier) + nMods |= ShiftMask; + if (modifiers & Qt::ControlModifier) + nMods |= ControlMask; + if (modifiers & Qt::AltModifier) + nMods |= Mod1Mask; + if (modifiers & Qt::MetaModifier) + nMods |= Mod4Mask; + ok = true; + return nMods; +} + +bool QHotkeyPrivateX11::registerShortcut(QHotkey::NativeShortcut shortcut) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + const QNativeInterface::QX11Application *x11Interface = qGuiApp->nativeInterface(); + Display *display = x11Interface->display(); +#else + const bool x11Interface = QX11Info::isPlatformX11(); + Display *display = QX11Info::display(); +#endif + + if(!display || !x11Interface) + return false; + + HotkeyErrorHandler errorHandler; + for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) { + XGrabKey(display, + shortcut.key, + shortcut.modifier | specialMod, + DefaultRootWindow(display), + True, + GrabModeAsync, + GrabModeAsync); + } + XSync(display, False); + + if(errorHandler.hasError) { + error = errorHandler.errorString; + this->unregisterShortcut(shortcut); + return false; + } + return true; +} + +bool QHotkeyPrivateX11::unregisterShortcut(QHotkey::NativeShortcut shortcut) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 2, 0) + Display *display = qGuiApp->nativeInterface()->display(); +#else + Display *display = QX11Info::display(); +#endif + + if(!display) + return false; + + HotkeyErrorHandler errorHandler; + for(quint32 specialMod : QHotkeyPrivateX11::specialModifiers) { + XUngrabKey(display, + shortcut.key, + shortcut.modifier | specialMod, + XDefaultRootWindow(display)); + } + XSync(display, False); + + if(HotkeyErrorHandler::hasError) { + error = HotkeyErrorHandler::errorString; + return false; + } + return true; +} + +QString QHotkeyPrivateX11::formatX11Error(Display *display, int errorCode) +{ + char errStr[256]; + XGetErrorText(display, errorCode, errStr, 256); + return QString::fromLatin1(errStr); +} + + + +// ---------- QHotkeyPrivateX11::HotkeyErrorHandler implementation ---------- + +bool QHotkeyPrivateX11::HotkeyErrorHandler::hasError = false; +QString QHotkeyPrivateX11::HotkeyErrorHandler::errorString; + +QHotkeyPrivateX11::HotkeyErrorHandler::HotkeyErrorHandler() +{ + prevHandler = XSetErrorHandler(&HotkeyErrorHandler::handleError); +} + +QHotkeyPrivateX11::HotkeyErrorHandler::~HotkeyErrorHandler() +{ + XSetErrorHandler(prevHandler); + hasError = false; + errorString.clear(); +} + +int QHotkeyPrivateX11::HotkeyErrorHandler::handleError(Display *display, XErrorEvent *error) +{ + switch (error->error_code) { + case BadAccess: + case BadValue: + case BadWindow: + if (error->request_code == 33 || //grab key + error->request_code == 34) {// ungrab key + hasError = true; + errorString = QHotkeyPrivateX11::formatX11Error(display, error->error_code); + return 1; + } + Q_FALLTHROUGH(); + // fall through + default: + return 0; + } +}