I'm trying to automatically add a backslash to valid file paths in a QLineEdit, which is used to show the current path of a QFileSystemModel.
The code looks as follows:
fileSystem = new QFileSystemModel;
fileSystem->setRootPath(QObject::tr("C:\\"));
QCompleter* fileSystemCompleter = new QCompleter(fileSystem);
fileSystemCompleter->setCaseSensitivity(Qt::CaseInsensitive);
fileTree = new QDeselectableTreeView();
fileTree->setModel(fileSystem);
fileTree->setRootIndex(fileSystem->index(fileSystem->rootPath()));
connect(fileTree, &QTreeView::clicked, [&] (QModelIndex index)
{
QString toAppend("");
if (fileSystem->isDir(index))
{
toAppend = '/';
}
fileSystemPathEdit->setText(fileSystem->filePath(index)+toAppend);
});
// path line edit
fileSystemPathEdit = new QLineEdit(fileSystem->rootPath());
fileSystemPathEdit->setPlaceholderText("Path...");
fileSystemPathEdit->setCompleter(fileSystemCompleter);
connect(fileSystemPathEdit, &QLineEdit::editingFinished, [&]()
{
// jump to that location
qDebug() << fileSystemPathEdit->text();
QModelIndex index = fileSystem->index(fileSystemPathEdit->text());
qDebug() << index;
fileTree->setExpanded(index,true);
fileTree->setCurrentIndex(index);
// CLOSE IF EMPTY
if (fileSystemPathEdit->text().isEmpty())
{
fileTree->collapseAll();
fileSystemPathEdit->setText(fileSystem->rootPath());
}
// append slashes to dirs
else if (fileSystem->isDir(index) && index.isValid())
{
qDebug() << "it's a dir";
if (!fileSystemPathEdit->text().endsWith('/',Qt::CaseInsensitive))
{
qDebug() << "added slash";
fileSystemPathEdit->setText(fileSystemPathEdit->text().append('/'));
qDebug() << fileSystemPathEdit->text();
}
}
this->update();
});
I get the following output when running the code:
"C:/export/home"
QModelIndex(0,0,0x3adb840,QFileSystemModel(0x1d9b7c0) )
it's a dir
added slash
"C:/export/home/"
It works ok when I push the Enter key from within the lineEdit, but if the text is set by the QCompleter, I still get the same debug output showing that the text has been changed, but the slash doesn't appear in the lineEdit. Does the QCompleter somehow unset the text?
This is a hack, but adding this connection to the QCompleter gives the desired behavior. I think there is a race condition when using editingFinished() at the same time that the QCompleter is activated, so adding delay allows the slash to be appended without being overridden. On the down side, that function know gets called several times to many per change. I'd still be interested in a better solution.
connect(fileSystemCompleter, activatedOverloadPtr, [&](QModelIndex index)
{
QTimer* timer = new QTimer;
timer->setSingleShot(true);
timer->setInterval(10);
connect(timer, &QTimer::timeout, fileSystemPathEdit, &QLineEdit::editingFinished);
timer->start();
});
Related
This is how i am currently choosing the font in my application.
void FontChange()
{
QString filePath = QFileDialog::getOpenFileName(NULL, tr("Open File"),
QStandardPaths::standardLocations(QStandardPaths::FontsLocation)[0],
tr("Fonts (*.ttf);;Everything (*.*)"), nullptr,
QFileDialog::DontUseNativeDialog);
if (filePath.isEmpty())
return;
QlineEditSetFont->setText(filePath);
stdstrLocation = filePath.toStdString();
this->isChanged = true; // this executes the function
}
I want to execute a function whenever i change my selection in the Dialog , currently i have to choose a font and than click on open to execute the function
////////////////////////////////////////////////////////////////////////////
Now i am using a non modal way but how can i determine if cancel has been pressed.
Further Edit
QFileDialog* dialog = new QFileDialog();
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setNameFilter("TTF (*.ttf)");
dialog->setOption(QFileDialog::DontUseNativeDialog);
dialog->setDirectory("C:\\Windows\\Fonts");
QObject::connect(dialog, &QFileDialog::currentChanged, [=](const QString &path) {
qDebug() << path; stdstrLocation = path.toStdString(); this->isChanged = true;
QStringList fileNames = dialog->selectedFiles();
qDebug() << "Selected FIles" << fileNames.size();
});
dialog->show();
You can't use the static convenience method but need to create the QFileDialog instance manually:
auto dialog = new QFileDialog(someParent);
dialog->setWindowTitle(tr("Open File"));
dialog->setDirectory(QStandardPaths::standardLocations(QStandardPaths::FontsLocation)[0]);
dialog->setNameFilter(tr("Fonts (*.ttf);;Everything (*.*)"));
// more setup...
connect(dialog, &QFileDialog::filesSelected, this, [this](const QStringList &selected) {
// handle selection change here
});
if (dialog->exec() == QDialog::Accepted) { // alternatively use open() to avoid blocking exec()
// do something with dialog->selectedFiles()...
}
delete dialog;
Actually, those are two different questions. The one from the title has been already answered. The answer to the second one, namely How can i determine if cancel has been pressed, lies in the documentation of QFileDialog::getOpenFileName itself:
If the user presses Cancel, it returns a null string.
With this in mind, you can do something like:
void FontChange()
{
QString filePath = QFileDialog::getOpenFileName(NULL, tr("Open File"),
QStandardPaths::standardLocations(QStandardPaths::FontsLocation)[0],
tr("Fonts (*.ttf);;Everything (*.*)"), nullptr,
QFileDialog::DontUseNativeDialog);
if (filePath.isNull()) {
// user pressed Cancel
} else if (filePath.isEmpty()) {
return;
} else {
QlineEditSetFont->setText(filePath);
stdstrLocation = filePath.toStdString();
this->isChanged = true; // this executes the function
}
}
You can of course rewrite the if part to be more suitable to your app's logic.
As a side note, QString::isEmpty also implies that it is NULL.
I've developed a video player based on Qt and QtGstreamer. It is used to play live streams (RTSP). I have to add the possibility for the user to take snapshots while he is playing a live stream without perturbing the video playback.
Here the graph of the pipeline I've made:
-->queue-->autovideosink
uridecodebin-->videoflip-->tee--|
| -->queue->videoconvert-->pngenc-->filesink
|
|->audioconvert-->autoaudiosink
I use the pad-added signal from uridecodebin to add and link dynamically my elements to the pipeline, function of the received caps.
void Player::onPadAdded(const QGst::PadPtr &pad)
{
QGst::CapsPtr caps = pad->currentCaps();
if (caps->toString().startsWith("video/x-raw")) {
qDebug("Received 'video/x-raw' caps");
handleNewVideoPad(pad);
}
else if (caps->toString().startsWith("audio/x-raw")) {
qDebug("Received 'audio/x-raw' caps");
if (!m_audioEnabled) {
qDebug("Audio is disabled in the player. Ignoring...");
return;
}
handleNewAudioPad(pad);
}
else {
qWarning("Unsuported caps, arborting ...!");
return;
}
}
[...]
void Player::handleNewVideoPad(QGst::PadPtr pad)
{
m_player->videoTeeVideoSrcPad = m_player->videoTee->getRequestPad("src_%u");
// Add video elements
m_player->pipeline->add(m_player->videoFlip);
m_player->pipeline->add(m_player->videoTee);
m_player->pipeline->add(m_player->videoQueue);
m_player->pipeline->add(m_player->videoSink);
// Add snap elements
m_player->pipeline->add(m_player->snapQueue);
m_player->pipeline->add(m_player->snapConverter);
m_player->pipeline->add(m_player->snapEncoder);
m_player->pipeline->add(m_player->snapSink);
// Link video elements
m_player->videoFlip->link(m_player->videoTee);
m_player->videoQueue->link(m_player->videoSink);
// Link snap elements
m_player->snapQueue->link(m_player->snapConverter);
m_player->snapConverter->link(m_player->snapEncoder);
m_player->snapEncoder->link(m_player->snapSink);
// Lock snap elements
m_player->snapQueue->setStateLocked(true);
m_player->snapConverter->setStateLocked(true);
m_player->snapEncoder->setStateLocked(true);
m_player->snapSink->setStateLocked(true);
m_player->videoFlip->setState(QGst::StatePlaying);
m_player->videoTee->setState(QGst::StatePlaying);
m_player->videoQueue->setState(QGst::StatePlaying);
m_player->videoSink->setState(QGst::StatePlaying);
// Link pads
m_player->videoTeeVideoSrcPad->link(m_player->videoQueue->getStaticPad("sink"));
pad->link(m_player->videoSinkPad);
m_player->videoLinked = true;
}
The method to take a snapshot:
void Player::takeSnapshot()
{
QDateTime dateTime = QDateTime::currentDateTime();
QString snapLocation = QString("/%1/snap_%2.png").arg(m_snapDir).arg(dateTime.toString(Qt::ISODate));
m_player->inSnapshotCaputre = true;
if (m_player->videoTeeSnapSrcPad) {
m_player->videoTee->releaseRequestPad(m_player->videoTeeSnapSrcPad);
m_player->videoTeeSnapSrcPad.clear();
}
m_player->videoTeeSnapSrcPad = m_player->videoTee->getRequestPad("src_%u");
// Stop the snapshot branch
m_player->snapQueue->setState(QGst::StateNull);
m_player->snapConverter->setState(QGst::StateNull);
m_player->snapEncoder->setState(QGst::StateNull);
m_player->snapSink->setState(QGst::StateNull);
// Link Tee src pad to snap queue sink pad
m_player->videoTeeSnapSrcPad->link(m_player->snapQueue->getStaticPad("sink"));
// Set the snapshot location property
m_player->snapSink->setProperty("location", snapLocation);
// Unlock snapshot branch
m_player->snapQueue->setStateLocked(false);
m_player->snapConverter->setStateLocked(false);
m_player->snapEncoder->setStateLocked(false);
m_player->snapSink->setStateLocked(false);
m_player->videoTeeSnapSrcPad->setActive(true);
// Synch snapshot branch state with parent
m_player->snapQueue->syncStateWithParent();
m_player->snapConverter->syncStateWithParent();
m_player->snapEncoder->syncStateWithParent();
m_player->snapSink->syncStateWithParent();
}
The bus message callback:
void Player::onBusMessage(const QGst::MessagePtr & message)
{
QGst::ElementPtr source = message->source().staticCast<QGst::Element>();
switch (message->type()) {
case QGst::MessageEos: { //End of stream. We reached the end of the file.
qDebug("Message End Off Stream");
if (m_player->inSnapshotCaputre) {
blockSignals(true);
pause();
play();
blockSignals(false);
m_player->inSnapshotCaputre = false;
}
else {
m_eos = true;
stop();
}
break;
}
[...]
}
The problem is:
When I set the snapshot property to true of the pngenc element, I receive the EOS event which stop my pipeline, so I need to restart it, which freeze the video playback for about half a second, which in not acceptable in my case.
When I set the snapshot property to false of the pngenc element, I have no pipeline perturbations, but my png file keeps growing until I call again the Player::takeSnapshot() method.
Where am I wrong ? Is there a better way to do it ?
I've tried unsuccessfully creating a QGst::Bin element for my snapshot branch. What about pad probe ?
Thanks by advance
You can take the last-sample property on any sink, e.g. your video sink. This contains a GstSample, which has a buffer with the very latest video frame in it. You can take that as a snapshot, and e.g. with gst_video_convert_sample() or the async variant of it, convert it to a PNG/JPG/whatever.
See https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-libs/html/GstBaseSink.html#GstBaseSink--last-sample and https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-libs/html/gst-plugins-base-libs-gstvideo.html#gst-video-convert-sample
Alternatively, you would have to shut down the filesink snapshot pipeline after the first frame. For example by having a pad probe to know when the first frame happened, and then injecting an EOS event to prevent further PNG frames to be appended to the same file.
Thanks to #sebastian-droge answer, I found the solution, using gst_video_convert_sample and the last-sample property of my video sink.
The solution I've implemented is:
void Player::takeSnapshot()
{
QDateTime currentDate = QDateTime::currentDateTime();
QString location = QString("%1/snap_%2.png").arg(QDir::homePath()).arg(currentDate.toString(Qt::ISODate));
QImage snapShot;
QImage::Format snapFormat;
QGlib::Value val = m_videoSink->property("last-sample");
GstSample *videoSample = (GstSample *)g_value_get_boxed(val);
QGst::SamplePtr sample = QGst::SamplePtr::wrap(videoSample);
QGst::SamplePtr convertedSample;
QGst::BufferPtr buffer;
QGst::CapsPtr caps = sample->caps();
QGst::MapInfo mapInfo;
GError *err = NULL;
GstCaps * capsTo = NULL;
const QGst::StructurePtr structure = caps->internalStructure(0);
int width, height;
width = structure.data()->value("width").get<int>();
height = structure.data()->value("height").get<int>();
qDebug() << "Sample caps:" << structure.data()->toString();
/*
* { QImage::Format_RGBX8888, GST_VIDEO_FORMAT_RGBx },
* { QImage::Format_RGBA8888, GST_VIDEO_FORMAT_RGBA },
* { QImage::Format_RGB888 , GST_VIDEO_FORMAT_RGB },
* { QImage::Format_RGB16 , GST_VIDEO_FORMAT_RGB16 }
*/
snapFormat = QImage::Format_RGB888;
capsTo = gst_caps_new_simple("video/x-raw",
"format", G_TYPE_STRING, "RGB",
"width", G_TYPE_INT, width,
"height", G_TYPE_INT, height,
NULL);
convertedSample = QGst::SamplePtr::wrap(gst_video_convert_sample(videoSample, capsTo, GST_SECOND, &err));
if (convertedSample.isNull()) {
qWarning() << "gst_video_convert_sample Failed:" << err->message;
}
else {
qDebug() << "Converted sample caps:" << convertedSample->caps()->toString();
buffer = convertedSample->buffer();
buffer->map(mapInfo, QGst::MapRead);
snapShot = QImage((const uchar *)mapInfo.data(),
width,
height,
snapFormat);
qDebug() << "Saving snap to" << location;
snapShot.save(location);
buffer->unmap(mapInfo);
}
val.clear();
sample.clear();
convertedSample.clear();
buffer.clear();
caps.clear();
g_clear_error(&err);
if (capsTo)
gst_caps_unref(capsTo);
}
I've create a simple test application, which implement this solution. The code is available on my Github
I am trying to implement copy/cut/paste in a complex application.
I have a QGraphicsScene that can contain QGraphicsItem subtypes of varied subtypes, fairly complex (with Item as a second parent storing custom properties).
I would copy/cut selected items, and paste them back in place.
I already have implemented it using a local version: a list of items.
void copyItemsActionOld()
{
foreach(QGraphicsItem* qItem, selectedItems())
{
Item* newItem = (dynamic_cast<Item*>(qItem))->createItemCopy();
m_itemClipboard.append(newItem);
}
}
On paste, I make a copy of all items in clipboard and add them to the scene. So simple.....
BUT
I need to implement it using the global system clipboard.
I saw that creating a custom mime type is as simple as calling setData on a QMimeData object, after I make up a format name... (I hope that is true)
static const QString _mimeType("application/myItem");
void copyItemsAction()
{
QMimeData* _mimeData = new QMimeData;
2 QByteArray _itemData = ?????;
_mimeData->setData(_mimeType, _itemData);
QClipboard* _clipboard = QApplication::clipboard();
_clipboard->clear();
_clipboard->setMimeData(_mimeData);
}
void pasteItemsAction()
{
QClipboard* _clipboard = QApplication::clipboard();
const QMimeData* _mimeData = _clipboard->mimeData();
QStringList _formats = _mimeData->formats();
foreach (QString _format, _formats)
{
if (_format == _mimeType)
{
QByteArray _itemData = _mimeData->data(_mimeType);
3 // then do what ? How do I parse it ?
}
}
}
My questions
1) Are the above fragments for copyItemsAction and pasteItemsAction anywhere close to how clipboard actions should work ?
2) How can I put item data in the QByteArray ?
3) How do I parse the data in QByteArray ?
4) Do I need to register the custom mime-type anywhere else ? (other than what I just did in my two functions); and will it be multi-platform ?
I have already implemented save and load functionality for all items. Something like...
void Item::saveItem(QDataStream &outFile)
{
outFile << type;
outFile << width;
outFile << color.name();
}
Can I use this to place the items data in the QByteArray ? (How ?)
I was on the right track, and I kept adding code to my question until I found how to make it work:
static const QString _mimeType("application/myItem");
void copyItemsAction()
{
QByteArray _itemData;
QDataStream outData(&_itemData, QIODevice::WriteOnly);
outData << selectedItems().size();
foreach(QGraphicsItem* qItem, selectedItems())
{
Item* item = dynamic_cast<Item*>(qItem);
item->saveItem(outData);
}
QMimeData* _mimeData = new QMimeData;
_mimeData->setData(_mimeType, _itemData);
_mimeData->setText("My Items");
QClipboard* _clipboard = QApplication::clipboard();
_clipboard->clear();
_clipboard->setMimeData(_mimeData);
}
void pasteItemsAction()
{
QClipboard* _clipboard = QApplication::clipboard();
const QMimeData* _mimeData = _clipboard->mimeData();
QStringList _formats = _mimeData->formats();
foreach (QString _format, _formats)
{
if (_format == _mimeType)
{
QByteArray _itemData = _mimeData->data(_mimeType);
QDataStream inData(&_itemData, QIODevice::ReadOnly);
int itemsSize;
inData >> itemsSize;
for (int i = 0; i < itemsSize; ++i)
{
Item* item = ...
item->loadItem(inData);
}
}
}
}
So, for question 1, yes I was on the right track;
For questions 2 and 3 - I was able to use a QDataStream to serialize info to/from the QByteArray.
If there is a better / more effective / faster way, I would love to know...
For question 4 - it seems that I can use just about any string, if all I want is to copy/paste within a single instance of my application.
It is also true if I want to use it between multiple applications, multiple instances of my application, or for drag-and-drop - on most platforms. (It does not seem to work between multiple applications/instances in the embedded platform I target.)
Caveat - it fails frequently when another clipboard using application is open, in windows.
I am passing a QVector from one window to another, I want to append the value present in QVector from previous window to a QString in present window. I get the error when I perform the addition no match for 'operator+'.
Here is my code:
Window1.cpp
void SelectOS::processNextButton()
{
if(ui->win32->isChecked()){
QString path;
path = qApp->applicationDirPath()+"/WIN/32Bit";
so->osName.push_back(path);
SelectSoftware *ss = new SelectSoftware();
this->hide();
ss->show();
}
}
QVector<QString> SelectOS::getosName(){
so = new SelectOS();
return so->osName;
}
Window2.cpp
void SelectSoftware::getSoftwareDetails()
{
SelectOS *so = new SelectOS();
SelectSoftware *ss = new SelectSoftware();
ss->os = so->getosName();
QString fileName = ss->os + "/" +SOFTWARELIST; // Here I get the error...
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)){
QString msg = "Could not find the file " + fileName;
errorExit(msg);
}
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine();
processLine(line.toLower());
}
}
Help me, thanks ...
Assuming SOFTWARELIST is a simple QString:
QString fileName = QString( "%1/%2" )
.arg( ss->os.last() )
.arg( SOFTWARELIST );
This means you are creating a QString with placeholders %1 and %2 where %1 will be replaced by the result of ss->os.last() which returns the last item in the vector and %2 will be replaced by whatever SOFTWARELIST is.
If SOFTWARELIST is a vector as well, you will need to call e.g. .last() on it as well.
QVector is a container class, which holds set of something. In your example set of QString's. So then you try to form a fileName which is a QString you obviously cannot add to a fileName QString list of other Qstring's. (truly saying you can, but not with '+' operator and slightly different code).
Honestly I didn't got straight away why you actually passing QVector if you only need an application path I would suggest to use just QString.
I have addFile function in my TableModel class which inserts a new record at the end.
void TableModel::addFile(const QString &path)
{
beginInsertRows(QModelIndex(), list.size(),list.size());
TableItem item;
item.filename = path;
QFile file(path);
item.size = file.size();
item.status = StatusNew;
list << item;
endInsertRows();
}
This function works fine but instead of appending record at the end I would like to insert it at the top. Any pointers on how to update my existing function ?
I have already tried some combinations but there is no Luck.
There are two things that you need to do. First is to adjust the call to beginInsertRows. Because it is here that we are telling the model that we are adding rows, where they will go, and how many we are adding. Here is the method description:
void QAbstractItemModel::beginInsertRows ( const QModelIndex & parent,
int first, int last )
So in your case since you want to add a row at the first index, and only one row, we pass 0 as the index of the first item, and 0 which is the index of the last item we are adding (because of course we are only adding one item).
beginInsertRows(modelIndex(), 0, 0);
Next we have to provide the data for the item. I assume that 'list' is a QList (if not it is probably similar). So we want to call the 'insert' method.
list.insert(0, item);
And that should be it.
For display you can try delegates as explained in the link (I haven't tried the example though). It will help the community if you can add your observations.
Thanks to everyone for replying. I have found the solution by my own:
In case if anyone is interested
void TableModel::addFile(const QString &path)
{
beginInsertRows(QModelIndex(), list.size(), list.size());
TableItem item;
item.filename = path;
QFile file(path);
item.size = file.size();
item.status = StatusNew;
list << item; // Why Assign first? Maybe not required
for (int i = list.size() - 1; i > 0; i--)
{
list[i] = list[i-1];
}
list[0] = item; // set newly added item at the top
endInsertRows();
}