From 186baf95eb1bc9bfe694f92279f74c5f8bf90ee6 Mon Sep 17 00:00:00 2001 From: bruno boutin Date: Thu, 12 Sep 2024 17:23:53 +0200 Subject: [PATCH] Set the control width of Dropdown by setting the fieldWidth property (#5586) * Add fieldWidth property to Dropdown * let dropdown participate in aligning labelwidths This is now easily addable to any qml control by adding the property controlXOffset and applying it to the relevant leftPadding * use new aligning in prefsUI --------- Co-authored-by: Joris Goosen --- .../JASP/Widgets/FileMenu/PrefsUI.qml | 121 +++++++----------- .../components/JASP/Controls/ComboBox.qml | 72 ++++++----- .../components/JASP/Controls/GroupBox.qml | 62 ++++----- QMLComponents/controls/comboboxbase.cpp | 3 + QMLComponents/controls/comboboxbase.h | 5 + 5 files changed, 125 insertions(+), 138 deletions(-) diff --git a/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml b/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml index dc5492eb02..09247d689a 100644 --- a/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml +++ b/Desktop/components/JASP/Widgets/FileMenu/PrefsUI.qml @@ -50,19 +50,44 @@ ScrollView { id: fontGroup title: qsTr("Fonts") - - property real maxText: Math.max(Math.max(interfaceTxt.implicitWidth, codeTxt.implicitWidth), Math.max(resultTxt.implicitWidth, languageTxt.implicitWidth)); - Row + Item { - spacing: 3 * preferencesModel.uiScale - width: parent.width + visible: false; + // If the defaultInterfaceFont etc does not exist on the machine, then the default font of the machine is used. + // These (invisible) Text items are just to ask what will be the real font used. + + Text + { + + id: defaultInterfaceFont + font.family: preferencesModel.defaultInterfaceFont + text: fontInfo.family + } - Text { id: interfaceTxt; width: fontGroup.maxText; text: qsTr("Interface:") } + Text + { + id: defaultRCodeFont + text: fontInfo.family + font.family: preferencesModel.defaultCodeFont + } + + Text + { + id: defaultResultFont + text: fontInfo.family + font.family: preferencesModel.defaultResultFont + } + } - DropDown + GroupBox + { + width: parent.width + + DropDown { id: interfaceFonts + label: qsTr("Interface:") values: preferencesModel.allInterfaceFonts addEmptyValue: true showEmptyValueAsNormal: true @@ -72,33 +97,12 @@ ScrollView onValueChanged: preferencesModel.interfaceFont = (currentIndex <= 0 ? "" : value) KeyNavigation.tab: codeFonts - - control.width: parent.width - (x + control.x) - - - Text - { - // If the defaultInterfaceFont does not exist on the machine, then the default font of the machine is used. - // This (invisible) Text item is just to ask what will be the real font used. - id: defaultInterfaceFont - font.family: preferencesModel.defaultInterfaceFont - text: fontInfo.family - visible: false - } } - } - - - Row - { - spacing: 3 * preferencesModel.uiScale - width: parent.width - - Text { id: codeTxt; width: fontGroup.maxText; text: qsTr("R, JAGS, or lavaan code:") } - + DropDown { id: codeFonts + label: qsTr("R, JAGS, or lavaan code:") values: preferencesModel.allCodeFonts addEmptyValue: true showEmptyValueAsNormal: true @@ -108,32 +112,12 @@ ScrollView onValueChanged: preferencesModel.codeFont = (currentIndex <= 0 ? "" : value) KeyNavigation.tab: resultFonts - - control.width: parent.width - (x + control.x) - - Text - { - id: defaultRCodeFont - text: fontInfo.family - font.family: preferencesModel.defaultCodeFont - visible: false - } - } - - } - - Row - { - spacing: 3 * preferencesModel.uiScale - width: parent.width - - Text { id: resultTxt; width: fontGroup.maxText; text: qsTr("Result & help:") } - DropDown { id: resultFonts + label: qsTr("Result & help:") values: preferencesModel.allResultFonts addEmptyValue: true showEmptyValueAsNormal: true @@ -143,16 +127,6 @@ ScrollView onValueChanged: preferencesModel.resultFont = (currentIndex <= 0 ? "" : value) KeyNavigation.tab: qtTextRendering - - control.width: parent.width - (x + control.x) - - Text - { - id: defaultResultFont - text: fontInfo.family - font.family: preferencesModel.defaultResultFont - visible: false - } } } @@ -207,24 +181,17 @@ ScrollView id: languageGroup title: qsTr("Preferred language") - Row + + DropDown { - spacing: 3 * preferencesModel.uiScale - width: parent.width - - Text { id: languageTxt; width: fontGroup.maxText; text: qsTr("Choose language ") } + id: languages + label: qsTr("Choose language ") + source: languageModel + startValue: languageModel.currentLanguage + onValueChanged: languageModel.currentLanguage = value - DropDown - { - id: languages - source: languageModel - startValue: languageModel.currentLanguage - onValueChanged: languageModel.currentLanguage = value - - KeyNavigation.tab: altnavcheckbox - - control.width: parent.width - (x + control.x) - } + KeyNavigation.tab: altnavcheckbox + } diff --git a/QMLComponents/components/JASP/Controls/ComboBox.qml b/QMLComponents/components/JASP/Controls/ComboBox.qml index e6d07c356d..eb213673ae 100644 --- a/QMLComponents/components/JASP/Controls/ComboBox.qml +++ b/QMLComponents/components/JASP/Controls/ComboBox.qml @@ -19,7 +19,7 @@ ComboBoxBase property alias currentLabel: comboBox.currentText property alias value: comboBox.currentValue property alias indexDefaultValue: comboBox.currentIndex - property alias fieldWidth: control.modelWidth + property alias fieldWidth: control.width property bool showVariableTypeIcon: containsVariables property var enabledOptions: [] property bool setLabelAbove: false @@ -28,8 +28,11 @@ ComboBoxBase property bool showBorder: true property bool showEmptyValueAsNormal: false property bool addLineAfterEmptyValue: false + property double controlXOffset: 0 onControlMinWidthChanged: _resetWidth(textMetrics.width) + + function resetWidth(values) { @@ -55,13 +58,16 @@ ComboBoxBase _resetWidth(maxWidth) } - function _resetWidth(maxWidth) + function _resetWidth(maxTextWidth) { - var newWidth = maxWidth + ((comboBox.showVariableTypeIcon ? 20 : 4) * preferencesModel.uiScale); - control.modelWidth = newWidth; - if (control.width < controlMinWidth) - control.modelWidth += (controlMinWidth - control.width); - comboBox.width = comboBox.implicitWidth; // the width is not automatically updated by the implicitWidth... + control.maxTextWidth = maxTextWidth + // The real field width is composed by the type icon (if displayed) + 2-padding + max width + 5-padding + dropdownIcon width + 2-padding + var newFieldWidth = (comboBox.showVariableTypeIcon ? contentIcon.x + contentIcon.width : 0) + maxTextWidth + dropdownIcon.width + 9 * preferencesModel.uiScale + if (newFieldWidth < controlMinWidth) + newFieldWidth = controlMinWidth + + control.realFieldWidth = newFieldWidth + if (!fixedWidth) control.width = newFieldWidth; } Component.onCompleted: control.activated.connect(activated); @@ -85,22 +91,24 @@ ComboBoxBase QTC.ComboBox { - id: control - model: comboBox.model - anchors.left: !rectangleLabel.visible || comboBox.setLabelAbove ? comboBox.left : rectangleLabel.right - anchors.leftMargin: !rectangleLabel.visible || comboBox.setLabelAbove ? 0 : jaspTheme.labelSpacing - anchors.top: rectangleLabel.visible && comboBox.setLabelAbove ? rectangleLabel.bottom: comboBox.top - - focus: true - padding: 2 * preferencesModel.uiScale //jaspTheme.jaspControlPadding - - width: modelWidth + extraWidth - height: jaspTheme.comboBoxHeight - font: jaspTheme.font - property int modelWidth: 30 * preferencesModel.uiScale - property int extraWidth: 5 * padding + dropdownIcon.width - property bool isEmptyValue: comboBox.addEmptyValue && comboBox.currentIndex === 0 + id: control + model: comboBox.model + anchors + { + top: rectangleLabel.visible && comboBox.setLabelAbove ? rectangleLabel.bottom: comboBox.top + left: !rectangleLabel.visible || comboBox.setLabelAbove ? comboBox.left : rectangleLabel.right + leftMargin: controlXOffset + (!rectangleLabel.visible || comboBox.setLabelAbove ? 0 : jaspTheme.labelSpacing) + } + + focus: true + padding: 2 * preferencesModel.uiScale + width: 0 + height: jaspTheme.comboBoxHeight + font: jaspTheme.font + property bool isEmptyValue: comboBox.addEmptyValue && comboBox.currentIndex === 0 property bool showEmptyValueStyle: !comboBox.showEmptyValueAsNormal && isEmptyValue + property double realFieldWidth: width + property double maxTextWidth: 0 TextMetrics { @@ -123,8 +131,8 @@ ComboBoxBase { id: contentIcon height: 15 * preferencesModel.uiScale - width: 15 * preferencesModel.uiScale - x: 3 * preferencesModel.uiScale + width: 15 * preferencesModel.uiScale // Even if not visible, the width should stay the same: if showVariableTypeIcon is true, a value may have no icon, but an empty icon place should still be displayed + x: 2 * preferencesModel.uiScale anchors.verticalCenter: parent.verticalCenter source: !visible ? "" : ((comboBox.currentColumnTypeIcon && comboBox.isBound) ? comboBox.currentColumnTypeIcon : (comboBox.values && comboBox.currentIndex >= 0 && comboBox.currentIndex < comboBox.values.length ? comboBox.values[comboBox.currentIndex].columnTypeIcon : "")) visible: comboBox.showVariableTypeIcon && !control.isEmptyValue && (comboBox.currentColumnType || !comboBox.isBound) @@ -133,19 +141,24 @@ ComboBoxBase Text { anchors.left: contentIcon.visible ? contentIcon.right : parent.left - anchors.leftMargin: 4 * preferencesModel.uiScale + anchors.leftMargin: 2 * preferencesModel.uiScale anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: control.showEmptyValueStyle ? parent.horizontalCenter : undefined text: comboBox.currentText font: control.font color: (!enabled || control.showEmptyValueStyle) ? jaspTheme.grayDarker : jaspTheme.black + width: (fixedWidth ? widthWhenContralHasFixedWidth : control.maxTextWidth) + 5 * preferencesModel.uiScale + elide: Text.ElideRight + + property double widthWhenContralHasFixedWidth: control.width - (x + dropdownIcon.width + 4 * preferencesModel.uiScale) // 4 = leftMargin + 2 padding right of dropdownIcon) + } } indicator: Image { id: dropdownIcon - x: control.width - width - 3 //control.spacing + x: control.width - width - 2 * preferencesModel.uiScale y: control.topPadding + (control.availableHeight - height) / 2 width: 12 * preferencesModel.uiScale height: 12 * preferencesModel.uiScale @@ -180,7 +193,7 @@ ComboBoxBase { id: popupRoot y: control.height - width: comboBoxBackground.width + scrollBar.width + width: Math.max(control.realFieldWidth, fieldWidth) + scrollBar.width property real maxHeight: typeof mainWindowRoot !== 'undefined' ? mainWindowRoot.height // Case Dropdowns used in Desktop : (typeof rcmdRoot !== 'undefined' ? rcmdRoot.height // Case Dropdown used in R Command @@ -211,7 +224,7 @@ ComboBoxBase contentItem: ListView { id: popupView - width: comboBoxBackground.width + width: popupRoot.width - scrollBar.width height: popupRoot.height model: control.popup.visible ? control.delegateModel : null currentIndex: control.highlightedIndex @@ -239,14 +252,13 @@ ComboBoxBase delegate: QTC.ItemDelegate { height: jaspTheme.comboBoxHeight - implicitWidth: comboBoxBackground.width + width: popupView.width enabled: comboBox.enabledOptions.length == 0 || comboBox.enabledOptions.length <= index || comboBox.enabledOptions[index] contentItem: Rectangle { id: itemRectangle anchors.fill: parent - anchors.rightMargin: scrollBar.visible ? scrollBar.width + 2 : 0 color: comboBox.currentIndex === index ? jaspTheme.itemSelectedColor : (control.highlightedIndex === index ? jaspTheme.itemHoverColor : jaspTheme.controlBackgroundColor) property bool isEmptyValue: comboBox.addEmptyValue && index === 0 diff --git a/QMLComponents/components/JASP/Controls/GroupBox.qml b/QMLComponents/components/JASP/Controls/GroupBox.qml index 72dc653551..71f44373fd 100644 --- a/QMLComponents/components/JASP/Controls/GroupBox.qml +++ b/QMLComponents/components/JASP/Controls/GroupBox.qml @@ -39,11 +39,11 @@ GroupBoxBase property int columnSpacing: jaspTheme.columnGroupSpacing property int columns: 1 property bool indent: false - property bool alignTextFields: true + property bool alignFields: true property alias label: label property alias preferredWidth: contentArea.width - property var _allTextFields: [] + property var _allAlignableFields: [] property bool _childrenConnected: false Label @@ -90,14 +90,14 @@ GroupBoxBase // The alignment should be done when the scaling of the TextField's are done id: alignTextFieldTimer interval: 50 - onTriggered: _alignTextFields() + onTriggered: _alignFields() } Timer { id: checkFormOverflowAndAlignTimer interval: 50 - onTriggered: _alignTextFields() + onTriggered: _alignFields() } Component.onCompleted: @@ -105,74 +105,74 @@ GroupBoxBase for (var i = 0; i < contentArea.children.length; i++) { var child = contentArea.children[i]; - if (child.hasOwnProperty('controlType') && child.controlType === JASPControl.TextField) - _allTextFields.push(child) + if (child.hasOwnProperty('controlType') && child.hasOwnProperty('controlXOffset'))//child.controlType === JASPControl.TextField) + _allAlignableFields.push(child) } checkFormOverflowAndAlignTimer.start() } - function _alignTextFields() + function _alignFields() { - if (!alignTextFields || _allTextFields.length < 1) return; + if (!alignFields || _allAlignableFields.length < 1) return; - var allTextFieldsPerColumn = {} + var allAlignableFieldsPerColumn = {} var columns = []; - var i, j, textField; + var i, j, field; - for (i = 0; i < _allTextFields.length; i++) + for (i = 0; i < _allAlignableFields.length; i++) { - textField = _allTextFields[i]; + field = _allAlignableFields[i]; if (!_childrenConnected) // Do not connect visible when component is just completed: the visible value is aparently not yet set for all children. // So do it with the first time it is aligned. - textField.visibleChanged.connect(_alignTextFields); + field.visibleChanged.connect(_alignFields); - if (textField.visible) + if (field.visible) { - if (!allTextFieldsPerColumn.hasOwnProperty(textField.x)) + if (!allAlignableFieldsPerColumn.hasOwnProperty(field.x)) { // Cannot use Layout.column to know in which column is the textField. // Then its x value is used. - allTextFieldsPerColumn[textField.x] = [] - columns.push(textField.x) + allAlignableFieldsPerColumn[field.x] = [] + columns.push(field.x) } - allTextFieldsPerColumn[textField.x].push(textField) + allAlignableFieldsPerColumn[field.x].push(field) } } for (i = 0; i < columns.length; i++) { - var textFields = allTextFieldsPerColumn[columns[i]] + var textFields = allAlignableFieldsPerColumn[columns[i]] // To align all the textfields on one column: // . First search for the Textfield with the longest label (its innerControl x position). // . Then add an offset (the controlXOffset) to all other textfields so that they are aligned with the longest TextField if (textFields.length >= 1) { - textField = textFields[0]; - textField.controlXOffset = 0; - var xMax = textField.innerControl.x; - var longestControl = textField.innerControl; + field = textFields[0]; + field.controlXOffset = 0; + var xMax = field.innerControl.x; + var longestControl = field.innerControl; for (j = 1; j < textFields.length; j++) { - textField = textFields[j]; - textField.controlXOffset = 0; - if (xMax < textField.innerControl.x) + field = textFields[j]; + field.controlXOffset = 0; + if (xMax < field.innerControl.x) { - longestControl = textField.innerControl; - xMax = textField.innerControl.x; + longestControl = field.innerControl; + xMax = field.innerControl.x; } } for (j = 0; j < textFields.length; j++) { - textField = textFields[j]; - if (textField.innerControl !== longestControl) + field = textFields[j]; + if (field.innerControl !== longestControl) // Cannot use binding here, since innerControl.x depends on the controlXOffset, // that would generate a binding loop - textField.controlXOffset = (xMax - textField.innerControl.x); + field.controlXOffset = (xMax - field.innerControl.x); } } diff --git a/QMLComponents/controls/comboboxbase.cpp b/QMLComponents/controls/comboboxbase.cpp index da42b9c959..4bfa3bcca7 100644 --- a/QMLComponents/controls/comboboxbase.cpp +++ b/QMLComponents/controls/comboboxbase.cpp @@ -118,6 +118,9 @@ bool ComboBoxBase::isJsonValid(const Json::Value &optionValue) const void ComboBoxBase::setUp() { + if (property("fieldWidth").toInt() > 0) // If the fieldWidth is set, it means the width should be fixed and not dependent on the values of the dropdown. + _fixedWidth = true; + JASPListControl::setUp(); _model->resetTermsFromSources(); diff --git a/QMLComponents/controls/comboboxbase.h b/QMLComponents/controls/comboboxbase.h index d70d232c29..9ee4ea82d6 100644 --- a/QMLComponents/controls/comboboxbase.h +++ b/QMLComponents/controls/comboboxbase.h @@ -35,6 +35,8 @@ class ComboBoxBase : public JASPListControl, public BoundControlBase Q_PROPERTY( QString startValue READ startValue WRITE setStartValue NOTIFY startValueChanged ) Q_PROPERTY( QString currentColumnType READ currentColumnType NOTIFY currentColumnTypeChanged ) Q_PROPERTY( QString currentColumnTypeIcon READ currentColumnTypeIcon NOTIFY currentColumnTypeIconChanged ) + Q_PROPERTY( bool fixedWidth READ fixedWidth NOTIFY fixedWidthChanged ) + public: ComboBoxBase(QQuickItem* parent = nullptr); @@ -53,6 +55,7 @@ class ComboBoxBase : public JASPListControl, public BoundControlBase const QString& currentColumnType() const { return _currentColumnType; } const QString& currentColumnTypeIcon() const { return _currentColumnTypeIcon;} int currentIndex() const { return _currentIndex; } + bool fixedWidth() const { return _fixedWidth; } std::vector usedVariables() const override; @@ -65,6 +68,7 @@ class ComboBoxBase : public JASPListControl, public BoundControlBase void currentColumnTypeIconChanged(); void currentIndexChanged(); void activated(int index); + void fixedWidthChanged(); protected slots: void termsChangedHandler() override; @@ -84,6 +88,7 @@ protected slots: _currentColumnRealType, _currentColumnTypeIcon; int _currentIndex = -1; + bool _fixedWidth = false; int _getStartIndex() const; void _resetItemWidth();