I have a QGraphicsScene where I am drawing a QPainterPath, and I need to be able to save the shape, and redraw it when the app runs again. Here is how I'm drawing the shape, simplified version, and my write method.
void drawPath(){
QPoint p1 = QPoint(10, 20);
writePointsToFile(p1);
QPoint p2 = QPoint(25, 30);
writePointsToFile(p2);
QPoint p3 = QPoint(40, 60);
writePointsToFile(p3);
QPainterPath path;
path.moveTo(p1.x(), p1.y());
path.lineTo(p2.x(), p2.y());
path.lineTo(p3.x(), p3.y());
}
void writePointsToFile(QPoint point){
QFile file("../path.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
out << point;
file.close();
}
Currently, my file is never written to when it runs.
But beyond that, is the correct way to serialize this data so that I can rebuild the shape?
I thought I would be able to handle re-drawing, but I'm not understanding the serialization well enough.
Do I serialize the points?
The List containing the points?
My thought was if I serialize the points, when I deserialize, I then add them to a list, and I should be able to recreate the shape based on the position of each point in the list; ie point at position 0 would be p1, point at 1 would be p2, etc. But I can't get this far because nothing is being written to the file anyway. Plus I'm not entirely sure what to expect from the serialization of the data in the first place.
Any help on this would be wonderful.
EDIT: based on feedback, I am now trying to this in my write method
QFile file("../path.dat");
file.open(QIODevice::WriteOnly);
QDataStream out(&file);
QDataStream & operator << (QDataStream& file, const QPainterPath& path);
out << path;
file.close();
This compiles fine, even though I'm not entirely sure I'm doing that right, nothing is being written to the file so I assume I'm still off somewhere.
Qt already provides the operators, necessary to serialize and deserialize the QPainterPath directly:
QDataStream & operator<<(QDataStream & stream, const QPainterPath & path)
QDataStream & operator>>(QDataStream & stream, QPainterPath & path)
So there is no need to serialize points, when you can serialize the exact content of the path, including complex multi-component paths.
So you should implement the path as a persistent member variable, so that you can read it from or write it to a file, and in the draw method you simply draw the path.
Currently, my file is never written to when it runs.
My bet is because writePointsToFile() is never invoked. You may also get in the good habit of checking for errors when you try and open files and such. You also don't specify QIODevice::Append so even if you did write to disk, you'd only write one single point, overwriting the previous each time.
Edit: based on your edit, it looks like you are getting ahead of yourself, and still have to learn basic C++ before you rush into using it. Try something like this, and figure where you are going wrong:
QPoint p1 = QPoint(10, 20);
QPoint p2 = QPoint(25, 30);
QPoint p3 = QPoint(40, 60);
QPainterPath path;
path.moveTo(p1.x(), p1.y());
path.lineTo(p2.x(), p2.y());
path.lineTo(p3.x(), p3.y());
QFile file("../path.dat");
if (!file.open(QIODevice::WriteOnly)) return;
QDataStream out(&file);
out << path;
Related
What is the best way to store multiple QFile in a collection? Would be best to use QList, array, or vectors?
I want to access the element of the collection as a QFile and not QFile*.
I'm trying to read a directory in Qt and store all the files in a collection. Then print all the file names to the screen.
How I'm reading the directory:
QDirIterator it(p_dir, QStringList() << "*.wav", QDir::Files, QDirIterator::Subdirectories);
while (it.hasNext())
{
// Put file into collection
}
Currently I'm using QList to store the QFiles:
QList<QFile*> list;
I read that the best way to get file information is to use QFileInfo type. How could I set the QFileInfo file to be the element from my list?
I want to do something like:
QFileInfo info;
QListIterator<QFile*> i(list);
while(i.hasNext())
{
info.setFile(i);
}
But the above gives me:
error: no matching function for call to 'QFileInfo::setFile(QListIterator&)'
fi.setFile(i);
Keep in mind QFile inherits QObject, and as such is not copyable or movable. It is common practice to store pointers to such objects in containers rather than the objects themselves. As for which container to use, it doesn't really matter much, but QVector will be the most efficient.
QListIterator is a Java style iterator. So:
QListIterator::QListIterator(const QList<T> & list)
Constructs an iterator for traversing list. The iterator is set to be
at the front of the list (before the first item).
The iterator doesn't point to anything, if you want to get the actual object, use i.next() to give you a QFile* which you then will have to dereference, since setFile() takes in a QFile &, not a QFile *.
QFile is not copyable. In C++98, there's generally no way of storing it in a container, unless the container supports in-place construction. I don't know offhand of any such C++98 containers, although writing one wouldn't be all that hard.
In C++11, you can use any container that doesn't need to copy elements and supports emplacement, e.g. std::list:
// https://github.com/KubaO/stackoverflown/tree/master/questions/qfile-list-36391586
#include <QtCore>
#include <list>
void populate(std::list<QFile> & files) {
QDir dir(QCoreApplication::applicationDirPath());
for (auto fileName : dir.entryList(QDir::Files)) {
qDebug() << "adding file" << fileName;
files.emplace_back(fileName);
}
}
void iterate(std::list<QFile> & files) {
for (auto & file : files)
if (file.open(QIODevice::ReadOnly)) {
qDebug() << "successfully opened" << file.fileName();
}
}
int main(int argc, char ** argv) {
QCoreApplication app(argc, argv);
std::list<QFile> files;
populate(files);
iterate(files);
}
On my system, when run from Qt Creator, the output is:
adding file "main.o"
adding file "Makefile"
adding file "qfile-list-36391586"
successfully opened "main.o"
successfully opened "Makefile"
successfully opened "qfile-list-36391586"
Since QFile is a proper class implementing RAII of system resources, the files will get automatically closed by files::~list, before main returns.
I'm currently manipulating QFileInfo based on certain criterian/logics..but if I just filter all the complicated logic, it would be something like
QDir testDir("/home/mypath/to/dir");
QFileInfoList files = testDir.entryInfoList(); // #1. here you may use filters as I'm filtering based on a specific file extension
QFileInfoList myfiles;
for(int index = 0; index < files.size(); ++index)
{
const QFileInfo& info = files.at(index);
qDebug() << info.absoluteFilePath() << endl;
// #2. I've some sort of filters I used in #1 above that I compares here by utilizing info here, something like
//if (info.baseName().contains("anyPattern");
myfiles.push_back(info);
}
// Now you can view whatever you stored
for (int vw_idx = 0; vw_idx < myfiles.size(); ++vw_idx)
{
const QFileInfo& info = files.at(index);
qDebug() << info.absoluteFilePath() << endl;
}
I'm making a small game using SFML 2.2
I'll start with my code. As I already had about 4000 lines of working code I made a texture manager for storing all textures that are used at a time. I used texures earlier of course but I needed a way to manage them. It looks like this:
//Declarations
class Manager
{
private:
static map<string, Texture > textures;
public:
static Texture & add(const string & name, const string & directory);
static Texture & resolve(const string & name);
};
//Definitions
Texture & Manager::add(const string & name, const string & directory)
{
// Check, whether a said texture was already maped
map<string, Texture>::const_iterator it = textures.find(name);
if (it != textures.end())
{
cout << "Said element was loaded" << endl;
return textures.at(name);
}
// Adding a new texture
Texture tex;
tex.loadFromFile(directory);
textures[name] = tex;
cout << "Texture added" << endl;
return textures.at(name);
}
Texture & Manager::resolve(const string & name)
{
cout << "Resolved" << endl;
return textures.at(name);
}
map<string, Texture > Manager::textures;
As you can see I have a global map accesed by two methods. Both methods return a reference to a certain texture. So as my project had already quiet a lot of textures I decided to check if my manager works fine and replaced loading one texture with method add and binding it to sprite with resolve. As I compiled code and run it everything worked good untill I went back to menu of my game and decided to "play again". Now, when it came to load the texture again, method add correctly recognized name (key) as already used and method resolve returned correctly texture. At least "Resolved" appeared on the screen. And this is the point when error ocures.
But when I moved calling add from method, where the texture is used to the constructor of the class that uses it, it works fine again. And it's called the same amount of times; once per "play game".
Here is the not working code:
void Engine::set_up(int & lvl)
{
srm::Manager::add("Loading screen", "data/loading_screen.png");
Sprite spr;
spr.setTexture(srm::Manager::resolve("Loading screen"));
// ...
}
Also not working code:
void Engine::set_up(int & lvl)
{
Sprite spr;
spr.setTexture(srm::Manager::add("Loading screen", "data/loading_screen.png");
// ...
}
And working:
//Constructor
Engine(){ srm::Manager::add("Loading screen", "data/loading_screen.png"); }
//Method
void Engine::set_up(int & lvl)
{
Sprite spr;
spr.setTexture(srm::Manager::resolve("Loading screen"));
// ...
}
I feel I made a mistake in my Manager. Here is what the error says:
Microsoft Visual C++ Runtime Library - Error
There is used word "heap" so it just confirmed me that those error is related to my Manager. I made a research in the web and I have found this:
Debug Assertion Failed: _CrtIsValidHeapPointer(pUserData)
I understand what cause the error but I can't understand where my mistake is. The weirdest thing is that those calling add in constructor works and calling it elsewhere doesn't!
I'm sorry if something isn't clear, I'll answer your questions then.
Does anybody know any solution to this issue? Can you explain me, what's wrong in my code? Thank you in advance!
EDIT
Ok, I tracked stack in debug mode and found the culprit. These are sf::Text objects which have in field value
Information not avaliable, no symbols loaded for
sfml-graphics-d-2.dll
and in every test it appears in other Text object. Always there are problems with strings, it's always "Unable to read memory" in string parts of my texts. I don't know why, for me it's not even related to my problem. However, when I changed my code back to this:
void Engine::set_up(int & lvl)
{
//srm::Manager::add("Loading screen", "data/loading_screen.png");
Texture texture;
texture.loadFromFile("data/loading_screen.png");
Sprite spr;
spr.setTexture(texture);
//spr.setTexture(srm::Pojemnik::zwroc("Ekran wczytywania"));
so a classic local sf::Texture object and binding it to sprite, everything works perfectly with no problem.
So as I said, when I put
srm::Manager::add("Loading screen", "data/loading_screen.png");
to the constructor or when I don't use my Manager class at all everything works, when I want to use my code - it hangs.
In my Qt app I'd like to encode a pointer to an object as a string, pass it to another bit of code then decode it so that I can access the object.
This is part of internal drag and drop with a QTreeView. In my mimeData() method I have:
QMimeData * TreeModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
foreach (QModelIndex index, indexes)
{
QString colText;
if (index.isValid()) {
TreeItem *item = getItem(index);
// grab the text from each column
for(int cc=0; cc < item->columnCount(); cc++ ) {
colText = item->data(cc).toString();
stream << colText;
}
// add the pointer to the item
qDebug() << quint64(&item);
stream << quint64(&item);
}
}
mimeData->setData("application/vnd.text.list", encodedData);
return mimeData;
}
The qDebug() line produces a number like 140736277471632 which could be right, but is probably wrong.
How should I encode a pointer as a string so that it can be fed into a stream. And how should I then decode it and get the pointer to the original object?
Thank you.
I would dis-advice doing this.
Serializing objects in strings and de-serializing later makes sense for "moving" objects from one process to another. But within one process, you should pass pointers directly, or wrapped in a container like shared-pointer.
If the only way to pass something is a string, create an instance (e.g. QMap<QString, YourPointerType>) where you can register a pointer and access it by a string-name.
If you wrap this map in a class, you can check, if this pointer already exists while registering and if it still exists while retrieving.
Besides, in a models you can store anything you want using User-Roles. You are not limited to store your custom data as mime data.
Here you don't want to take the address of item, but its value. It's a pointer, its value is the address you're looking for, not its address (which, as already mentioned, is completely irrelevant and dangerous to manipulate once the if block scope is exited).
qDebug << qint64(&item);// will print the address this pointer is stored at.
qDebug << qint64(item);// will print the address this pointer is pointing at
EDIT: If you want to get the address back from a string into a pointer, read it as a number from a stringstream, i.e.:
std::istringstream is{str};
long pointer;//be careful with the size of a pointer in your case.
is >> pointer;
TreeItem* item = reinterpret_cast<TreeItem*>(q);
I use QFileDialog to open a file and use a QDatastream to read it.
QString fileName = QFileDialog::getOpenFileName(this, tr("open file"),
" ",
tr("Text (*.c);;Bin(*.bin)"));
QFile f(fileName);
QDataStream readstream(&f);
f.open(QIODevice::ReadOnly);
But after that ,I have to use a function written in C so I have a problem about how to get the parameters.
My C function is:
Ymodem_Transmit (uint8_t *buf, const uint8_t* sendFileName, uint32_t sizeFile)
1.uint8_t *buf is a pointer to the start address of the file ,but I dont know how to get it from QDataStream. Maybe I can read them into a buffer but my file is a little big so I don't want to use a big buffer.
2.const uint8_t* sendFileName is a string and how to get that?
Even thoughQFileDialog::getOpenFileName return me a Qstring filename but I think it includes the file's path,not exactly the filename.What I want is a real filename , I believe some class-function can do this but I can't find it.
3.uint32_t sizeFile Can I get it by using qint64 QFile::size() ? If I was right , how can I transfer a qint64 into uint32_t? Will that work if I do so:
qint64 filesize=QFile::size();
(uint32_t)filesize;
I highly recommend you not to use your C-function - QFile itself provides a really nice interface for accessing data in your file system. If you want to get your filename excluding a path use QFIleInfo class and fileName()member. And yes, you can get the file size this way, but be carefull - if your file size is rather big (bigger than can hold uint32_t) your value will be overflown.
According to the documentation, one can load an sf::Texture three different ways: from file, from stream, from memory. I think I need the latter, but I'm not even sure about that. And if I do, I can't figure out how to use it.
I have a method, which, given an url to work with, needs to return an sf::Sprite. I'm successfully downloading the binary data, and storing it in a string (since Http::Response::getBody() returns that). I know for a fact, that this is the right data, because if I write and save it to a file, I can view the image. I don't want a file however, I just need the image displayed.
Here is the function:
sf::Sprite Downloader::GetPicture(const std::string &url)
{
sf::Http http;
std::string host = url.substr(0, url.find("net/")+4 );
std::string uri = url.substr(url.find("net/")+4, url.size());
http.setHost(host);
request.setUri(uri);
response = http.sendRequest(request);
std::string data = response.getBody();
//THIS IS WRONG
sf::Texture texture;
texture.loadFromMemory((void*)data, sizeof(data));
return sf::Sprite(texture);
/* THIS WORKS, BUT I DON'T WANT TO SAVE, REOPEN, THEN DELETE AT THE END
std::ofstream fileout("test.jpg", std::ios::binary);
fileout << data;
fileout.close();
*/
}
Here is loadFromMemory's signature for the lazy (the void* confuses me, that's probably the problem).
Also, this might be a completely wrong way to do it; maybe extending sf::InputStream, and using loadFromStream?
The problem is, returned sf::Sprite has 0×0 dimension and 0 size, that's how I know it's wrong.
There are two mistakes in your code. One was spotted by Casey, that is you should use texture.loadFromMemory(data.data(), data.length()).
The problem with texture.loadFromMemory((void*)data, sizeof(data)); is that you will load some garbage and not necessarily the picture data; you will load the other attributes of the string (like an integer for the length, and maybe a pointer for the data itself).
The other one is related to the white square problem: you return a sprite but not the texture. After the function returns, the texture is destroyed and the sprite will display only a white rectangle instead.
You could do something like:
sf::Texture dlpicture(std::string host, std::string uri)
{
sf::Http http(host);
sf::Http::Request request(uri);
auto response = http.sendRequest(request);
auto data = response.getBody();
sf::Texture text;
text.loadFromMemory(data.data(), data.length());
return text;
}
which you can try with sf::Texture texture = dlpicture("www.sfml-dev.org", "download/goodies/sfml-logo-small.png");.
SFML indeed support jpg format but not all its variants. Progressive jpg is not supported for example. You'll have to use another format.