Specifying font for many Text-elements in Qt QML - c++

I have a widget specified through a QML file. This widget contains a top levelRectangle which contains two Columns. Each of these Columns contains many Text-elements. This QML widget is wrapped in a subclass of QDeclarativeView in C++.
I want to specify the font for each of these Text-elements. Today I do this by specifying top-level properties:
property string fontfamily: "Arial"
property bool fontbold: false
property bool fontitalic: false
property int fontpixelsize: 11
property string fontcolor: "White"
and bind each Text-elements to these properties:
Text
{
color: fontcolor
font.family: fontfamily
font.bold: fontbold
font.italic: fontitalic
font.pixelSize: fontpixelsize
...
}
This isn't very elegant and new fields needs to be added every time I need support for something new (e.g. underlined fonts). I have not been able to declare a property of type font and bind to this instead (widget is blank and qmlviewer warns about "expected type after property").
Is there a better way to specify a font for all Text-elements?
Note! I'm handwriting the QML files.

In Qt 5.6 (at least, probably earlier too), you can use Qt.font() to dynamically allocate a font object and refer to it elsewhere. So, this works:
property font myFont: Qt.font({
family: fontfamily,
bold: fontbold,
italic: fontitalic,
pixelSize: fontpixelsize
});
Text
{
color: fontcolor
font: parent.myFont
}
More info on Qt.font() here: https://doc.qt.io/qt-5/qml-qtqml-qt.html#font-method

Another possibility is to write a new QML component, that inherits from Text an sets some properties by default:
StyledText.qml
import QtQuick 1.0
Text {
// set default values
color: "blue"
font.family: "Arial"
font.bold: true
font.italic: true
font.pixelSize: 12
}
main.qml
import QtQuick 1.0
Rectangle {
Row {
spacing: 10
Column {
StyledText {
text: "Foo1"
}
StyledText {
text: "Bar1"
}
StyledText {
text: "Baz1"
}
}
Column {
StyledText {
text: "Foo2"
}
StyledText {
text: "Bar2"
}
StyledText {
text: "Baz2"
}
}
}
}

One possible solution is to write a function, that iterates over the children of a passed element (for example a Column). In this function all the properties can be set:
import QtQuick 1.0
Rectangle {
Row {
spacing: 10
Column {
id: col1
Text {
property bool useStyle: true
text: "Foo1"
}
Text {
property bool useStyle: true
text: "Bar1"
}
Text {
property bool useStyle: true
text: "Baz1"
}
}
Column {
id: col2
Text {
property bool useStyle: true
text: "Foo2"
}
Text {
text: "not styled"
}
Text {
property bool useStyle: true
text: "Baz2"
}
}
}
function setTextStyle(parentElement) {
for (var i = 0; i < parentElement.children.length; ++i) {
console.log("t", typeof parentElement.children[i]);
if (parentElement.children[i].useStyle) { // apply style?
parentElement.children[i].color = "blue";
parentElement.children[i].font.family = "Arial"
parentElement.children[i].font.bold = true;
parentElement.children[i].font.italic = true;
parentElement.children[i].font.pixelSize = 12;
}
}
}
// set style
Component.onCompleted: {
setTextStyle(col1);
setTextStyle(col2);
}
}
Each element, that contains the property useStyle that is set to true, gets styled. This is shorter, than assigning the style manually, but you can still define which elements should get styled or not.

Necro posting, but I feel it's still missing an up-to-date solution.
FontMetrics will do the trick without using Qt.font(). You can declare it in your parent item or in a Singleton type, and then you can bind the property to it.
Here there's an example
Item {
id: root
FontMetrics {
id: fontMetrics
font.family: "Arial"
font.pixelSize: 24
}
property alias font: fontMetrics.font
Text { font: root.font }
Text { font: root.font }
}

Some useful workarounds here, but I'm stuck being able to define some base fonts while still being able to specify details later, for more than a few fonts. In particular, because:
FontLoader sets the same name for every font of the same family. Ref
If Qt.font() (or FontMetrics) is used, it's all or nothing for the font property.
Defining new Text Components requires one file per font. Update: maybe not true since 5.15.
Triggering a function is difficult to manage effectively across a whole application.
I think there's room for one more solution:
In my main.qml I used one FontLoader per font file, but only bothered setting an id when the family changes:
FontLoader { source: "qrc:/fonts/ITCAvantGardeStd-Bk.otf"; id: flAvantGarde; }
FontLoader { source: "qrc:/fonts/ITCAvantGardeStd-BkObl.otf"; }
FontLoader { source: "qrc:/fonts/ITCAvantGardeStd-BoldCn.otf"; }
FontLoader { source: "qrc:/fonts/ITCAvantGardeStd-Md.otf"; }
FontLoader { source: "qrc:/fonts/ADAM.CG PRO.otf"; id: flAdam; }
and then, and here's the significant part, defined one property var per base font like so:
property var fontAgBk: { "family": flAvantGarde.name, "styleName": "Book" }
property var fontAgBkObl: { "family": flAvantGarde.name, "styleName": "Book Oblique" }
property var fontAgBoldCn: { "family": flAvantGarde.name, "styleName": "Bold Condensed" }
property var fontAgMd: { "family": flAvantGarde.name, "styleName": "Medium" }
property var fontAdam: { "family": flAdam.name }
The var is key being able to specify a dictionary that can be defined once, but pulled apart later.
Elsewhere in any qml, I can do something like:
Text {
id: myText
color: "#123456"
font.family: fontBoldCn.family
font.styleName: fontBoldCn.styleName
font.pixelSize: 24
font.letterSpacing: 5
}
Still requires some repetitive code, but at least the magic strings are only defined once.

Related

QML TreeView display nodes by levels or custom delegate

I have a tree model derived from a QAbstractItemModel. And I can display the data in a tree like way.
What I want is to display teh data by the layers. To display only one level of a layer at a time AND put each layer on a stack and navigate backwards by poping the layer from the stack.
I guess I have to implement a custom delegate? Any advice would be highly appreciated. Thank you.
I recently implemented something similar, based on a QFileSystemModel, set as a qml contextProperty, named treeModel in the example below.
The idea was to keep track of the current QModelIndex, and to use the data() & rowCount() functions of the QAbstractItemModel to get the actual model data, and to use a recursive stack view for the navigation
General layout
ApplicationWindow {
id: main
visible: true
width: 640
height: 480
ColumnLayout
{
anchors.fill: parent
// Breadcrumb
SEE BELOW
// View
StackView
{
id: stackView
Layout.fillHeight: true
Layout.fillWidth: true
initialItem: TreeSlide {}
}
}
}
TreeSlide
The view itself is pretty simple. I didn't used anything fancy here, and it displays only one role, but you could extend it without trouble. Note that the view's model is NOT your treeModel, but instead just the rowCount for the rootIndex.
ListView
{
Layout.fillHeight: true
Layout.fillWidth: true
model: treeModel.rowCount(rootIndex)
clip: true
snapMode: ListView.SnapToItem
property var rootIndex
// I used a QFileSytemModel in my example, so I had to manually
// fetch data when the rootIndex changed. You may not need this though.
onRootIndexChanged: {
if(treeModel.canFetchMore(rootIndex))
treeModel.fetchMore(rootIndex)
}
Connections {
target: treeModel
onRowsInserted: {
rootIndexChanged()
}
}
delegate: ItemDelegate {
property var modelIndex: treeModel.index(index,0, rootIndex)
property bool hasChildren: treeModel.hasChildren(modelIndex)
width: parent.width
text: treeModel.data(modelIndex)
onClicked: {
if(hasChildren)
{
// Recursively add another TreeSlide, with a new rootIndex
stackView.push("TreeSlide.qml", {rootIndex: modelIndex})
}
}
}
}
Breadcrumb
To navigate the model, instead of a simple back button, I used a kind of dynamic breadcrumb
// Breadcrumb
RowLayout
{
Repeater
{
id: repeat
model: {
var res = []
var temp = stackView.currentItem.rootIndex
while(treeModel.data(temp) != undefined)
{
res.unshift(treeModel.data(temp))
temp = temp.parent
}
res.unshift('.')
return res
}
ItemDelegate
{
text : modelData
onClicked: {
goUp(repeat.count - index-1)
}
}
}
}
the goUp function simply goes up the stack by poping items
function goUp(n)
{
for(var i=0; i<n; i++)
stackView.pop()
}
To to do it completely by guides we should use DelegateModel and DelegateModel.rootIndex
DelegateModel {
id: delegateSupportPropConfigModel
model: supportModel
delegate: SupportPropConfigListItem {
id: currentItem
width: scrollRect2.width - 60
fieldName: model.fieldName
fieldValue: model.value
onClick:{
delegateSupportPropConfigModel.rootIndex = supportPropConfigModel.index(0, 0, supportPropConfigModel)
}
}
}
Column {
id: columnSettings
spacing: 2
Repeater {
model: delegateSupportPropConfigModel
}
}

QML: How to retrieve a default font object?

I have a QML Item with some Text fields in it, which should have all the same font. Do achieve this i introduce a new property myFont of type font. Do initialize this property I use the Qt.font function, which creates a font object. But I have to specify at least one property (either family or pointSize).
My Question is now: How can I retrieve the default font for the myFont property?
If I create only a Text{} item, it has already a default font attached, how can I get the same font for the myFont property? (Meanwhile I use a hidden Text field and create an alias to it's font property, but I want a better solution).
Item {
property font myFont: Qt.font({pointSize: 10})
Text {
id: header
font: myFont
text: "My Header"
}
Text {
id: subject
font: myFont
text: "My Subject"
}
Text {
id: message
font: myFont
text: "Some meassage!"
}
}
I think the right way to solve this is to create your own object type with the font you need to use.
In the following example, the new object would be MyText.qml. I don't know your whole code, but I suppose you have a ListView with the delegate you posted in your question.
MyText.qml
import QtQuick 2.0
Text {
font: Qt.font({pointSize: 10})
}
main.qml
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
ListModel {
id: myModel
ListElement {
header: "header xxx"
subject: "subject xxx"
message: "message xxx"
}
ListElement {
header: "header yyy"
subject: "subject yyy"
message: "message yyy"
}
ListElement {
header: "header zzz"
subject: "subject zzz"
message: "message zzz"
}
}
ListView {
anchors.fill: parent
model: myModel
delegate: Item {
width: 300; height: 80
Column {
MyText {
id: myheader
text: header + " - family: " + font.family + " size: " + font.pointSize
}
MyText {
id: mysubject
text: subject + " - family: " + font.family + " size: " + font.pointSize
}
MyText {
id: mymessage
text: message + " - family: " + font.family + " size: " + font.pointSize
}
}
}
}
}
Now, I've digged into Qt source code.
And it turns out that Qt uses a registered private TextSingleton which is defined (Qt 5.6) as:
pragma Singleton
import QtQuick 2.2
Text {
}
The font property of various qml controls is initialized:
font: TextSingleton.font
Digging further into C++ code reveals that for the Text item, the font property is a default initialized QFont, which gives the QFont object retrieved from QGuiApplication::font().
As I mentioned here, FontMetrics is the way to go since it's configurable and without using Qt.font(). You can declare it in your parent item or in a SIngleton type and the you can bind the property to it.
Here there's an example
Item {
id: root
FontMetrics {
id: fontMetrics
font.family: "Arial"
font.pixelSize: 24
}
property alias font: fontMetrics.font
Text { font: root.font }
Text { font: root.font }
}
You can access the default font in QML with Qt.application.font.
You can get and set this font in C++ using
QFont font = QApplication::font();
font.setPointSize(12); //make various other changes or get a completely new QFont
QApplication::setFont(font);

Simple keyboardless touchscreen widgets in Qt

I'm looking for a simple way to make widgets for a touch-screen that will allow users to set the time and IP address on the computer running the code and provide a simple (uppercase Latin-alphabetic) name.
This question is not about how to actually set the system time or IP address; I'm just looking for information about how to make the graphical widgets themselves.
What I want is for each editable property (time, address, and name) to be divided into "scrollable" fields, where the fields for "time" are hours, minutes, possibly seconds, and AM/PM/24-hr, and the fields for address/name are the individual characters. Each field would have an arrow above and below it, and touching on an arrow would scroll through the valid values for that field.
I think this is a pretty common UX pattern, especially in meatspace (e.g. on alarm clocks), but just in case it's not clear what I'm trying to describe, here's an example with a user editing the "name" property:
^^^
BN
vvv
User presses "down" below the "N":
^^^
BO
vvv
User presses "down" below the empty space:
^^^^
BOA
vvvv
...and again on the same down-arrow:
^^^^
BOB
vvvv
I'm writing this using C++14 with Qt 5. (If worst comes to worst, I'd be open to writing a separate app using a different language and/or framework, but I'm not asking for framework suggestions here; if you have one, let me know and I'll open a corresponding question on Software Recommendations SE.)
I don't see anything in the Qt 5 widget library like this; most of the input widgets are text fields. QSpinBox looks somewhat promising, but the arrows are probably too small for my touchscreen, and using a separate spinbox for each letter would probably be confusing and ugly.
I don't really know enough about Qt or GUI-programming in general to feel confident trying to write my own widgets from scratch, but this interface looks simple enough that I would expect a couple lines of QML would get me well on my way.
ListView as well as PathView can produce the desired result with slightly different behaviors and slightly different performances. Differently from ListView, PathView is circular, i.e. elements can be iterated continuously by using just one of the selection controls. It is also easier to fully customize the behavior of the path in PathView via the PathAttribute type. Anyhow path customization seems not to be a required feature, according to the question.
If you implement the solution via a ListView you should ensure that just one element is shown and that any model is processed.
Component {
id: spinnnnnnnner
Column {
width: 100
height: 110
property alias model: list.model
property string textRole: ''
spacing: 10
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "-"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.decrementCurrentIndex() }
}
ListView {
id: list
clip: true
width: 100
height: 55
enabled: false // <--- remove to activate mouse/touch grab
highlightRangeMode: ListView.StrictlyEnforceRange // <--- ensures that ListView shows current item
delegate: Text {
width: ListView.view.width
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 50
font.bold: true
text: textRole === "" ? modelData :
((list.model.constructor === Array ? modelData[textRole] : model[textRole]) || "")
}
}
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "+"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.incrementCurrentIndex() }
}
}
}
The checks over the model ensure that any type of model can be passed to the component. Here is an example using three very different models:
import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
ApplicationWindow {
visible: true
width: 400
height: 300
ListModel {
id: mod
ListElement {texty: "it1"}
ListElement {texty: "it2"}
ListElement {texty: "it3"}
}
Row {
Repeater {
id: rep
model: 3
delegate: spinnnnnnnner
Component.onCompleted: {
rep.itemAt(0).model = mod // listmodel
rep.itemAt(0).textRole = "texty"
rep.itemAt(1).model = 10 // number model
//
rep.itemAt(2).model = ["foo", "bar", "baz"] // array model
}
}
}
}
PathView implementation is not so different from the ListView one. In this case it is sufficient to define a vertical path and specify that just one one element is visible at a time via pathItemCount. Finally, setting preferredHighlightBegin/preferredHighlightEnd ensures that the visible element is centered in the view. The revisited component is the following:
Component {
id: spinnnnnnnner
Column {
width: 100
height: 110
property alias model: list.model
property string textRole: ''
spacing: 10
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "-"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.decrementCurrentIndex() }
}
PathView {
id: list
clip: true
width: 100
height: 55
enabled: false // <--- remove to activate mouse/touch grab
pathItemCount: 1
preferredHighlightBegin: 0.5
preferredHighlightEnd: 0.5
path: Path {
startX: list.width / 2; startY: 0
PathLine { x: list.width / 2; y: list.height }
}
delegate: Text {
width: PathView.view.width
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 50
font.bold: true
text: textRole === "" ? modelData :
((list.model.constructor === Array ? modelData[textRole] : model[textRole]) || "")
}
}
Item {
width: 100
height: 25
Text { anchors.centerIn: parent; text: "+"; font.pixelSize: 25; font.bold: true }
MouseArea {anchors.fill: parent; onClicked: list.incrementCurrentIndex() }
}
}
}

How to switch Qt5 Controls QML styles in realtime

So my objective is to set up themes for an application that I have made. I would like to be able to check a menu option that will change the theme of the application as soon as I press it; and change the theme back to the native one on pressing the button. I understand how to style the Qt Quick Controls but I am having trouble with the loading and unloading of these themes. Currently my application comes with the native-like theme that Qt Controls comes with. I am able to load custom styles but I am unable to swap them the custom styles and the native theme. How would I go about creating this feature?
For example I am trying to swap the theme of a TableView. I first tried setting a property of TableViewStyle to be styleData.value This produced the desired effect of keeping the native-like theme, but now how would I go about assigning the Rectangle QmlObject to a property value? The interpreter complains about how I cannot assign a Object to a property.
TableViewStyle {
property string color: styleData.value
headerDelete: color // This does not work. Because
// styleData.value in undefined
//property string color: rect // Currently doing this does not
//Rectangle {id: rect; color: "red"} // have any effect on the value of
//headerDelegate: color // the headerDelegate. Putting the
// Rectangle in a Component produces
// The same value too.
}
I did not find a way to go back to the default style without having to load the element first with the default style and save it but this works for a Button switching between two custom styles and the default ButtonStyle
// Property used to save the default ButtonStyle
property var defaultStyle: undefined
// Property used to know the current style
property string currentStyle: "blue"
Button {
id: myBtn
text: qsTr("Hello World")
anchors.centerIn: parent
onClicked: {
if(currentStyle == "blue") {
style = myButtonStyleRed;
currentStyle = "red";
} else if(currentStyle == "red") {
style = defaultStyle;
currentStyle = "default";
} else {
style = myButtonStyleBlue;
currentStyle = "blue";
}
}
// Save the default Button style on load before switching to the blue one
Component.onCompleted: {
defaultStyle = myBtn.style;
myBtn.style = myButtonStyleBlue;
}
}
Component {
id: myButtonStyleBlue
ButtonStyle {
label: Text {
color: "blue"
text: control.text
}
}
}
Component {
id: myButtonStyleRed
ButtonStyle {
label: Text {
color: "red"
text: control.text
}
}
}

Autocomplete and suggesstion in QML textInput element

I have a QML textInput element like this:
TextBox.qml
FocusScope {
id: focusScope
property int fontSize: focusScope.height -30
property int textBoxWidth: parent.width * 0.8
property int textBoxHeight: 45
property string placeHolder: 'Type something...'
property bool isUserInTheMiddleOfEntringText: false
width: textBoxWidth
height: textBoxHeight
Rectangle {
width: parent.width
height: parent.height
border.color:'blue'
border.width: 3
radius: 0
MouseArea {
anchors.fill: parent
onClicked: {
focusScope.focus = true
textInput.openSoftwareInputPanel()
}
}
}
Text {
id: typeSomething
anchors.fill: parent; anchors.rightMargin: 8
verticalAlignment: Text.AlignVCenter
text: placeHolder
color: 'red'
font.italic: true
font.pointSize: fontSize
MouseArea {
anchors.fill: parent
onClicked: {
focusScope.focus = true
textInput.openSoftwareInputPanel()
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
focusScope.focus = true
textInput.openSoftwareInputPanel()
}
}
TextInput {
id: textInput
anchors {
right: parent.right
rightMargin: 8
left: clear.right
leftMargin: 8
verticalCenter: parent.verticalCenter
}
focus: true
selectByMouse: true
font.pointSize: fontSize
}
Text {
id: clear
text: '\u2717'
color: 'yellow'
font.pointSize: 25
opacity: 0
visible: readOnlyTextBox ? false : true
anchors {
left: parent.left
leftMargin: 8
verticalCenter: parent.verticalCenter
}
MouseArea {
anchors.fill: parent
onClicked: {
textInput.text = ''
focusScope.focus = true;
textInput.openSoftwareInputPanel()
}
}
}
states: State {
name: 'hasText'; when: textInput.text != ''
PropertyChanges {
target: typeSomething
opacity: 0
}
PropertyChanges {
target: clear
opacity: 0.5
}
}
transitions: [
Transition {
from: ''; to: 'hasText'
NumberAnimation {
exclude: typeSomething
properties: 'opacity'
}
},
Transition {
from: 'hasText'; to: ''
NumberAnimation {
properties: 'opacity'
}
}
]
}
I want to add autocomplete and suggestions like google search to this text box. Autocomple get data from database and database return a list of dictionaries by a pyside SLOT.(or c++ slot)
How I can do this work?
Take a look at this code: https://github.com/jturcotte/liquid/blob/master/qml/content/SuggestionBox.qml
I bet it will do the job.
Edit:
Code that linked above is somewhat complicated and requires C++ backend, so I simplified it and made pure Qml example application, that you can play with, edit a little and apply to your needs. Sources can be found here. Most important things there are:
This implementation of SuggestionBox that uses some sort of model as it's source for completing/suggesting something
Its signal itemSelected(item) will be emitted every time user clicks on item
Main component of application that binds its LineEdit component to SuggestionBox
Note that code is quite rough and written for a sake of example.
I was looking for something very similar: a QML autocomplete component built around QML TextField, rather than the lower-level, more flexible but also more work intensive TextInput as in the question.
Since I could not find that, I implemented it. If anyone wants to use it: it's licensed under MIT and available as part of an application I am developing. You find the component in src/qml/AutoComplete.qml, and the application may serve as usage example. Features:
highlighting of autocompleted characters in bold, as in Google Search
Key bindings (navigating with arrow keys, Return / Enter, Esc to close completion box, Esc Esc to unfocus)
uses a simple QStringList as model for now, with the application showing how to update the model with live SQL database queries when the next key is pressed
heavily documented code, so it should be easy enough to adapt
Let me know if this is useful, I might then package it as a Qt QPM package or even try to make it mature enough to be added to the QML UI library KDE Kirigami.