I am making a simple playlist player on qml. I mean there is a Audio player which plays files with extension .mp3 in a folder. But this "playlist player" assumes the whole folder as a playlist. So I give the path of the playlist folder as a command line argument to the program, for ex. ./playlist_player /home/user/playlist-folder and program plays whole mp3s in the playlist-folder folder. But since qml does not understands wildchars like asterisk(*), I use QDir to find the names of the mp3s to find the names of the mp3s and expose those strings to qml by using an approach which described in here http://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html. So I have an QObject-derived class names FileNames and it has Q_PROPERTY(QStringList mp3List READ mp3List). So on the constructor of the FileNames I look for the path given in command line, and detect for files with extension .mp3s and push_back those paths to the FileNames::mp3List. And on main.cpp, I instantiate a FileNames object after that instantiate QQuickView object and I pass the FileNames object to Qml side with QQmlContext::setContextProperty member function.
Everything works until that I need also the number of the mp3s in the list to iterate all over with next function which increment index value for the list on qml side. I know I can expose another property for passing count of the mp3List but I ended up with this solution might not be the best native one.
Here is the code that I have wrote;
/* filenames.h */
class FileNames : public QObject {
Q_OBJECT
Q_PROPERTY(QStringList mp3List READ mp3List)
Q_PROPERTY(int mp3ListCount READ mp3ListCount)
public:
explicit FileNames(QObject *parent = 0);
QStringList mp3List() const;
int mp3ListCount() const;
private:
QStringList m_mp3List;
int m_mp3ListCount;
};
/* filenames.cpp */
FileNames::FileNames(QObject *parent) :
QObject(parent), m_mp3ListCount(0)
{
QString path("/home/user/music/");
QDir dirname(path);
QStringList dir = dirname.entryList();
for (const auto &file : dir)
if (file.endsWith(".mp3")) {
m_mp3List.push_back("file://" + path + file);
++m_mp3ListCount;
}
}
QStringList FileNames::mp3List() const
{
return m_mp3List;
}
int FileNames::mp3ListCount() const
{
return m_mp3ListCount;
}
/* main.cpp */
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
FileNames names;
QQuickView view;
view.engine()->rootContext()->setContextProperty("names", &names);
view.setSource(QUrl(QStringLiteral("qrc:///main.qml")));
view.setResizeMode(QQuickView::SizeRootObjectToView);
view.showFullScreen();
return app.exec();
}
/* Playlist.qml */
Item {
id: root
property int index: 0
property MediaPlayer mediaPlayer
property variant list;
property int listCount;
function setIndex(i)
{
console.log("setting index to: " + i);
index = i;
if (index < 0 || index >= listCount) {
index = 0;
mediaPlayer.source = "";
}
mediaPlayer.source = list[index];
}
function next()
{
setIndex(index + 1);
}
function previous()
{
setIndex(index + 1);
}
Connections {
target: root.mediaPlayer
onStopped: {
if (root.mediaPlayer.status == MediaPlayer.EndOfMedia)
{
root.next();
root.mediaPlayer.play();
}
}
}
}
/* main.qml */
Rectangle {
id: root
width: 1024
height: 600
color: "black"
Playlist {
id: playlist
mediaPlayer: player
list: names.mp3List
listCount: names.mp3ListCount
}
MediaPlayer {
id: player
}
VideoOutput {
anchors.fill: parent
source: player
}
}
So does anyone has more native solution to making a "playlist_player" application with Qt?
UPDATE ->
So right now I am using FolderListModel but seems like this class does not work properly without a view. I guess because it works asynchronously. Here is how is my code look like;
/* Playlist.qml */
Item {
id: root
property int index: 0
property MediaPlayer mediaPlayer
property FolderListModel fm
function setIndex(i)
{
index = i;
console.log("setting index to: " + i);
index %= fm.count;
mediaPlayer.source = "file://" + fm.get(index, "filePath");
console.log("setting source to: " + mediaPlayer.source);
}
function next()
{
setIndex(index + 1);
}
function previous()
{
setIndex(index + 1);
}
Connections {
target: root.mediaPlayer
onStopped: {
if (root.mediaPlayer.status == MediaPlayer.EndOfMedia) {
root.next();
root.mediaPlayer.play();
}
}
}
}
/* main.qml */
Rectangle {
id: root
width: 1024
height: 600
color: "black"
property bool onStart: true
Playlist {
id: playlist
mediaPlayer: player
fm: FolderListModel {
id: fm
folder: "file:///home/user/music"
showDirs: false
showDotAndDotDot: false
nameFilters: ["*.mp3"]
property bool ready: count > 0
// startup initialization;
onReadyChanged: if (player.status == MediaPlayer.NoMedia) {
playlist.setIndex(0);
player.play();
}
}
}
MediaPlayer { id: player }
VideoOutput {
anchors.fill: parent
source: player
}
}
Thank you,
Sina.
Using C++ types in QML or defining context properties is perfectly fine when integration with C++ code is needed. C++ naturally extends the possibilities of QML and it is only a matter of understand when a C++ back-end class, or a newly QML type written in C++, is needed. For further discussion about this topic please have a look to this other answer.
Given the answer linked above and the lifetime of your playlist component, using a QML-type instead of a context property would be the right choice. However, as said, QML already provide the right component for you: FolderListModel. In your case it can be easily defined like this:
FolderListModel {
id: folderModel
nameFilters: ["*.mp3"]
showDirs: false
folder: "file:" + /* CPP PROVIDED PATH */
}
and you can use get function to query for the next mp3 in the list, according to the count property.
A great advantage is that FolderListModel does listen for file changes in your folder so that you don't need to check for file addition/removal. Last, but not least, FolderListModel can be easily integrated in a GUI implementation, using it as a model for a ListView. Please see here for an example.
Addendum
As noted by OP the FolderListModel works asynchronously. That's a common behaviour since doing all the checks during component initialisation could lead to a long start-up (especially for directories full of files). There's no property to track when FolderListModel has finished tracking the filesystem, nor it probably makes sense to have one since the filesystem check is continuous. Adding a bool property could help, something like this:
FolderListModel {
id: fm
folder: "..."
showDirs: false
showDotAndDotDot: false
nameFilters: ["*.mp3"]
property bool ready: count > 0
}
When ready is true (because mp3s have been added or the initial scanning has finished) it could be possible to trigger the player. Clearly other properties can be added to improve the behaviour of the model.
The FolderListModel QML Type will do what you need. It supports name filtering, etc.
Related
I have 3 nested ListViews.The first is the month, second is day and third is hour of day.All of them constructs a calendar.There is a loader that loads a window when I click in the hour ListView, and set text in a Label.This text can be displayed in the hour view and can be deleted or edited through the above window .Unfortunately the text can not be saved because when I scroll the ListViews, delegates changing and not keeping the context of the label(That is normal I suppose).The objective is to be able to save those texts in the label(store the data) and restore them when the application is closed and re-opened.
Below is a generic code sample for 3 ListViews:
ApplicationWindow{
id:appwindow
............
Item{
id:dayView
...........
ListView{
id:monthofdayCalendar
orientation:Qt.Horizontal
model:12
delegate: Item{
ListView{
id:dayCalendar
orientation: Qt.Horizontal
model:32
delegate: Item{
...............
ListView{
id:daylistView
orientation: Qt.Vertical
model:24
delegate:Item{
id:hourItem
property string hourTime:hourweeklistviewLabel
property string notetaking:notesLabe
.............
MouseArea{
anchors.fill:parent
onClicked:{
windowLoader.active =true
daylistView.currentIndex=index
}
}
Rectangle{}
Label{
id:hourweeklistviewLabel
}
Label{
id:notesLabel
anchors.left:hourweeklistviewLabel.right
anchors.leftMargin: 30
text:""
}//Label
}//delegate:Item
}//ListView
} //delegate:Item
}//ListView
}//delegate:Item
}//Listview
}//Item
Below is the code of loader:
Loader {
id:windowLoader
focus: true
active:false
sourceComponent: Window{
id:inputWin
title:"Enter Note"
width:500
height:300
visible:true
onClosing:{
windowLoader.active=false
monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentIndex = calendarMonth.selectedDate.getDate() === new Date().getDate()
&& calendarMonth.selectedDate.getDay() === new Date().getDay()
&& calendarMonth.selectedDate.getMonth() === new Date().getMonth()?getHour():12
}
TextField {
id:title
x:50
y:20
placeholderText :'Enter Note'
text:monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.notetaking.text
}
TextField{
id:timeDate
anchors.horizontalCenter: title.horizontalCenter
anchors.top:title.bottom
anchors.topMargin:10
placeholderText : calendarMonth.selectedDate.getDate() +"-"
+ (calendarMonth.selectedDate.getMonth()+1)+"-"
+ calendarMonth.selectedDate.getFullYear() + " "
+ monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.hourTime.text
}
Button {
id: button
text: qsTr("Add Note")
anchors.centerIn:parent
onClicked: {
if (title.text !==""){monthofdayCalendar.currentItem.daycalendarAlias.currentItem.dayList.currentItem.notetaking.text= title.text}
else{}
}
}
}
}
The big question is how to save (store) the data of notesLabel.text and be able to display it and restore it every time I close and re-open the application.
As you can see the model for each ListView is not a ListModel so I think I can not use those models to save the data if I am right.If I am wrong please advise.
Anyway your help will be appreciateed.
EDIT
I've changed the integer models with ListModel dynamically created.The code of the ListModels is below:
ListModel{
id:hourlistModel
Component.onCompleted:{
for (var i = 0; i <25; i++){
append(createListElement())
}
}
property int h:0
function createListElement(){
return {
hour : h++
}
}
}
ListModel{
id:daylistModel
Component.onCompleted:{
for (var j=0; j <= 31; j++){
append(createListElement())
}
}
property int dD:0
function createListElement(){
return {
day : dD++
}
}
}
ListModel{
id:monthlistModel
Component.onCompleted:{
for (var k=0; k <=11; k++){
append(createListElement())
}
}
property int mN:0
function createListElement(){
return {
monthName : mN++
}
}
}
Can I store the data from Label notesLabel, now I've changed the models of ListViews with ListModels?
Thanks in advance.
I would create an exposed C++ class.
Using the exposed C++ class you have a range of options to pass data from the front/backend
Q_Property/Member
Q_Invokable
Signal/Slots
Given that you are using strictly strings, I would use an exposed series of QStrings or a QStringList.
QString
QStringList
To tackle to file read/write use your now exposed C++ class. You can either stick to file I/O via standard c++ or QFile system.
Constructor - Read the .txt file and save the data to your property data.
Exchange data as needed updating either the QML or C++ property member
Deconstructor - save the property member data back to file.
Brief example code:
someQML.qml
Import MyExposedClass 1.0
Item {
MyExposedClass {
id: myExposedClassID
text: myExposedClassID
}
Text{
id: yearTextID
text: myExposedClassID.year
}
Text{
id: monthTextID
text: myExposedClassID.month
}
Text{
id: dayTextID
text: myExposedClassID.day
}
Button {
id: myButtonID
onButtonPressed {
var finalStr = yearTextID + monthTextID + dayTextID
// If you used a QMember of qstring lets say
myExposedClassID.saveFile = finalStr
// If you used a QInvokable
myExposedClassID.saveFile_INVOK(finalStr)
}
}
}
myClass.h
class myClass : public QObject {
Q_OBJECT
// Serial Dev
Q_PROPERTY(QString day READ getDay WRITE setDay NOTIFY dayChanged)
Q_PROPERTY(QString month READ ... WRITE ... NOTIFY ...)
Q_PROPERTY(QString year READ ... WRITE ... NOTIFY ...)
...
// On construction, read the text file and update the qproperty variables
//implement getters and setters for qproperties
// On deconstruction, write the properties to file
}
If you have issues with MVC and QStrings/QStringlist. You may have to look into QVariants or QAbstracts.
I find QML to be a risky hole to dive into. Its easy to add more and more functionality to a QML file. But if you try and redesign it or change up some logic, it can very quickly ruin the QML. Splitting the QML into a QML & C++ is a nice way to achieve modularity and control.
I want to highlight the current present text in TextField of QML as shown in the below added image.
I know about selectAll but the problem with that is that when new texts are added, previous texts disappear. I know that probably with TextEdit and TextArea you can probably do that using QQuickTextDocument but with those two I don't have the option to restrict what input it will take using something something like this
validator: IntValidator { bottom:lowestInput; top: highestInput}
So, is there anyway I can highlight the text in TextField without selectAll?
Thanks.
When you use selectAll, it will work as if you have selected the text with the mouse. So, when the user will write something else, the selected text will be removed.
You need to highlight the text instead of select it.
Use the QSyntaxHighlither class and a TextField element to highlight a pattern.
First, you need to create a new class that inherits from QSyntaxHighlighter and redefine the method highlightBlock().
You also have to create a new method which is invokable from QML. A TextEdit uses a QQuickTextDocument as text document. You need to get the QTextDocument in it:
class SyntaxHighlighter: public QSyntaxHighlighter
{
Q_OBJECT
public:
SyntaxHighlighter(QTextDocument* parent=nullptr): QSyntaxHighlighter(parent)
{}
protected:
void highlightBlock(const QString &text) override
{
QTextCharFormat format;
format.setFontWeight(QFont::Bold);
format.setForeground(Qt::white);
format.setBackground(Qt::darkBlue);
QString const pattern("PT 36631");
int index = text.indexOf(pattern);
while (index != -1)
{
setFormat(index, pattern.length(), format);
index = text.indexOf(pattern, index + 1);
}
}
Q_INVOKABLE void setDocument(QQuickTextDocument* document)
{
QSyntaxHighlighter::setDocument(document->textDocument());
}
};
Then, set the highlighter as a new context property in your QML:
SyntaxHighlighter* highlighter = new SyntaxHighlighter;
QQuickView *view = new QQuickView;
view->rootContext()->setContextProperty("highlighter", highlighter);
view->setSource(QUrl("qrc:/main.qml"));
view->show();
Now, you can use it in your TextEdit:
TextEdit {
id: editor
anchors.fill: parent
Component.onCompleted: {
highlighter.setDocument(editor.textDocument)
}
}
If you only need to check if the input is an integer in a range, you could use the signal textChanged to validate the value.
A quick example:
TextEdit {
id: editor
anchors.fill: parent
Component.onCompleted: {
highlighter.setDocument(editor.textDocument)
}
QtObject {
id: d
property string validatedText: ""
}
onTextChanged: {
var lowestInput = 0;
var highestInput = 255;
var integerOnly = parseInt(text);
if (!integerOnly)
{
editor.text = d.validatedText
return
}
if (integerOnly < lowestInput || integerOnly > highestInput)
{
editor.text = d.validatedText
return
}
d.validatedText = editor.text
}
}
Prologue
QAbstractListModel might be a solution, but I think it is a bit overkill for me ... not sure
Intro
I'm making a vector inside C++ to be accessible from within QML:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QVector<QString> items READ items WRITE setItems NOTIFY itemsChanged)
// ...
}
As I have tested, I can access my vector from within QML:
console.log("myClass.items >>> ", myClass.items) // Logs vector of strings
Question
On my QML UI, I intend to show a list of the strings inside the vector. I try to use ListModel and ListView but I don't know how to do it. Can anybody help?
ListModel {
id: myListModel
// How to compose my model here according to vector of strings
// i.e. myClass.items
}
ListView {
model: myListModel
delegate: {
// ... show the strings inside the vector
}
}
UPDATE
As suggested by #Amfasis and #mohabouje, I modified my code like this, but it is not working for some reason.
C++ side:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(QStringList items READ items WRITE setItems NOTIFY itemsChanged)
//...
}
QML side:
Row {
ListView {
id: items
model: myClass.items // Directly connect to C++ string list
delegate: Text {
text: modelData // Strings are NOT displayed on QML UI
// Log of modelData shows it is empty
}
}
}
On QML I log myClass.items and I see my string list:
console.log("myClass.items >>> ", myClass.items)
// logs:
// qml: myClass.items >>> [item 0,item 1,item 2]
Finally Fixed
When setting or modifying items, I need to emit the modification signal otherwise it won't work:
m_items = /* set items here */;
emit itemsChanged(m_items); // This is required!
You should expose your class interface to the QML engine. In this case, you can replace your QVector<QString> by an QStringList.
class MyModel: public QObject {
Q_OBJECT
Q_PROPERTY(QStringList model READ model NOTIFY modelChanged)
...
};
If you take a look into the docs:
Models that do not have named roles (such as the ListModel shown below) will have the data provided via the modelData role. The modelData role is also provided for models that have only one role. In this case the modelData role contains the same data as the named role.
So, you should use the property modelData:
ListView {
model: mymodel.model
delegate: Text {
text: modelData
}
}
To expose your C++ class, take a look into this page.
I have a gauge.qml as shown below
Gauge.qml
Item {
property real gaugevalue //this determine the gaugevalue.
width: 100
height: 100
Rectangle {
Image {
x: 0
y: 0
source: "qrc:/gaugebg.png"
}
Image {
x: 50
y: 49
source: "qrc:/needle.png";
rotation: 0
// I am doing some rotation logic with gaugevalue. please ignore logic for now
transform: Rotation {
origin.x: 10; origin.y:10;
angle: gaugevalue
}
}
}
}
Also, I have a main.qml as shown below, in which I have placed my gauge
main.qml
Window {
id: main
width: 640
height: 480
//gauge position
property int gaugeX: 50
property int gaugeY: 60
Gauge {
id : id_gauge
gaugevalue : 50 //Now I am setting the gaugevalue from here. I need this to be controlled from my GaugeController class.
x:gaugeX
y:gaugeY
}
}
What I am trying to achieve is make my code fit into MVC architecture. I am planning to write a 'GaugeController' class in C++ which will set the 'gaugevalue' (and more properties later). I really confused by reading many articles which leads to think in different way (I see many solution like using Q_PROPERTY or setProperty etc. (refer to this link : https://www.zhieng.com/exposing-qml-object-pointer-to-cc/). So It would be great if you could correct my code to achieve this in MVC architecture.
using qmlRegisterType<>() and then create an instance of GuageController in your QML file which can be used to control Guage by passing values via signals/slots
main.cpp
qmlRegisterType<GaugeController>("myApp", 1, 0, "GaugeController");
main.qml
import myApp 1.0
...
GaugeController {
id: id_controller
// heres how you implment a method as a SLOT via QML
onGaugeValueChanged(gaugeValue): {
id_gauge.gaugeValue = id_controller.gaugeValue
}
}
gaugeController.cpp
class GaugeController : public QQuickItem
{
Q_OBJECT
// Q_PROPERTY MACRO allows the property to be read by QML
Q_PROPERTY(int gaugeValue READ gaugeValue WRITE setGaugeValue NOTIFY gaugeValueChanged)
public:
int gaugeValue() { return _gaugeValue; };
signals:
void gaugeValueChanged();
public slots:
void setGaugeValue(newVal) { _gaugeValue = newVal; }
private:
int _gaugeValue;
** EDIT **
from main.qml to pass parameters to GuageController
GaugeController {
id: controller
gaugeValue: 10
}
// or
function myfunction { controller.gaugeValue = 5 }
On a QML Map, using "onCenterChanged" to capture a user-activated move, the filtering process of the points to be displayed on, starts as soon as the move is initiated.
Given the large number of data to be processed during this operation, I want it to begin only after the total stabilization of the Map (stop sliding/zooming, left mouse button released and mouse wheel inactive).
here is a snippet of the QML Map
Map {
id: mainMap
anchors.centerIn: parent;
anchors.fill: parent
plugin: Plugin {name: "osm"}
center: startingPoint
zoomLevel: 4.5
onCenterChanged: {
updateBoundingBox()
}
MapItemView {
id:viewPointOnMap
model: navaidsFilter
delegate: Marker{}
}
onMapReadyChanged: {
updateBoundingBox()
}
function updateBoundingBox(){
navaidsFilter.bBox = mainMap.visibleRegion.boundingGeoRectangle() //boundingBox
}
}//Map
and the filter snippets :
void NavaidsFilter::setBBox(const QGeoRectangle &bbox)
{
if(m_processedZone.isEmpty()|| !m_processedZone.contains(bbox)){ //First bbox or displacement/zoom out of the previous box
m_processedZone = bbox;
m_boundaryZone = bbox;
invalidateFilter();
}
}
bool NavaidsFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
if(!m_boundaryZone.isValid()){
return false;
}
QModelIndex ix = sourceModel()->index(sourceRow, 0, sourceParent);
QGeoCoordinate pos = ix.data(NavaidsModel::PositionRole).value<QGeoCoordinate>();
return m_boundaryZone.contains(pos);
}
How can we achieve this?
Thanks for help
I have never worked with Map so I don't know a specific solution. Here are two general solutions that work in many cases:
Introduce a property that aggregates all states that are relevant
readonly property bool moving: mainMap.gesture.rotationActive
| mainMap.gesture.tiltingActive
| ...`
once this property changes to false do what is necessary. (onMovingChanged)
Use a Timer that you restart, whenever something changes. If for some ms nothing changes, let it trigger and do what is necessary.