QMarginsF() not setting margins in QWebEnginePage::printToPdf - c++

I'd like to know if there's anyone who can help me set some margins into qt QWebEnginePage::printToPdf pdf.
I'm loading an html from some url and rendering it with QWebEnginePage and then using printToPdf method to create the pdf file from html content.
But i'm not being able to set document margins (i've managed to set those margins with css, but i'd like to set it with the propert Qt class (QMarginsF)).
The code:
// NEW QT WEBENGINE PAGE
QWebEnginePage page;
// MARGINS
QMarginsF margins(75,75,75,75);
// CONNECT
QObject::connect( &page, &QWebEnginePage::loadFinished, [&page, output, &margins]()
{
// qDebug(QString("Margins: %1").arg(margins.top()).toStdString().c_str());
// PRINT TO PDF
page.printToPdf( [output](const QByteArray &data)
// LAMBDA (print2pdf first param)
{
QFile file(output);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
return;
}
file.write( data );
system( (" xdg-open " + output).toStdString().c_str() );
QApplication::exit(0);
},
// PAGE LAYOUT (print2pdf second param)
QPageLayout( QPageSize( QPageSize::A4 ), QPageLayout::Portrait, margins));
});
Thanks in advance.

Related

Why QTextDocument background color changes only once?

I am using TextEditor from the example provided with Qt (https://doc.qt.io/qt-5/qtquickcontrols1-texteditor-example.html). Here is the complete code https://code.qt.io/cgit/qt/qtquickcontrols.git/tree/examples/quickcontrols/controls/texteditor?h=5.15
I have created a custom method void DocumentHandler::setBackgroundColor(const QColor &color) to change the background color of the overall HTML document.
My problem is whenever I call a method to change the background color of QTextDocument using setDefaultStyleSheet, it is executed only once. ie, my document background changes only once. For the next calls, I can see the qDebug printing correctly, but the setDefaultStyleSheet doesn't work. However, everything works perfectly with normal text, only not with m_doc->toHtml().
How do I fix this?
If I change m_doc->setHtml("some random text"), it works as required
...
QColor DocumentHandler::backgroundColor() const
{
return m_backgroundColor;
}
void DocumentHandler::setBackgroundColor(const QColor &color)
{
m_backgroundColor = color.name(); // QColor variable
m_doc->setDefaultStyleSheet("body{background-color: '"+ color.name() +"'}"); // m_doc is QTextDocument *
m_doc->setHtml(m_doc->toHtml());
qDebug() << "BACKGROUND COLOR CHANGED" << color.name() ;
emit backgroundColorChanged();
}
In the QML, I have called it like this
...
DocumentHandlerModel {
id: document
target: textArea
cursorPosition: textArea.cursorPosition
selectionStart: textArea.selectionStart
selectionEnd: textArea.selectionEnd
backgroundColor: colorDialog2.color
onFontFamilyChanged: {
var index = Qt.fontFamilies().indexOf(document.fontFamily)
if (index === -1) {
fontFamilyComboBox.currentIndex = 0
fontFamilyComboBox.special = true
} else {
fontFamilyComboBox.currentIndex = index
fontFamilyComboBox.special = false
}
}
onError: {
errorDialog.text = message
errorDialog.visible = true
}
}
Cause
The documentation of QTextDocument::setDefaultStyleSheet says:
Note: Changing the default style sheet does not have any effect to the existing content of the document.
You try to overcome this by calling setHtml after setDefaultStyleSheet like that:
m_doc->setHtml(m_doc->toHtml());
However, this does not produce the desired result, because setDefaultStyleSheet actually embeds the background color in the HTML through the bgcolor CSS property.
To test this, add
m_doc->setHtml("<html><body><p>Test</p></body></html>");
qDebug() << m_doc->toHtml();
after
m_doc->setHtml(m_doc->toHtml());
The HTML content of m_doc from <html><body><p>Test</p></body></html>, when tested with #FF00FF, becames:
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\np, li { white-space: pre-wrap; }\n</style></head><body style=\" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;\" bgcolor=\"#ff00ff\">\n<p style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Test</p></body></html>"
Note: Please, notice that assignment: bgcolor=\"#ff00ff\.
So, by writing m_doc->setHtml(m_doc->toHtml()); you effectively return the old color. That is why the color changes only the first time.
By the way, the widget now changes its color, but has lost its content.
Solution
It is hard to find an elegant solution, because in this case markup and styles are not kept separate, as in real HTML and CSS, but the style is embedded by Qt in the markup. The one thing which comes to my mind, is to parse the content of m_doc manually.
Note: Such a solution would be extremely fragile and I advise strongly against it. Maybe instead of using a stylesheet, change the background color by setting up the palette of the widget, which renders the content of the QTextDocument on the screen.
Note: In any case this does not seem like the intended behavior, at least it does not match the description in the documentation, so I would have reported it as a bug to Qt, if I were you.
Example
Here is an example I wrote for you in order to demonstrate the proposed solution:
void DocumentHandler::setBackgroundColor(const QColor &color)
{
const QString &bgcolor("bgcolor=\"");
QString html(m_doc->toHtml());
int n = html.indexOf(bgcolor, html.indexOf("<body"));
int k = n + bgcolor.length();
m_doc->setDefaultStyleSheet("body { background-color: '" + color.name() + "' }");
if (n >= 0)
html.replace(n + bgcolor.length(), html.mid(k, html.indexOf("\"", n + bgcolor.length()) - k).length(), color.name());
m_doc->setHtml(html);
m_backgroundColor = color.name(); // QColor variable
emit backgroundColorChanged();
}

Qt Command Log using QListWidget

I am trying to build a command log on a user interface. Meaning, when the user click a button, check a box, upload some images etc, basically every time the user interacts with the user interface the action is recorded inside a QListWidget Command Log shown below. Basically this is how the ui looks as soon as the user run it:
And this is what I am try to achieve everytime the user interacts with the ui:
Below snippets of code from the constructor:
mainwindow.h
private:
QListWidget *mNewTextSQLLog;
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
mDockWidget_A = new QDockWidget(QLatin1String("Command Log"));
mDockWidget_A->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
mDockWidget_A->setMinimumHeight(30);
// Adding object to the DockWidget
mNewText = new QListWidget;
mNewText->setStyleSheet("background-color: light grey;");
mNewText->setMinimumHeight(50);
mNewText->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mDockWidget_A->setWidget(mNewText);
addDockWidget(Qt::BottomDockWidgetArea, mDockWidget_A);
resizeDocks({mDockWidget_A}, {200}, Qt::Horizontal);
}
And then some command of the ui, for example here is when the user upload images using a QPushButton and images are also shown on a QLabel:
void MainWindow::imageOriginlUploadB()
{
dir_Original_B = QFileDialog::getExistingDirectory(this, tr("Choose an image directory to load"),
filesListRight, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if(dir_Original_B.length() > 0){
QImage image;
QDir dirBObj(dir_Original_B);
QStringList filesListRight = dirBObj.entryList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
ui->labelOrigImageB->setPixmap(QPixmap::fromImage(image.scaled(125,125,Qt::KeepAspectRatio,Qt::SmoothTransformation)));
for ( int i = 0 ; i < filesListRight.size() ; i++ )
{
ui->listWidgetOriginalImgB->addItem(filesListRight.at(i));
}
ui->listWidgetOriginalImgB->update();
ui->labelOrigImageB->show();
}
}
void MainWindow::on_originalmgB_clicked()
{
imageOriginlUploadB();
}
or here is resizing the QGraphicsView using a QPushButton:
void MainWindow::on_fitViewBtn_clicked()
{
ui->graphicsViewLX->fitInView(mLeftScene->sceneRect(), Qt::KeepAspectRatio);
ui->graphicsViewRX->fitInView(mRightScene->sceneRect(), Qt::KeepAspectRatio);
}
And this is the activation of a QCheckBox:
void MainWindow::on_checkBoxScreen_A_toggled(bool checked)
{
if(ui->checkBoxScreen_A->isEnabled()) {
if(checked)
{
ui->checkBoxScreen_A->setText("Active");
ui->saveToFile_A->setEnabled(true);
ui->saveToFile_A->setStyleSheet("QPushButton{ background-color: green }");
}
else {
ui->checkBoxScreen_A->setText("Inactive");
ui->saveToFile_A->setEnabled(false);
ui->saveToFile_A->setStyleSheet("QPushButton{ background-color: grey }");
}
}
}
How to achieve that?
Thank you very much for pointing in the right direction
I think QListWidget isn't quite the right widget to use for a Command Log -- you probably want to use either a QPlainTextEdit or a QTextEdit instead. (The main difference between the two is that QPlainTextEdit is optimized for displaying large amounts of text, at the expense of not supporting some of the fancier text-formatting features provided by QTextEdit)
Once you've created one of those two widgets, adding text to the bottom of log is just a matter of calling appendPlainText() (or append()) on the widget each time you want to add another line of log-text.
Unless you want to allow the user to edit the text in the Command Log, calling setReadOnly(true) on the widget is also a good idea.
(If you also want the log-view to automatically scroll to the bottom so that the newly-added text will be visible, you can also call myCommandLogWidget->verticalScrollBar()->setValue(myCommandLogWidget->verticalScrollBar()->maximum()); after adding the text)

How to place WT widgets into PDF?

UPDATE
I have also made a post about this issue on the WT forums here http://redmine.webtoolkit.eu/issues/3042. For those of you looking for more information on styling, you will find more information there.
ORIGINAL POST
I have a problem with placing a WGroupBox (eventually a WTable) into a PDF. I am using the PDF sample code from the widget gallery, and have been trying to make it place a WTable I have into the PDF, that being unsuccessful I toned it back to a WGroupBox.
http://www.webtoolkit.eu/widgets/media/pdf-output
The "PDF Images" samples work fine.
The "Rendering HTML to PDF" is where I have problems.
I am using the example exactly as it appears on the page, with the only addition being a WContainerWidget pointer for the items that I want in the PDF.
I do not understand what I need to pass into the renderPdf function as the WString that will be passed into the WPdfRenderer render function, I have tried examining the html of the widget gallery code to compare with my own, and mimic what the gallery is doing, but everything has so far failed to make a PDF.
This is what the URL looks like after I press the "Create Pdf" button, it displays a blank page with an empty pdf "Loading" message at the bottom left.
http://127.0.0.1:8080/ui/report.pdf?wtd=Psx1WfLVpsvVDpxW&request=resource&resource=oylm6i6&rand=1
Sample code below.
//This is the code I have executing on my main page, just a group box with a few labels, a button for the sample pdf, a chart, and another button for the more advanced sample pdf(this one is broken).
//PDF Images sample/////////////////////////////////////////////////////////////////////////
Wt::WContainerWidget* container = table_container;
Wt::WResource* sample_pdf = new SamplePdfResource(container);
Wt::WPushButton* button1 = new Wt::WPushButton("Create pdf sample", container);
button1->setLink(sample_pdf);
//PDF Images sample chart///////////////////////////////////////////////////////////////////
Wt::Chart::WCartesianChart* chart = CreateCartesianChart(table_container);
Wt::WPainter* p = new Wt::WPainter((Wt::WPdfImage*)sample_pdf);
chart->paint(*p);
std::ofstream f("chart.pdf", std::ios::out | std::ios::binary);
sample_pdf->write(f);
//GroupBox to be placed into PDF////////////////////////////////////////////////////////////
Wt::WGroupBox* group_box = new Wt::WGroupBox("Example Group Box", table_container);
group_box->addStyleClass("example");
Wt::WText* text = new Wt::WText("This is a text sample within the group box 1", group_box);
text = new Wt::WText("This is a text sample within the group box 2", group_box);
text = new Wt::WText("This is a text sample within the group box 3", group_box);
//The pdf to take the GroupBox//////////////////////////////////////////////////////////////
Wt::WPushButton* button = new Wt::WPushButton("Create pdf", container);
Wt::WResource* pdf = new ReportResource(group_box, container);
button->setLink(pdf);
Below is the "Rendering HTML to PDF" code from the widget gallery, with the new container widget for a target.
namespace {
void HPDF_STDCALL error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no,
void *user_data) {
fprintf(stderr, "libharu error: error_no=%04X, detail_no=%d\n",
(unsigned int) error_no, (int) detail_no);
}
}
class ReportResource : public Wt::WResource
{
public:
ReportResource(Wt::WContainerWidget* target, Wt::WObject* parent = 0)
: Wt::WResource(parent),
_target(NULL)
{
suggestFileName("report.pdf");
_target = target;
}
virtual void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{
response.setMimeType("application/pdf");
HPDF_Doc pdf = HPDF_New(error_handler, 0);
// Note: UTF-8 encoding (for TrueType fonts) is only available since libharu 2.3.0 !
HPDF_UseUTFEncodings(pdf);
renderReport(pdf);
HPDF_SaveToStream(pdf);
unsigned int size = HPDF_GetStreamSize(pdf);
HPDF_BYTE *buf = new HPDF_BYTE[size];
HPDF_ReadFromStream (pdf, buf, &size);
HPDF_Free(pdf);
response.out().write((char*)buf, size);
delete[] buf;
}
private:
Wt::WContainerWidget* _target;
void renderReport(HPDF_Doc pdf)
{
std::stringstream ss;
_target->htmlText(ss);
std::string out = ss.str();
std::string out_id = _target->id();
std::string out_parent_id = _target->parent()->id();
renderPdf(Wt::WString::tr(???), pdf);
}
void renderPdf(const Wt::WString& html, HPDF_Doc pdf)
{
HPDF_Page page = HPDF_AddPage(pdf);
HPDF_Page_SetSize(page, HPDF_PAGE_SIZE_A4, HPDF_PAGE_PORTRAIT);
Wt::Render::WPdfRenderer renderer(pdf, page);
renderer.setMargin(2.54);
renderer.setDpi(96);
renderer.render(html);
}
};
//PDF Images code
class SamplePdfResource : public Wt::WPdfImage
{
public:
SamplePdfResource(Wt::WObject *parent = 0)
: Wt::WPdfImage(400, 300, parent)
{
suggestFileName("line.pdf");
paint();
}
private:
void paint()
{
Wt::WPainter painter(this);
Wt::WPen thickPen;
thickPen.setWidth(5);
painter.setPen(thickPen);
painter.drawLine(50, 250, 150, 50);
painter.drawLine(150, 50, 250, 50);
painter.drawText(0, 0, 400, 300, Wt::AlignCenter | Wt::AlignTop, "Hello, PDF");
}
};
//Cartesian chart initialization
Wt::Chart::WCartesianChart* CreateCartesianChart(Wt::WContainerWidget* parent)
{
Wt::WStandardItemModel *model = new Wt::WStandardItemModel(PERFORMANCE_HISTORY, 2, parent);
//Create the scatter plot.
Wt::Chart::WCartesianChart* chart = new Wt::Chart::WCartesianChart(parent);
//Give the chart an empty model to fill with data
chart->setModel(model);
//Set which column holds X data
chart->setXSeriesColumn(0);
//Get the axes
Wt::Chart::WAxis& x_axis = chart->axis(Wt::Chart::Axis::XAxis);
Wt::Chart::WAxis& y1_axis = chart->axis(Wt::Chart::Axis::Y1Axis);
Wt::Chart::WAxis& y2_axis = chart->axis(Wt::Chart::Axis::Y2Axis);
//Modify axes attributes
x_axis.setRange(0, PERFORMANCE_HISTORY);
x_axis.setGridLinesEnabled(true);
x_axis.setLabelInterval(PERFORMANCE_HISTORY / 10);
y1_axis.setRange(0, 100);
y1_axis.setGridLinesEnabled(true);
y1_axis.setLabelInterval(10);
y2_axis.setRange(0, 100);
y2_axis.setVisible(true);
y2_axis.setLabelInterval(10);
//Set chart type
chart->setType(Wt::Chart::ChartType::ScatterPlot);
// Typically, for mathematical functions, you want the axes to cross at the 0 mark:
chart->axis(Wt::Chart::Axis::XAxis).setLocation(Wt::Chart::AxisValue::ZeroValue);
chart->axis(Wt::Chart::Axis::Y1Axis).setLocation(Wt::Chart::AxisValue::ZeroValue);
chart->axis(Wt::Chart::Axis::Y2Axis).setLocation(Wt::Chart::AxisValue::ZeroValue);
// Add the lines
Wt::Chart::WDataSeries s(1, Wt::Chart::SeriesType::LineSeries);
chart->addSeries(s);
//Size the display size of the chart, has no effect on scale
chart->resize(300, 300);
return chart;
}
The pdf renderer takes a HTML string. So I think the solution is
renderPdf(WString::fromUTF8(out), pdf);

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
}
}

Qt QTextBrowser How to capture text and change its cursor

I have QTextBrowser with delegate class ,
in the QTextBrowser i set html text with links , but in this html i have text that looks like
link with css like this:
"<span style=\" font-size:8pt; text-decoration: underline; color:#ffffff;\">dummy_link</span>"
i like to change the cursor type to point when the mouse over it . and then trigger Qt function .
the problem is that when i try to implement in the QTextBrowser with delegate class
the mouseMoveEvent(QMouseEvent *e) like this : all other links ( tags )loss there pointer cursors here is when i do :
void TextBrowserDelegate::mouseMoveEvent(QMouseEvent *e)
{
QCursor newCursor = cursor();
Qt::CursorShape CurrCursor = newCursor.shape();
QTextCursor tc = cursorForPosition( e->pos() );
tc.select( QTextCursor::WordUnderCursor );
QString sharStr = tc.selectedText();
if(sharStr == "dummy_link")
{
Qt::CursorShape newCursor = Qt::PointingHandCursor;//Qt::ArrowCursor;
setCursor(newCursor);
}
e->accept();
}
what im doing wrong here ?
With the code you've provided, it looks like only a link with the text "dummy_link" will get the cursor that you are choosing. The QTextBrowser class should automatically change the cursor if you set the proper flags.
QTextBrowser::setOpenLinks(true);
If your TextBrowserDelegate inherits from QTextBrowser you can use the following code in your constructor:
TextBrowserDelegate::TextBrowserDelegate(QWidget *parent){
this->setOpenExternalLinks(true);
this->setOpenLinks(true);
connect(this,SIGNAL(anchorClicked(QUrl)),this,SLOT(onClickedLink(QUrl)));
}
void TextBrowserDelegate::onClickedLink(QUrl url){
//do something with url
}