Qt: Print raw text - c++

I need to output a ZPL script to a Zebra printer in a Qt application.
The printer is on a smb share network configured as raw printer in the client computer.
Everything goes fine if I call cupsPrintFile( "printer_name", "./tmp_print_file.zpl", "", 0, NULL ) from a C++ test program.
If I use QTextDocument::print() using the same text in "./tmp_print_file.zpl" as document, nothing gets printed.
I sniffed the network and found that the data being sent to the printer server is not raw data, but, a postscript!
Is there any way to get the data sent to the printer with no modification at all?
Let me be clear that I don't want to render a text, but just send the label script, that is ready to print, directly to the printer, that understands the ZPL protocol.
Thanks for all.
EDIT:
As #Martin said, I tried:
printer.setOutputFormat( QPrinter::NativeFormat );
QTextDocument *doc = new QTextDocument( QString( label ), this );
doc->print( &printer );
but it didn't work.

Before I start, must thank Dave. His suggestion to bypass the temporary file while printing with CUPs works fine.
Now, my conclusion: There is no easy way to print raw data using Qt only.
Maybe creating custom QPainter or going down to the bits of QPrinter could give a solution, but it would take me too much time.
The final solution is simply use CUPs API inside my Qt application. Unfortunatelly, it is not portable.
Here is a snippet:
#include <cups/cups.h>
//...
int print_label( const char *text, const char *printer_name, const char *job_name )
{
int jobId = 0;
jobId = cupsCreateJob( CUPS_HTTP_DEFAULT, printer_name, job_name, 0, NULL );
if ( jobId > 0 )
{
qDebug( ) << "Printing job #" << jobId << " (\"" << job_name << "\").";
const char* format = CUPS_FORMAT_TEXT; // CUPS_FORMAT_POSTSCRIPT;
cupsStartDocument( CUPS_HTTP_DEFAULT, printer_name, jobId, text, format, true );
cupsWriteRequestData( CUPS_HTTP_DEFAULT, text, strlen( text ) );
cupsFinishDocument( CUPS_HTTP_DEFAULT, printer_name );
}
return jobId;
}
//...
// Now, inside any Qt function (may be a slot):
QPrinter printer;
QPrintDialog *dialog = new QPrintDialog( &printer, this );
dialog->setWindowTitle( tr( "Zebra label" ) );
if ( dialog->exec( ) != QDialog::Accepted )
return;
// This is the sample label. Can be anything.
const char label[] =
"^XA~TA000~JSN^LT0^MNW^MTD^PON^PMN^LH0,0^JMA^PR4,4^MD0^JUS^LRN^CI0^XZ\n"
"^XA\n"
"^MMT\n"
"^LL0600\n"
"^PW900\n"
"^LS0\n"
"^BY2,3,54^FT24,109^BCN,,Y,N\n"
"^FD>;43210000>773>0000^FS\n"
"^PQ1,0,1,Y^XZ\n";
// Informative only.
ui->txtLabelScript->setPlainText( label );
// Call the printing function.
if ( print_label( label, printer.printerName( ).toAscii( ), "Zebra_Label" ) == 0 )
qDebug( ) << "CUPS Error: " << ippErrorString( cupsLastError( ) );
And it's done.
Don't forget to link libcups (-lcups).
I still hope any buddy to add another solution prooving that Qt-only is possible. Meanwhile, it is enough.
Thanks everybody.

Could you just do exactly what you did in your test program:
Create a temporary file (QTemporaryFile).
Send the contents to the file.
Call your cupsPrintFile method.
Or there is probably a way with the CUPS API to bypass the temporary file. Disclaimer: I have absolutely no experience with the CUPS API; this is just based on a cursory look at some online documentation. Looks like perhaps the following sequence:
cupsCreateJob > cupsStartDocument > cupsWriteRequestData > cupsFinishDocument
If that works, you just need to convert your QString to the correct byte encoding.

Thanks for fljx's code, it is very useful for me.
I changed a litte for sending raw text to zebra printer .
const char* format = CUPS_FORMAT_RAW;

Take a look at QPrinter(),
QTextDocument is designed to render formatted text.

Related

Qt error: QSqlQuery::value: not positioned on a valid record when trying to retrive a stat from a table (QComboBox)

this is the code I'm using to get the GoalsFor stat from this table after the user chooses a team from a ComboBox like this using this code:
void MainWindow::on_hometeam_currentIndexChanged(const QString &hometeam)
{
QString hteam(hometeam);
QSqlQuery q("SELECT GoalsForH FROM teams WHERE TEAM=hteam");
q.exec();
int fieldNo = q.record().indexOf("hometeam");
q.next();
qDebug() << q.value(fieldNo).toInt();
}
But this is what the debugger always shows whenever I choose a team:
QSqlQuery::value: not positioned on a valid record
0
I tried everything I came across on the net and it seems like I'm doing exactly what other users or even the documentation say yet to no avail, any help would be appreciated, thanks !
The problem seems to be with the SQL itself; since hteam isn't actually defined in SQL. I would instead recommend using the prepare function, which can also deal with filtering strings to prevent SQL injections. Something like the below should give you the result you are looking for.
void MainWindow::on_hometeam_currentIndexChanged(const QString &hometeam)
{
QString hteam(hometeam);
QSqlQuery q;
q.prepare("SELECT GoalsForH FROM teams WHERE TEAM=:hteam");
q.bindValue(":hteam", hteam);
if ( !q.exec() ) {
qDebug() << q.lastError();
} else {
int fieldNo = q.record().indexOf("GoalsForH");
while ( q.next() ) {
qDebug() << q.value(fieldNo).toInt();
}
}
}
You were also grabbing indexOf("hometeam"), which isn't actually returned by the query. This then returns -1 which wouldn't be valid. Change this to "GoalsForH" to get the proper column index.

Is there a way to effectively update CAN data in QML?

Currently I am receiving CAN data in real time using socketcan API in main.cpp file.
I am constantly updating the data frame of CAN in one variable in main.cpp.
I want to express the gauge in real time by passing the variable containing the CAN data frame in main.cpp to the QML animation gauge.
I need to detect the change of the variable containing CAN data in QML in real time. I wonder if there is an effective way.
I tried to share data with QML using emit.
However, the function written by emit inside device-> connect (device, & QCanBusDevice :: framesReceived, [device] () {...} does not work.
When using it, I get the error
'this' cannot be implicitly captured in this context.
I looked up the error, but did not find the answer.
if (QCanBus::instance()->plugins().contains(QStringLiteral("socketcan"))) {
qWarning() << "plugin available";
}
QString errorString;
QCanBusDevice *device = QCanBus::instance()->createDevice(
QStringLiteral("socketcan"), QStringLiteral("vcan0"), &errorString);
if (!device) {
qWarning() << errorString;
} else {
device->connectDevice();
std::cout << "connected vcan0" << std::endl;
device->connect(device, &QCanBusDevice::framesReceived, [device]() {
QCanBusFrame frame = device->readFrame();
QString testV = frame.toString();
QString qvSpeed = frame.payload();
std::string text = testV.toUtf8().constData();
std::string vSpeed = qvSpeed.toUtf8().constData();
//At that point the vVal values ​​are being updated in real time.
//I want to pass the updated vVal to qml gui in real time.
int vVal = static_cast<int>(frame.payload()[0]);
//emit sendMessage(vVal); // 'this' cannot be implicitly captured in this context error.
std::cout << text << std::endl;
});
}
As of now, main.cpp can't send the data and QML can't solve the error.
Inside device-> connect, emit sendMessage (vVal); will cause "'this' cannot be implicitly captured in this context" error.
I'm wondering if there is a good way to implement animation by expressing QML GUI data in real time.
Your capture clause only captures device. You need to also explicitly capture this:
device->connect(device, &QCanBusDevice::framesReceived, [this,device]{ /*...*/ });
BTW, note that there's no need to specify the () for a no-args lambda expression.

print QPlainTextEdit document plain text only

I'm wondering how to print the document of a QPlainTextEdit component without any colors, backgrounds or formats ( plain text only ). The code I have is printing the background ( white on black in my case ).
QPrinter printer;
QPrintDialog dialog( &printer, NULL );
dialog.setWindowTitle( tr( "Print Content" ) );
if ( isSelection ) {
dialog.addEnabledOption( QAbstractPrintDialog::PrintSelection );
}
if ( dialog.exec() == QDialog::Accepted ) {
document->print(&printer);
}
Any ideas ?? Thanks in advance !
Use this:
QPrinter printer(QPrinter::HighResolution);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("output.pdf");
QString buffer = ui->plainTextEdit->document()->toHtml();
ui->plainTextEdit->setPlainText(ui->plainTextEdit->toPlainText());
ui->plainTextEdit->document()->print(&printer);
ui->plainTextEdit->clear();
ui->plainTextEdit->appendHtml(buffer);
The main idea is to print only plainText without formatting, but set normal formatted text after printing, so user will not lose formatted data.
I thought about improvement, so I wrote also this:
QPrinter printer(QPrinter::HighResolution);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("output.pdf");
QTextDocument *buffer = ui->plainTextEdit->document()->clone();
buffer->setPlainText(ui->plainTextEdit->toPlainText());
buffer->print(&printer);
Why is it better? We clone QTextDocument *buffer so we work with this new document. Our plainTextEdit remains untouchable, so user will not see unformatted text while print. But don't forget delete buffer When you don't need this clone aby more.
Result:
In pdf:
As you can see, there is no formatting.

Qt How to Prevent dropping item FROM application TO Windows File Explorer

I'm looking for a clean and cross-platform way to Prevent dropping an item FROM a Qt application TO Windows File Explorer (or other OS equiv.)
The following diagram shows the desired behavior:
I haven't had luck finding examples online or hacking a work-around together, but it seems like it would be a common-enough use-case that there would be a well designed and implemented solution floating around.
What I've tried and do not have working:
Detecting the Drag and Killing It:
detecting the QDragEnterEvent, QDragMoveEvent, QDragLeaveEvent
comparing the answerRect() or pos() of the event to the Geometry of
the Window or Widget to detect if the drag has left the application
This is pretty hacky (and not working at them moment) and I'm hoping you can point me towards a more elegant solution.
(UPDATE - tried changing mimeType, but Windows File Explorer still accepts the drop)
Changing the MIME Type to a custom type:
Pre: the "Widget w/ Drag & Drop" from the diagram above is a QTreeView with a QFileSystemModel model
Sub-classing the QFileSystemModel and overriding the mimeTypes() function like the code below
From the qDebug() output, it looks like the mimeType is correctly being set, but Windows File Explorer still accepts the drop :/
QStringList MyFileSystemModel::mimeTypes() const
{
QStringList customMimeTypes;
customMimeTypes << QString("UnicornsAndRainbows/uri-list");
qDebug() << "customMimeTypes: " << customMimeTypes;
return customMimeTypes;
}
Please let me know when you have a chance.
Thanks! :)
Dmitry Sazonov gave the correct answer. I will explain how I implemented it below. Dmitry, if you want cred, post it as an answer and not a comment so I can accept it as the answer.
What I did wrong on my question update based on Dmitry's suggestion was to override the QFileSystemModel::mimeTypes() when, in fact, I had to modify the QTreeView::mouseMoveEvent() and QTreeView::dropEvent().
//---------------------------------------------------------
void MyTreeView::mouseMoveEvent( QMouseEvent *event )
{
if( !(event->buttons() & Qt::LeftButton) )
{
return; // we only care about left mouse drags at the moment
}
if( (event->pos() - dragStartPosition).manhattanLength() < QApplication::startDragDistance() )
{
return; // a buffer when calculating waht qualifies as a "drag event"
}
QDrag *drag = new QDrag( this );
QMimeData *mimeData = new QMimeData();
QByteArray data;
const QStringList selectedPaths = this->getSelectedPaths(); // custom helper method
foreach( QString path, selectedPaths )
{
data.append( path ).append( ";" ); // using ';' as path deliminator
}
data.chop( 1 );
//--- this sets the custom MIME Type filter
mimeData->setData( CUSTOM_MIMETYPE_STRING, data );
drag->setMimeData( mimeData );
Qt::DropAction dropAction = drag->exec( Qt::CopyAction );
}
//---------------------------------------------------------
void MyTreeView::dropEvent( QDropEvent *event )
{
// ...
QList<QByteArray> paths;
//--- this filters based on our custom MIME Type
paths = event->mimeData()->data( CUSTOM_MIMETYPE_STRING ).split(';');
foreach( QByteArray path, paths )
{
// do something with the file paths
}
}

How to retrieve file name with full path for writing data into it

Brief description: IN my Qt utility , I want that as soon as user hit the close button following things happens
1) A File Dialog box appear with save /cancel options and with
default file name in it.
2) If the user has saved the file in different location on his
computer , I should be able to write logs on that saved file.
I have done the first part but I am clueless on how to retrieve the file name with full path when the user has already closed the dialog box.
MY code for part 1 is given below.
void some_class ::on_write_file()
{
// some code ..
bla bla bla
switch( set_file_name_for_logging( QString::fromStdString( filename ) , this ) )
{
case QDialog::Accepted :
std::cout <<" Retrive filename and full path name from the location where user has saved the file " and write on it;
break;
case QDialog::Rejected :
break;
default :
throw_error( "Unexpected return value from save_ dump file dialog" );
break;
}
}
}
int set_file_name_for_logging( const QString& str, som_class *cal )
{
QFileDialog file_dialog( cal );
file_dialog.setDirectory(".");
file_dialog.setAcceptMode(QFileDialog::AcceptSave);
file_dialog.setNameFilter( ("Text files (*.txt )") );
file_dialog.selectFile( str );
int ret = file_dialog.exec();
return ret ;
}
You can access the chosen file using file_dialog.selectedFiles(). Also take a look at static function QFileDialog::getSaveFileName.
As written in my comment, you can use the following method:
QStringList QFileDialog::selectedFiles() const
Returns a list of strings containing the absolute paths of the selected files in the dialog. If no files are selected, or the mode is not ExistingFiles or ExistingFile, selectedFiles() contains the current path in the viewport.
Also, note that your code will need some refactoring to actually pass either the QFileDialog over or the path string itself. Currently, there is no direct access to them.
If you chooes to pass the QFileDialog somehow, you can get the string list, and it will only contain one item in your case, so you could use the first() convenience method then.