PDA

View Full Version : Howto display QPixmap and Text?



segi
25th July 2009, 09:24
Hello,

I have a sql database with the following tables:
table content
id integer, content text
table images
id integer, shortname text, data blob

The following snippet is from content.conten:

This is an example text with an image :img1. The text is very senseless. Here another image :img2.
I have to replace the ":img(id)" keywords by the correct image.
I generate a QPixmap of each image by QPixmap::loadFromData(const QByteArray&). To show them in a QLabel inline the text module doesn't seem possible. I see no way to create a QLabel for each text module and each image. (IMHO would it be very bad design)
Maybe QTextBrowser could be the right GUI element but sometimes I have to replace only the images on user input. Do I use a QTextDocument than?
What other possibilities I have to display the information to the user?

Bye

wysota
25th July 2009, 12:36
QLabel is fine - you can use the <img> tag to add images to your text.

segi
25th July 2009, 13:27
I know that QLabel supports rich text. But I have a QPixmap object and I don't know how to insert it inline a QLabel.
Something like the following won't work:


QPixmap pixmap;
pixmap.loadFromData(_data);
QLabel *label;
label->setText("some senseless text with an image <img src="+pixmap+"/>");

Sure it is possible to get the image from the database, create a QPixmap object, save it in a temporary file and include this e.g. <img src="temp.png" />. But isn't there an easier way?

wysota
25th July 2009, 13:32
There are many ways :) You can try embedding the data directly in the URL although I'm not sure if Qt Rich Text supports it or you can use the resource system or a similar system based on QAbstractFileEngine.

segi
25th July 2009, 14:25
Embedding in the URL isn't supported by Qt's Rich Text.
I don't want to use the resource system because i have to export the images from database into regular files.
Is subclassing QAbstractFileEngine really necessary?

wysota
25th July 2009, 15:01
Is subclassing QAbstractFileEngine really necessary?

No, you can register the data with the resource system dynamically through QResource::registerResource(). Of course you need to properly feed the data.

segi
25th July 2009, 17:28
I read much about QResource now. That isn't what I really want.
To subclass QAbstractFileEngine seems the best solution. But IHMO it's a lot of work and the documentation of it is not as good as of the other Qt classes.
I have a some code which not working well:


// sqlfileengine.h
#ifndef SQLFILEENGINE_H
#define SQLFILEENGINE_H

#include <QAbstractFileEngine>
#include <QAbstractFileEngineHandler>

class QByteArray;
class QString;

class SqlFileEngine;

class SqlFileEngineHandler : public QAbstractFileEngineHandler
{
public:
SqlFileEngineHandler();
virtual QAbstractFileEngine* create(const QString& filename) const;
};

class SqlFileEngine : public QAbstractFileEngine
{
public:
SqlFileEngine();
explicit SqlFileEngine(const QString& file);

virtual QString fileName(FileName file=DefaultName) const;
virtual void setFileName(const QString& file);

bool open(QIODevice::OpenMode openMode);
qint64 read(char* data, qint64 maxlen);
bool atEnd () const;

qint64 size() const;
qint64 pos() const;

private:
void init();

QString _filename;
QByteArray _data;
qint64 _pos;
};

#endif /* end of SQLFILEENGINE_H */

// sqlfileengine.cpp
#include "sqlfileengine.h"

#include <QByteArray>
#include <QDebug>
#include <QString>
#include <QStringList>
#include <QSqlQuery>
#include <QVariant>

// SqlFileEngineHandler
SqlFileEngineHandler::SqlFileEngineHandler()
: QAbstractFileEngineHandler()
{
}

QAbstractFileEngine* SqlFileEngineHandler::create(const QString& filename) const
{
return filename.toLower().startsWith("sql:") ? new SqlFileEngine(filename) : 0;
}

// SqlFileEngine
SqlFileEngine::SqlFileEngine()
{
}

SqlFileEngine::SqlFileEngine(const QString& file)
: _pos(0)
{
qDebug() << "SqlFileEngine ctor";
setFileName(file);
init();
}

QString SqlFileEngine::fileName(FileName file) const
{
Q_UNUSED(file);
return _filename;
}

void SqlFileEngine::setFileName(const QString& file)
{
_filename=file.section(':', 1);
qDebug() << "SqlFileEngine::setFileName() " << _filename;
}

bool SqlFileEngine::open(QIODevice::OpenMode openMode)
{
Q_UNUSED(openMode);
qDebug() << "SqlFileEngine::open() ";
return true;
}

qint64 SqlFileEngine::read(char* data, qint64 maxlen)
{
qDebug() << "SqlFileEngine::read(" << data << "," << maxlen << ")";

if(atEnd())
return 0;

if(!_data.isEmpty())
{
QByteArray curData=_data.mid(_pos, _pos+maxlen);
qstrcpy(data, curData.data());
qDebug() << data;
_pos+=curData.size();
qDebug() << _pos;
}
return 0;
}

bool SqlFileEngine::atEnd() const
{
return (_pos>=size());
}

qint64 SqlFileEngine::size() const
{
return _data.size();
}

qint64 SqlFileEngine::pos() const
{
return _pos;
}

void SqlFileEngine::init()
{
// filename == table/col/id
QStringList strl=_filename.split("/");
QSqlQuery query;

query.prepare("SELECT "+strl.at(1)+" FROM "+strl.at(0)+" WHERE id=:id");
query.bindValue(":table", strl.at(0));
query.bindValue(":col", strl.at(1));
query.bindValue(":id", strl.at(2));

qDebug() << "SELECT " << strl.at(1) << " FROM " << strl.at(0) << " WHERE id=" << strl.at(2);

if(query.exec())
{
while(query.next())
{
qDebug() << query.value(0).toByteArray();
_data=query.value(0).toByteArray();
}
}
}

// main.cpp
#include "sqlfileengine.h"

#include <QApplication>
#include <QLabel>
#include <QSqlDatabase>

int main(int argc, char* argv[])
{
QApplication app(argc, argv);

QSqlDatabase db=QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("/home/segi/user.db");
if(!db.open())
{
qWarning("Couldn't open database.");
return -1;
}

SqlFileEngineHandler engine;

QLabel label;
label.setText("<img src=\"sql:images/image/1\" />");
label.show();

return app.exec();
}

I only get something like that:


SqlFileEngine::setFileName() images/image/1
SELECT "image" FROM "images" WHERE id="1"
SqlFileEngine::open()
SqlFileEngine::read( ¶ôß3·iø캿ñs·▒ , 1 )
SqlFileEngine::read( PNG

, 16384 )
SqlFileEngine::read( ¶ôß3·iø캿ñs·▒ , 1 )
SqlFileEngine::read( PNG

, 16384 )
SqlFileEngine::read( ¶ôß3·iø캿ñs·▒8 , 1 )
SqlFileEngine::read( PNG

, 16384 )
SqlFileEngine::read( ¶ôß3·iø캿ñs·▒8 , 1 )
SqlFileEngine::read( PNG

, 16384 )
...

Is there another method to tell that the end of file is reached than return 0 in SqlFileEngine::read?
What do I wrong?
Where can I find better/more information about subclassing QAbstractFileEngine? (What have i to reimplement and so on.)

Bye

wysota
25th July 2009, 18:41
QAbstractFileEngine::extension()

wysota
25th July 2009, 19:05
By the way, here is a quick implementation.

segi
26th July 2009, 12:03
Thank you very much.
I get it working. But why the read method is called so much times?
I add a debug message to your MemFileHandler::read method and get the following output:


Starting /home/segi/tmp/memfilehandler/memfilehandler...
read: return 2844 counter 0
read: return 2844 counter 1
read: return 2844 counter 2
...
read: return 2844 counter 27

Can I avoid these additional calls?
Sadly there are a lot of errors at runtime:
I tried big images: e.g. /usr/share/wallpapers/default-1600x1200.png


Starting /home/segi/tmp/memfilehandler/memfilehandler...
read: return 16384 counter 0
read: return 16384 counter 1
read: return 16384 counter 2
...
read: return 16384 counter 27
read: return 53 counter 28
read: return 16384 counter 29
libpng error: IDAT: CRC error

What can I do to avoid this error?
On jpeg files I get:


Starting /home/segi/tmp/memfilehandler/memfilehandler...
read: return 378 counter 0
read: return 378 counter 1
Not a JPEG file: starts with 0xff 0xd9

I created a new jpg with gimp (100x100px only a white background).
qDebug() << QImageReader::supportedImageFormats():


Starting /home/sebastian/tmp/memfilehandler/memfilehandler...
("BW", "EPS", "EPSF", "EPSI", "EXR", "PCX", "PSD", "RGB", "RGBA", "SGI", "TGA", "XCF",
"bmp", "bw", "dds", "eps", "epsf", "epsi", "exr", "gif", "ico", "jp2", "jpeg", "jpg",
"mng", "pbm", "pcx", "pgm", "png", "ppm", "psd", "rgb", "rgba", "sgi", "svg",
"tga", "tif", "tiff", "xbm", "xcf", "xpm", "xv")

What's the problem?

Bye

segi
26th July 2009, 12:58
T
I tried big images: e.g. /usr/share/wallpapers/default-1600x1200.png


...
libpng error: IDAT: CRC error

What can I do to avoid this error?
Sorry, my fault. I just copied and modified your MemFileHandler::read() method.
I add
m_pos+=maxlen and the error on big files is gone.
But why is this method called so often?
Why doesn't work jpg files?

Bye

wysota
26th July 2009, 13:03
What does QImageReader::supportedImageFormats() have to do with this?

About the handler - it's probably buggy (although I can't see the bug) or incomplete. You'd have to reimplement every method from the interface and see which methods get called.

segi
26th July 2009, 13:08
What does QImageReader::supportedImageFormats() have to do with this?
I thought the jpeg error would reasoned by a missing library or something like that.

Bye

segi
26th July 2009, 13:56
I now reimplement QAbstractFileEngine::isSequential() because it's called very often in the following methods:


ICOReader::canRead(QIODevice *iodev)
static QImageIOHandler *createReadHandlerHelper(QIODevice *device, const QByteArray &format, bool autoDetectImageFormat)
somewhere in /usr/lib/kde4/plugins/imageformats/kimg_eps.so

If QAbstractFileEngine::isSequential() returns false QAbstractFileEngine::seek() is calling and the file will read once again. My isSequential() returns always true. (Now isSequential() is calling over 30 times but IHMO it's ok.)

wysota: Thank you for your efforts.

I try to solve the JPEG problem in another thread.

Bye

wysota
26th July 2009, 14:15
But the device is not sequential...

segi
26th July 2009, 15:04
Mh, I thought sequential means: "ABCDEFGH" and reading follows in the same order.
I read the data from the sql table into a QByteArray. Isn't a QByteArray sequential in RAM?
(I'm not a professional programmer. I don't really know how a QByteArray is stored in RAM.)
This little change solves all my problems:
Big files could be read in and jpg files are also read correctly.
But I want to do it in the right way.
I can't find another possibility to stop countless recalls of QAbstractFileEngine::read.
Maybe QAbstractFileEngine::seek would be right. But I don't know what to change.

Bye

wysota
26th July 2009, 17:15
Mh, I thought sequential means: "ABCDEFGH" and reading follows in the same order.
Sequential means you don't know how much data is available in the device and that you can only go "forward" with processing the data and never look back. In other words a sequential device is a device where implementing size() and seek() is not possible.



I read the data from the sql table into a QByteArray. Isn't a QByteArray sequential in RAM?
An opposite to sequential is "random access". QByteArray is a random-access (index based) container.


I can't find another possibility to stop countless recalls of QAbstractFileEngine::read.
Maybe QAbstractFileEngine::seek would be right. But I don't know what to change.

My implementation of seek() is correct, the problem is elsewhere.

wysota
26th July 2009, 18:18
Here is a corrected implementation - I wasn't updating position after a read attempt.

segi
27th July 2009, 21:21
Thank you very much. It works like expected. (and isSequential() returns false now)
The main problem was that i have testing with your little application and don't update the file position, too. I already noticed this a few posts above and changed it only in my app. Sorry for any inconvenience.
A last little question....
The application output:


/home/segi/tmp/IMG_9066.JPG
Open ok
RAW: 2188490
File read
virtual qint64 MemFileHandler::read(char*, qint64) 0 2188490
READ: 2188490
MEM: 2188490
virtual qint64 MemFileHandler::read(char*, qint64) 0 16384
READ: 16384
virtual bool MemFileHandler::seek(qint64) 0
virtual qint64 MemFileHandler::read(char*, qint64) 0 16384
READ: 16384
virtual bool MemFileHandler::seek(qint64) 0
virtual bool MemFileHandler::seek(qint64) 16384
virtual qint64 MemFileHandler::read(char*, qint64) 16384
READ: 16384
virtual qint64 MemFileHandler::read(char*, qint64) 32768 16384
READ: 16384
virtual qint64 MemFileHandler::read(char*, qint64) 49152 16384

Why does Qt call this methods a few times before reading the real data?


virtual bool MemFileHandler::seek(qint64) 0
virtual qint64 MemFileHandler::read(char*, qint64) 0 16384

Is it possible that the QImageIOHandler wants to find out which image format the file is of?

Bye

wysota
28th July 2009, 00:08
Yes, that's possible. But that's just a guess. Is it important for you in any way? You can probably force the device to be sequential to omit that, if you want. But I wouldn't do that.

segi
28th July 2009, 10:49
It's not important for my app. I don't want to change this behaviour.
I only want to know what happens.