How to highlight the clicked (by mouse) element of a delegate w.r.t FolderListModel? - qml

import QtQuick 2.0
import Qt.labs.folderlistmodel 2.0
Item
{
Component {
id: highlight
Rectangle {
id: rooot
width: 180; height: 20
color: ListView.isCurrentItem ? "black" : "red"; radius: 5
y: list.currentItem.y
Behavior on y {
SpringAnimation {
spring: 3
damping: 0.2
}
}
}
}
ListView {
id: list
width: 480; height: 400
model: folderModel
delegate: Text { id: h; text: fileName }
highlight: highlight
highlightFollowsCurrentItem: false
focus: true
}
FolderListModel
{
id: folderModel
folder: "/home/anisha/"
nameFilters: ["*"]
}
}
This works only when I use keyboard. How to make it work on mouse clicks?

To react on mouse events you need to place MouseArea item.
In the sample below (being an expanded version of the code you provided) I have added a MouseArea to the delegate item that upon being clicked sets the ListView's currentIndex to the delegate's index (a special property visible in the ListView's delegate).
import QtQuick 2.0
import Qt.labs.folderlistmodel 2.0
Item
{
Component {
id: highlight
Rectangle {
id: rooot
width: 180; height: 20
color: ListView.isCurrentItem ? "black" : "red"; radius: 5
y: list.currentItem.y
Behavior on y {
SpringAnimation {
spring: 3
damping: 0.2
}
}
}
}
ListView {
id: list
width: 480; height: 400
model: folderModel
delegate:
Text {
id: h;
text: fileName
MouseArea {
anchors.fill: parent
onClicked: list.currentIndex = index
}
}
highlight: highlight
highlightFollowsCurrentItem: false
focus: true
}
FolderListModel
{
id: folderModel
folder: "/home/anisha/"
nameFilters: ["*"]
}
}
As an alternative approach you might try placing a single MouseArea filling the whole ListView and use ListView's indexAt(int x, int y) method to check which delegate was clicked. However, you would need to care about more edge-conditions in such case.

Related

TableView is invisible

I'm trying to set something up, where there's a a HorizontalHeaderView that spans the entire width of the window, and a TableView that also spans the entire width of the window, along with stretching down to the bottom of the window. I also wanted a TextField and Button side by side, to sit on top of all this.
So far, I've gotten the TextField and Button to sit at the top, and part of the HorizontalHeaderView to be visible, but the TableView is completely invisible. I've tried fiddling around with height, Layout.preferredHeight, etc. but nothing makes it stretch to the bottom and fill the width of the window.
Here's what I've got so far:
import QtQuick 6.0
import QtQuick.Controls.Basic 6.0
import QtQuick.Layouts 6.0
ApplicationWindow {
title: "Portmod"
width: 640
height: 480
visible: true
header: TabBar {
id: mainTabBar
width: parent.width
TabButton {
text: "Manage"
width: implicitWidth
}
TabButton {
text: "Search"
width: implicitWidth
}
}
StackLayout {
id: mainStackLayout
currentIndex: mainTabBar.currentIndex
ColumnLayout {
id: manageTab
RowLayout {
TextField {
placeholderText: "Filter packages..."
}
Button {
text: "Filter"
}
}
HorizontalHeaderView {
id: installedPackagesHeader
syncView: installedPackagesTable
}
TableView {
id: installedPackagesTable
columnSpacing: 1
rowSpacing: 1
clip: true
model: installedPackagesModel
Keys.onUpPressed: installedPackagesTableVerticalScrollBar.decrease()
Keys.onDownPressed: installedPackagesTableVerticalScrollBar.increase()
Keys.onLeftPressed: installedPackagesTableHorizontalScrollBar.decrease()
Keys.onRightPressed: installedPackagesTableHorizontalScrollBar.increase()
ScrollBar.vertical: ScrollBar {
id: installedPackagesTableVerticalScrollBar
parent: installedPackagesTable
}
ScrollBar.horizontal: ScrollBar {
id: installedPackagesTableHorizontalScrollBar
parent: installedPackagesTable
}
delegate: Rectangle {
implicitWidth: 300
implicitHeight: 25
Text {
text: display
anchors.left: parent.left
}
}
}
}
Rectangle {
id: searchTab
width: parent.parent.width
height: parent.parent.height
}
}
}
Here's my custom QSortFilterProxyModel and QAbstractItemModel:
class InstalledPackagesProxyModel(QSortFilterProxyModel):
def __init__(self, data: list[list]) -> None:
super().__init__()
self.realModel = InstalledPackagesModel(data)
self.setSourceModel(self.realModel)
def get_atom(self, index: QModelIndex) -> Atom:
"""
Returns a reference to the source index instead of proxy index, to handle the sorted view.
"""
mapped_index = self.mapToSource(index)
data = [tuple(pkg_data_list) for pkg_data_list in self.realModel._data]
return Atom(f"{data[mapped_index.row()][2]}/{data[mapped_index.row()][0]}")
class InstalledPackagesModel(QAbstractTableModel):
def __init__(self, data: list[list]):
super(InstalledPackagesModel, self).__init__()
self._data = data
self.header_labels = ["Name", "Use Flags", "Category", "Version"]
def data(self, index, role):
if role == Qt.DisplayRole: # type: ignore
value = self._data[index.row()][index.column()]
if isinstance(value, Atom):
return str(value)
return value
def rowCount(self, index):
return len(self._data)
def columnCount(self, index):
try:
return len(self._data[0])
# If there are no installed mods in the prefix
except IndexError:
return 1
def headerData(self, section, orientation, role=Qt.DisplayRole): # type: ignore
if role == Qt.DisplayRole and orientation == Qt.Horizontal: # type: ignore
return self.header_labels[section]
return QAbstractTableModel.headerData(self, section, orientation, role)
This is what it looks like:
You need to use anchors.fill: parent on the StackView so it fills out its parent and has a proper size. Furthermore the HorizontalHeaderView needs to get a implicitHeight and the TableView should set Layout.fillWidth and Layout.fillHeight to true.
You need to use Layout.fillWidth and Layout.fillHeight to span to the full width and height of layouts.
import QtQuick 6.0
import QtQuick.Controls.Basic 6.0
import QtQuick.Layouts 6.0
import Qt.labs.qmlmodels 1.0
ApplicationWindow {
title: "Portmod"
width: 640
height: 480
visible: true
header: TabBar {
id: mainTabBar
width: parent.width
TabButton { text: "Manage" }
TabButton { text: "Search" }
}
StackLayout {
id: mainStackLayout
currentIndex: mainTabBar.currentIndex
anchors.fill: parent
ColumnLayout {
id: manageTab
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
TextField {
Layout.fillWidth: true
placeholderText: "Filter packages..."
}
Button {
Layout.fillWidth: true
text: "Filter"
}
}
HorizontalHeaderView {
id: installedPackagesHeader
syncView: tableView
implicitHeight: 30
Layout.fillWidth: true
}
TableView {
id: tableView
Layout.fillWidth: true
Layout.fillHeight: true
columnSpacing: 1
rowSpacing: 1
clip: true
columnWidthProvider: function (column) {
return tableView.model ? tableView.width / tableView.model.columnCount : 0
}
rowHeightProvider: function (row) { return 30 }
onWidthChanged: tableView.forceLayout()
model: TableModel {
TableModelColumn { display: "name" }
TableModelColumn { display: "color" }
rows: [
{ "name": "cat", "color": "black" },
{ "name": "dog", "color": "brown" },
{ "name": "bird", "color": "white" }
]
}
delegate: Rectangle {
Text {
text: display
anchors.left: parent.left
}
}
}
}
Rectangle {
id: searchTab
Layout.fillWidth: true
Layout.fillHeight: true
color: "red"
}
}
}
I figured out that I needed to set the columnWidthProvider to this:
return Window.width / installedPackagesTableView.model.columnCount()
This is my full code for the TableView:
TableView {
id: installedPackagesTableView
property int selectedRow: 0
focus: true
columnSpacing: 1
rowSpacing: 1
clip: true
model: installedPackagesModel
columnWidthProvider: function () {
return Window.width / installedPackagesTableView.model.columnCount()
}
rowHeightProvider: function (row) {
return 30;
}
onWidthChanged: installedPackagesTableView.forceLayout()
Layout.fillWidth: true
Layout.fillHeight: true
Keys.onUpPressed: function () {
if (selectedRow != 0) {
selectedRow -= 1;
}
// Move scrollbar up if the selectedRow is going to be invisible
if (selectedRow == topRow) {
installedPackagesTableVerticalScrollBar.decrease()
}
}
Keys.onDownPressed: function () {
if (selectedRow != installedPackagesModel.rowCount() - 1) {
selectedRow += 1;
// Move scrollbar down if the selectedRow is going to be invisible
if (selectedRow == bottomRow) {
installedPackagesTableVerticalScrollBar.increase()
}
}
}
Keys.onReturnPressed: installedPackagesModel.getAtom(selectedRow)
ScrollBar.vertical: ScrollBar {
id: installedPackagesTableVerticalScrollBar
}
ScrollBar.horizontal: ScrollBar {
id: installedPackagesTableHorizontalScrollBar
}
delegate: ItemDelegate {
highlighted: row == installedPackagesTableView.selectedRow
onClicked: installedPackagesTableView.selectedRow = row
onDoubleClicked: installedPackagesModel.getAtom(row)
text: model.display
}
}

How can I make a custom delegate for my ListView?

I'm trying to make a custom ListView (AppDataListView) that can be used for data manipulation (essentially having a bunch of inputs). The problem I'm having is finding a way to tell AppDataListView what input types to use in the delegate of the ListView.
I currently try and do this by creating a loader in the ListView delegate, setting its source to a property of type Component, and then when I create an instance of AppDataListView, I specify the Component... However, I don't have any access to the model data, so it's kind of pointless.
Does anyone know how I can accomplish this?
Main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick .Controls 2.12
import QtQuick.Layouts 1.12
Window {
width: 640
height: 480
visible: true
ListModel {
id: sampleData
ListElement {
itemId: 1
name: "Name1"
}
ListElement {
itemId: 2
name: "Name2"
}
ListElement {
itemId: 3
name: "Name3"
}
ListElement {
itemId: 4
name: "Name4"
}
}
AppDataListView {
anchors.fill: parent
headers: ["ID", "Name"]
model: sampleData
delegate: Component {
RowLayout {
anchors.fill: parent
TextArea {
// I can't access itemId from here even though this is loaded into the delegate.
text: itemId
}
TextArea {
// I can't access name from here even though this is loaded into the delegate.
text: name
}
}
}
}
}
AppDataListView.qml
import QtQuick 2.0
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
Rectangle {
property var headers: []
property alias model: listView.model
property alias listView: listView
required property Component delegate;
id: root
color: "#bdbdbd"
ColumnLayout {
anchors.fill: parent
anchors.margins: 1
spacing:0
RowLayout {
spacing: 1
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Item {
Layout.minimumWidth: 30
Layout.maximumWidth: 30
}
Repeater {
id: headerRepeater
model: headers
delegate: Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
gradient: Gradient {
GradientStop { position: 0.0; color: "#FFFFFF" }
GradientStop { position: 0.5; color: "#F1F1F1" }
GradientStop { position: 1.0; color: "#FFFFFF" }
}
Label {
text: modelData
padding: 5
anchors.centerIn: parent
}
}
}
}
ListView {
id: listView
Layout.fillWidth: true
Layout.fillHeight: true
interactive: true
clip: true
boundsBehavior: Flickable.StopAtBounds
spacing: 1
ScrollBar.vertical: ScrollBar {
active: true
policy: ScrollBar.AlwaysOn
}
delegate: RowLayout {
width: parent.width
spacing: 1
Button {
id: rowBtn
Layout.minimumWidth: 30
Layout.maximumWidth: 30
background: Rectangle {
gradient: Gradient {
GradientStop { position: 0.0; color: (rowBtn.down ? "#56aff5" : rowBtn.hovered ? "#d9ebf9" : "#FFFFFF") }
GradientStop { position: 0.5; color: (rowBtn.down ? "#1b93f1" : rowBtn.hovered ? "#a4b2bd" : "#F1F1F1") }
GradientStop { position: 1.0; color: (rowBtn.down ? "#56aff5" : rowBtn.hovered ? "#d9ebf9" : "#FFFFFF") }
}
}
}
Loader {
sourceComponent: root.delegate
}
}
}
}
}
Capturing #Amfasis excellent comments to the question and adding some more detail....
Change your Loader reference to this:
Loader {
sourceComponent: root.delegate
property int itemId: model.itemId
property string name: model.name
}
fixes it. Here's why....
A Component declaration is put into the QML namespace hierarchy where it is declared not where it is instantiated at runtime. In other words, by declaring the delegate within main.qml, it can only see that namespace regardless of where it is instantiated at (in this case in AppDataListView).
More info here:
https://doc.qt.io/qt-5/qml-qtqml-component.html#creation-context
As #Amfasis pointed out, the workaround in this case is to declare properties on the Loader to pass in the model references you need. In this case the Loader acts as a bridge of sorts from the ListView namespace over to the delegate Component's namespace in main.qml.
More info on that here:
https://doc.qt.io/qt-5/qml-qtquick-loader.html#using-a-loader-within-a-view-delegate

How to animate scroll in a QML ScrollView?

How can I animate scrolling in a QML ScrollView?
I've tried a Behavior on the contentItem.contentY, but that isn't working.
With Qt Quick Controls 1
You just have to animate the value changes on the property flickableItem.contentY.
A quick example:
Item {
anchors.fill: parent
ColumnLayout {
anchors.fill: parent
Button {
id: btn
onClicked: scroll.scrollTo(scroll.flickableItem.contentY + 100)
}
ScrollView {
id: scroll
function scrollTo(y) {
scrollAnimation.to = y
scrollAnimation.start()
}
NumberAnimation on flickableItem.contentY {
id: scrollAnimation
duration: 1000
}
contentItem: Column {
Repeater {
model: 30
Rectangle {
width: 100; height: 40
border.width: 1
color: "yellow"
}
}
}
}
}
}
When you click on the button, it will scroll by 100 px with a smooth jump.
With Qt Quick Controls 2
The flickableItem.contentY isn't available anymore. The simpliest way to do the same thing in Qt Quick Controls 2 is to animate the position of the ScrollBar.
Notice that the position of QScrollBar is in percent (expressed between 0 and 1), not in pixels.
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ScrollView {
id: scroll
width: 200
height: 200
clip: true
function scrollTo(y) {
scrollAnimation.to = y
scrollAnimation.start()
}
ScrollBar.vertical: ScrollBar {
id: test
parent: scroll
x: scroll.mirrored ? 0 : scroll.width - width
y: scroll.topPadding
height: scroll.availableHeight
active: scroll.ScrollBar.horizontal.active
policy: ScrollBar.AlwaysOn
NumberAnimation on position {
id: scrollAnimation
duration: 1000
}
}
ListView {
model: 20
delegate: ItemDelegate {
text: "Item " + index
}
}
}
Button {
id: btn
anchors.top: scroll.bottom
onClicked: scroll.scrollTo(test.position + 0.1)
}
}
When you click on the button, it will scroll by 10% of the height.

QML Render the same Entity in two Scene3Ds

How can I insert the same entity to be displayed into more than one Scene3Ds in QML?
(it'll probably be abundantly clear that I'm pretty new to QML)
I would like to create something similar to the Multi Viewport QML Example, except I would like to have the viewports in differet layouts (e.g. a SplitView).
Ultimately I would like my entities (Mesh or SceneLoader entities) to be created completely outside of my views (my Scene3D), and be able to show the same data in multiple views without having copies.
I haven't had much luck putting a Layout into a Scene3D, and even so, that would constrain me against having the same data show up elsewhere. I simply can't figure out how to define my data outside the views, especially since I can't figure out how to append to the components/data/children properties.
For example, in the (long) example below, I figured out how to define torusMesh2 outside of the Scene3D by setting it's .parent to inject it into scene3DRightEntity, but I cannot figure out how to inject it into both Scene3Ds
import QtQuick 2.0
import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.3
import QtQuick.Scene3D 2.0
import Qt3D.Extras 2.0
Item {
Button {
text: 'Button'
onClicked: {
console.log('Button clicked');
}
}
SphereMesh {
id: torusMesh2; radius: 5
parent: scene3DRightEntity
}
PhongMaterial { id: material2; parent: scene3DRightEntity }
Transform { id: torusTransform2; scale3D: Qt.vector3d(1.5, 1, 0.5); rotation: fromAxisAndAngle(Qt.vector3d(1, 0, 0), 45); parent: scene3DRightEntity }
Rectangle {
id: topRect
anchors.fill: parent; anchors.margins: 50
color: 'green'
SplitView {
anchors.fill: parent; orientation: Qt.Horizontal
Rectangle {
id: scene
anchors.margins: 50; width: 200; Layout.minimumWidth: 100; Layout.maximumWidth: 500
color: "darkRed"
Text { text: "View 1"; anchors.centerIn: parent }
Scene3D {
id: scene3dLeft
anchors.fill: parent; anchors.margins: 10; focus: true
aspects: ["input", "logic"]
cameraAspectRatioMode: Scene3D.AutomaticAspectRatio
Entity {
SimpleCamera {
id: camera1; fieldOfView: 45; position: Qt.vector3d( 0.0, 0.0, 40.0 )
}
components: [
RenderSettings {
activeFrameGraph: ForwardRenderer {
camera: camera1.camera
clearColor: "transparent"
}
}
, InputSettings { }
]
TorusMesh {
id: torusMesh1; radius: 5; minorRadius: 1; rings: 100; slices: 20
}
PhongMaterial { id: material1 }
Transform { id: torusTransform1; scale3D: Qt.vector3d(1.5, 1, 0.5); rotation: fromAxisAndAngle(Qt.vector3d(1, 0, 0), 45) }
Entity {
id: torusEntity1
components: [ torusMesh1, material1, torusTransform1 ]
}
}
}
}
Rectangle {
id: scene2
Layout.fillWidth: true; Layout.minimumWidth: 50; Layout.maximumWidth: 400; height: 300
color: "darkBlue"
Scene3D {
id: scene3dRight
anchors.fill: parent; anchors.margins: 50; focus: true;
aspects: ["input", "logic"]
cameraAspectRatioMode: Scene3D.AutomaticAspectRatio
Entity {
id: scene3DRightEntity
SimpleCamera {
id: camera2
position: Qt.vector3d( 0.0, 0.0, 40.0 )
}
components: [
RenderSettings {
activeFrameGraph: ForwardRenderer {
camera: camera2.camera
clearColor: "transparent"
}
}
, InputSettings { }
]
Entity {
id: torusEntity2
components: [ torusMesh2, material2, torusTransform2 ]
}
}
}
}
}
}
} // Item
UPDATE
My first post was creating torusMesh2 as a child of the SplitView, this edit moves it to the top of the tree and sets its .parent.

Scale Element in StateELement

I can write lines below and got it work:
states: State {
name: "active"; when:myitem.activeFocus;
PropertyChanges { target: myitem; z:1000; scale: 1.2 }
}
transitions: Transition {
NumberAnimation { properties: scale; duration: 1000 }
}
But in these lines i can not give specific origin to scale property!
I found Scale Element
transform: Scale { origin.x: 25; origin.y: 25; xScale: 3}
How can i inject this into state property above, because i want to use "when" property of state,
i want scaling to run on that "when" condition.
Or is there any other way to scale in a condition with specifying origins?
Thanks for any idea.
You should set an id for the Scale element. Then you can change its properties in the "active" state.
Here a minimal working example:
import QtQuick 1.0
Item {
height: 200; width: 500
Rectangle {
id: myitem
height: 10; width: 100
anchors.centerIn: parent
color: "blue"
transform: Scale { id: scaleTransform; origin.x: 25; origin.y: 25 }
states: State {
name: "active"; when: mouseArea.pressed
PropertyChanges { target: scaleTransform; xScale: 3 }
}
transitions: Transition {
NumberAnimation { property: "xScale"; duration: 1000 }
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
}
}
Another possibility without states could be:
import QtQuick 1.0
Item {
height: 200; width: 500
Rectangle {
id: myitem
height: 10; width: 100
anchors.centerIn: parent
color: "blue"
transform: Scale {
id: scaleTransform
origin.x: 25; origin.y: 25
xScale: mouseArea.pressed ? 3 : 1 // <-
Behavior on xScale { // for animation
NumberAnimation { duration: 1000 }
}
}
}
MouseArea {
id: mouseArea
anchors.fill: parent
}
}
xScale is set to 3 when mouse is pressed, else to 1 (if you don't know the " ternary operation" look here).