QFileSystemWatcher: detects removed & added files but not modified one - c++

I'm developing an app on windows with Qt and I need to detect changes in a specific folder.
So I used a QFileSystemWatcher, and I connect the directoryChanged signal to a function that will send a message in case of changes.
The problem is that the "slot" function connected to directoryChanged is not called if I modify a file's content, but only when a file or directory is removed or added.
However, the documentation says that this signal is emitted when "the directory at a specified path, is modified (e.g., when a file is added, modified or deleted) or removed from disk."
Does anyone have an explanation?
Thanks in advance =)

According to Qt source code version 4.8.2, as following:
void QFileSystemWatcherPrivate::_q_directoryChanged(const QString &path, bool removed)
{
Q_Q(QFileSystemWatcher);
if (!directories.contains(path)) {
// perhaps the path was removed after a change was detected, but before we delivered the signal
return;
}
if (removed)
directories.removeAll(path);
emit q->directoryChanged(path);
}
It seems that directoryChanged emit when a file removed, added(for the new added file not contains in directories), and renamed. The implementation does not guarantee to detect a modification of a file's content. Hope that helps :P

Related

QFileSystemModel doesn't emit fileRenamed signal

I am trying to watch the changes in a directory using QFileSystemModel. Whenever I rename a file in the root path, only the directoryLoaded() signal is emitted. I want the fileRenamed() signal to be emitted so that I know which file is renamed to a new name. Here is my code:
model = new QFileSystemModel;
model->setRootPath("C:/test/");
QObject::connect(model, SIGNAL(fileRenamed(const QString&, const QString&, const QString&)), this, SLOT(updateRename()));
QObject::connect(model, SIGNAL(directoryLoaded(const QString&)), this, SLOT(loadDir()));
I am afraid you expect too much from this QFileSystemModel class. It does not and cannot catch if the renaming operation happens outside of the model. I looked up all uses of fileRenamed() signal and it seems that the only place where it is emitted is here: https://code.woboq.org/qt5/qtbase/src/widgets/dialogs/qfilesystemmodel.cpp.html#933
And if you go a few lines above, you can see that this is triggered when the renaming happens inside this function https://code.woboq.org/qt5/qtbase/src/widgets/dialogs/qfilesystemmodel.cpp.html#873 In other words, if you use QFileSystemModel::setData() to set a name to an item, it will rename the item and emit the signal. And it is the only way to have the signal emitted.
And this is logical, if renaming happens outside of your program, then there is no certain way to find out that a certain file was renamed. Your application only observes that some file disappeared and another file with a different name emerged. Of course, you can check whether the timestamp and size of the file is the same, whether they are also in the same parent folder, maybe also check the file content... and only if these things match and the only difference is in the file names, then you can conclude that the file was renamed. But this is something you must program yourself. QFileSystemModel will not do it for you because it does not know your specific intentions.

QT Creator - "No matching signal for" with deleted elements

I am getting these warning messages but I deleted the objects it warned me about. I am confused why it keeps bringing these messages up. Moreover, this is causing a bug where I have specified a signal for a push-button, but it does not do its function (I even set a debug log message if it were pressed).
in my moc_mainwindow.cpp file I noticed these lines of code:
static const qt_meta_stringdata_MainWindow_t qt_meta_stringdata_MainWindow = {
{
QT_MOC_LITERAL(0, 0, 10), // "MainWindow"
QT_MOC_LITERAL(1, 11, 22), // "on_radioButton_clicked"
QT_MOC_LITERAL(2, 34, 0), // ""
QT_MOC_LITERAL(3, 35, 26), // "on_exitRadioButton_clicked"
QT_MOC_LITERAL(4, 62, 21), // "on_pushButton_clicked"
QT_MOC_LITERAL(5, 84, 18) // "on_exitBtn_clicked"
},
"MainWindow\0on_radioButton_clicked\0\0"
"on_exitRadioButton_clicked\0"
"on_pushButton_clicked\0on_exitBtn_clicked"
};
which may have to do with the warning messages:
QMetaObject::connectSlotsByName: No matching signal for on_radioButton_clicked()
QMetaObject::connectSlotsByName: No matching signal for on_exitRadioButton_clicked()
QMetaObject::connectSlotsByName: No matching signal for on_pushButton_clicked()
any help is gladly appreciated :)
You need to delete the build folder (the whole thing), and build the project again. I suggest a switch to cmake with Ninja - it won’t have such problems.
You have obsolete signal/slot connections in UI files - the .ui.h headers are where connectSlotsByName is invoked. So simply Greg your entire source folder for the names of the signals (Ctrl-Shift-F in Qt Creator), include all file types, and you’ll likely find those names inside the connections element in the .ui Xml files. They can be removed from there manually, or using the Designer built into Qt Creator.
Deleting the build folder is the starting point - you can’t afford to have any old state, and the “Clean” option in the IDE doesn’t do that last time I checked.
So after some digging around I found the issue. It seems that it doesn't delete the slots which are located in the 'mainwindow.h' file. I need to delete these:
After running the application again, it then removes 'QT_MOC_LITERAL' that aren't being used (i.e deleted UI elements) as mentioned above (in the question) in the 'moc_mainwindow.cpp' file.

Qt QFileDialog::setDirectory funny behavior in Ubuntu 12.04

I have a class that inherits from QFileDialog. In the constructor, I call setDirectory and pass in the last directory visited (which the class keeps track of; see code below). On Windows, this works fine. And if I show the dialog multiple times, it is internally smart enough to resume at the last location (e.g. where the user saved a file before). This is the desired behavior.
On Ubuntu 12.04 (GCC 4.8 compiler), on the other hand, the system does not automatically resume where last left off if I call showFileDialog multiple times. So I tried adding the setDirectory call within that function as commented below, but that didn't change anything. Furthermore, if I take out setDirectory from the constructor so it is only called in showFileDialog, the file dialog opens to the folder from which the program was run. (i.e. setDirectory didn't work.) Subsequent calls to showFileDialog will open a file dialog starting in the directory requested.
So it seems like the call has a delayed effectiveness. Is this a Qt bug, or mine? Either way, how can I get the setDirectory call to be effective?
Example code:
QString FileDialog::defaultDir = QDir::homePath();
FileDialog::FileDialog(QWidget *parentWindow /*, ...*/)
: QFileDialog(parentWindow)
{
setDirectory(defaultDir);
//...
}
QString FileDialog::showFileDialog()
{
// Adding setDirectory(defaultDir) here doesn't help.
if(!exec())
{
return QString::null;
}
defaultDir = directory().path();
//...
}
It is not clear from the code above how you know that the path was changed. I'm not sure that directory() is responsible for that.
Consider using void QFileDialog::directoryEntered(const QString & directory) signal.
Workaround found:
I happen to set the dialog title (setWindowTitle()) every time I open a FileDialog. If I connect to the QFileDialog::windowTitleChanged signal and call setDirectory within the slot, it is effective.
This is an unintuitive workaround though, so I am open to better answers.

qt5 designer, using fileopen, displaying file path in lineedit, is there an issue doing it this way?

just started using qt,
looked through docs, google, examples, etc.. trying to find simple examples(working mind you)
that showed how to do (imho) simple things, by themselves.
well i stumbled upon my answer and i was wondering if this approach would cause an issue later as the code becomes more complex.
there are more includes than needed for this example, but this is direct from working code.
mainwindow.h:
i added
private slots:
void vpkButton_clicked();
and after
Ui::MainWindow *ui;
i added
QLineEdit *vpkPathTxt;
in mainwindow.cpp:
after
ui->setupUi(this);
i added
connect( this->ui->vpkButton, SIGNAL( clicked() ), this, SLOT(vpkButton_clicked()) );
to connect my ui button to the proper slot, the issue was getting the string from vpkButton_clicked() to display in the line edit i made in the designer,
what ended up working for me was adding this next:
vpkPathTxt = this->ui->vpkPathTxt;
the function in my main.cpp became very easy:
(QString declarations at top outside voids)
void MainWindow::vpkButton_clicked()
{
vpkName = QFileDialog::getOpenFileName(this,
tr("Open VPK File"), "~/", tr("VPK Files (*_dir.vpk)"));
vpkPathTxt->setText(vpkName);
qDebug() << vpkName;
}
the reason i am ask is because it seems a little too easy to be reliable, and the fact that i havent seen it done like this,
any input welcome
thankyou
One problem with your slot is that you don't consider the case where the user discards the "open file" dialog. In this case, the function QFileDialog::getOpenFileName returns a null QString, so you should only proceed with your logic if the return value was not a null string:
if (!vpkName.isNull()) {
...
}
The second problem is as follows and I made some assumptions since I don't see your full code:
I guess you want to load a file using the file name the user has chosen in the dialog. But you set the file name in the line edit too, which the user can edit by hand. I also guess that the actual file loading happens in a different step (i.e. after clicking another button), so after the user has edited the file name by hand in the line edit it won't be the same than in your local variable vpkName.
When loading the file I'd read the contents of the line edit instead of the variable vpkName so the edit made by hand will be respected.
A different method is to also watch for editing of the line edit and reflect the changes in your variable too. Then it will be ok to read the variable instead of the line edit when loading the file later on.

Qt 4.x: how to implement drag-and-drop onto the desktop or into a folder?

I've written a little file-transfer application written in C++ using Qt 4.x ... it logs into a server, shows the user a list of files available on the server, and lets the user upload or download files.
This all works fine; you can even drag a file in from the desktop (or from an open folder), and when you drop the file icon into the server-files-list-view, the dropped file gets uploaded to the server.
Now I have a request for the opposite action as well... my users would like to be able to drag a file out of the server-files-list-view and onto the desktop, or into an open folder window, and have that file get downloaded into that location.
That seems like a reasonable request, but I don't know how to implement it. Is there a way for a Qt application to find out the directory corresponding to where "drop event" occurred, when the icon was dropped onto the desktop or into an open folder window? Ideally this would be a Qt-based platform-neutral mechanism, but if that doesn't exist, then platform-specific mechanisms for MacOS/X and Windows (XP or higher) would suffice.
Any ideas?
Look at QMimeData and its documentation, it has a virtual function
virtual QVariant retrieveData ( const QString & mimetype, QVariant::Type type ) const
this means to do you drag to the outside you implement this functions accordingly
class DeferredMimeData : public QMimeData
{
DeferredMimeData(QString downloadFilename) : m_filename(downloadFilename)
virtual QVariant retrieveData (const QString & mimetype, QVariant::Type type) const
{
if (mimetype matches expected && type matches expected)
{
perform download with m_filename
}
}
}
The delayed encoding examples shows this principle.
You will probably also have to override hasFormat and formats to provide the appropriate types, application/octet-stream probably being the one that might get you the most play, you will probably have to read up on how windows specifically handles drag and drop using mime types.
I don't know how you will supply the file name under which the file is saved, but you will probably have to get into the windows side of things. Looking at the source of QWindowsMime might also help. There might me a multiple step process where you will get requests for text/uri-list data for the filenames and then application/octet-stream for the data.
Hope this helps
I think you are going about that in the wrong way.
You don't care where the drop goes to, you just know it was dropped. In the DropEvent, download the file to a temporary location and then set the mime data to be what was downloaded. Granted this may end up with a second copy to the hard drive, it will be cross platform from the start. You may be able to optimize it afterward with platform specific calls.
Take a look at Dropsite example to see how the mime data works from other sources...
Edit:
It looks like the double copy method is the standard if you don't write a shell extension (for windows at least). Filezilla and 7zip both do that, they return a "text/uri-list" mime type with a temp location. This way explorer copies the data from the temp location to the real one. Your application can do the same, create a temp file (or just the name) with no data. Using the delayed encoding example, create the data on the drop. This whole operation is very platform specific it appears. On Linux (running KDE) I can drag and drop just based on mime type. It appears that windows is not as flexible.
You just implement the drag part. You don't need to know where the drop happen because the application receiving it will handle it and decide where to store your file.
You create a QMimeData, not sure which type but from what i saw here and here, maybe "application/octet-stream" with your file as data in a QByteArray or "text/uri-list" with url to your file.
You create a QDrag, and use its setMimeData() method and exec() (not sure about which QT::DropAction to choose).
Here is an example (disclamer : i did it with color not files) :
void DraggedWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
start_pos = event->pos();
}
void DraggedWidget::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton) {
int distance = (event->pos() - start_pos).manhattanLength();
if (distance >= QApplication::startDragDistance())
{
/* Drag */
QMimeData *mime_data = new QMimeData;
mime_data->setData( ... );
QDrag *drag = new QDrag(this);
drag->setMimeData(mime_data);
drag->exec(Qt::CopyAction);
}
}
}
Sorry it's quite incomplete, i hope it helps a little.