QWebEngineView Open In External Browser - c++

I'm in the process of moving my code from QtWebKit to QtWebEngine. In general, the transition has been fairly smooth, however, I'm stuck on one particular issue. I use a QWebEngineView to display a Google Maps page. Some of the markers placed have have infowindows that pop up "Click Here for More Information" which opens the link in an external browser.
Using QtWebKit, this was fairly easy through the setLinkDelegation policy. However, it seems a little more complex here. I've tried to follow the example but somehow I need to redefine QWebEnginePage within QWebEngineView. Below is what I've come up with so far. Any idea how I can actually connect this all up?
Thanks
#ifndef MYQWEBENGINEVIEW_H
#define MYQWEBENGINEVIEW_H
#include <QWebEngineView>
#include <QDesktopServices>
class MyQWebEnginePage : public QWebEnginePage
{
Q_OBJECT
public:
MyQWebEnginePage(QObject* parent = 0) : QWebEnginePage(parent){}
bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame)
{
qDebug() << "acceptNavigationRequest("<<url << "," << type << "," << isMainFrame<<")";
if (type == QWebEnginePage::NavigationTypeLinkClicked)
{
QDesktopServices::openUrl(url);
return false;
}
return true;
}
};
class MyQWebEngineView : public QWebEngineView
{
Q_OBJECT
public:
MyQWebEngineView(QWidget* parent = 0);
MyQWebEnginePage* page() const;
};
#endif // MYQWEBENGINEVIEW_H

You don't need the second part. Try this:
QWebEngineView *view = new QWebEngineView();
MyQWebEnginePage *page = new MyQWebEnginePage();
view->setPage(page);

Related

uniquely identify a QQuickItem instance across app restart

Context:
We are developing an IDE for VR/AR effects editing. Users would like to have MACRO recording. Something similar to Selenim/AutoIT in spirit, but specific for this app with deep dataModel integration. Users are trained JS developers. Software GUI is Qt/QML (mostly qml)
Current Blocker
I need a way to uniquely identify a specific QQuickItem by its objectName:
across multiple restart of the app.
across platform (OS/Cpu/...)
name should be human readable enough for use in JS Macro
Should serialise enough information from QQuickItem/QEvent to post a valid event
It is acceptable to use CI static analysis to ensure all item have a valid unique name and detect name changes.
The problem here is some button, for instance the number buttons have exactly the same fqn/path/name unless I use and ugly hack, renaming it by the text of its sub-component:
Component.onCompleted: {
objectName = "button." + button.text
}
property bool keepObjectName: true
Here a fully qualified name based on metaObject()->className() is not enough, as it wont be unique. qml ids are not usable as per documentation.
Question: Is there a way in Qt to obtain such unique identifier?
Related Question:
Qt GUI event recording and playback
Abandoned Road:
print trace of signals/slots called
Reason: Most events in IDE do not use connect() the standard Qt way. Too Low level.
Draft Technical Solution / POC (mostly for reference)
https://doc.qt.io/qt-5/qtdoc-demos-calqlatr-example.html
Using the Qt QML calculator example here. Added this to main.cpp:
#include "Filter.h"
#include <unordered_map>
std::unordered_map<QString,QQuickItem*> map;
void install(QQuickItem* root, KeyPressEater* keyPressEater, const QString& fqn){
if(!root->property("keepObjectName").isValid() || !root->property("keepObjectName").value<bool>()){
qDebug("setObjectName: %s", root->metaObject()->className());
qDebug("Object original name:%s", root->objectName().toStdString().c_str());
root->setObjectName(root->metaObject()->className());
}
else{
qDebug("unchanged Object Name: %s", root->objectName().toStdString().c_str());
}
auto fqnRoot = fqn.isEmpty() ? root->objectName() : fqn + "." + root->objectName();
qDebug("Object fqn: %s", fqnRoot.toStdString().c_str());
root->installEventFilter(keyPressEater);
keyPressEater->insert(root,fqnRoot);
for(auto* child: root->childItems()){
QString fqn = fqnRoot + "." + child->metaObject()->className();
install(child, keyPressEater,fqnRoot);
}
qDebug("\n\n\n");
}
Filter.h looks like this:
#ifndef FILTER_H
#define FILTER_H
#include <QQuickItem>
#include <QtCore>
#include <unordered_map>
#include <QQuickItem>
class KeyPressEater : public QObject
{
Q_OBJECT
public:
void insert(QQuickItem* item,const QString& str){
if(map2.find(str) != map2.end())
{
qDebug("Duplicate object name:%s", str.toStdString().c_str());
}
else{
qDebug("Object name:%s", str.toStdString().c_str());
map.insert({item,str});
map2.insert({str,item});
}
}
void play(){
playing = true;
for(auto p: records){
//p.second->event(p.first);
QCoreApplication::postEvent(p.second, p.first);
QCoreApplication::sendPostedEvents();
}
playing = false;
}
protected:
bool eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress) {
//QMouseEvent *keyEvent = static_cast<QMouseEvent *>(event);
qDebug("click");
QQuickItem *item = static_cast<QQuickItem *>(obj);
qDebug("%s",map[item].toStdString().c_str());
qDebug("%s",item->metaObject()->className());
if(!playing){
records.push_back({event->clone(),item});
}
return false;
} else {
// standard event processing
if(event->type() == QEvent::KeyPress){
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == 32){
play();
}
}
return QObject::eventFilter(obj, event);
}
}
std::unordered_map<QQuickItem*,QString> map;
std::unordered_map<QString,QQuickItem*> map2;
std::atomic<bool> playing = false;
std::vector<std::pair<QEvent*,QObject*>> records;
};
#endif // FILTER_H
How to use:
Copy the above the main.cpp and new filter.h file in the calculator app example. Call install in main function:
install(view.rootObject(), &keyPressEater,"");

QSqlDatabase: How to avoid 'qt_sql_default_connection' still in use & duplicate connection related warning?

Sorry if this is a trivial question but I have been trying to build a small .ui that used QSQLITE as database and that uses QTableView to show 4 columns on a default database file as example.
I debugged the problem in every side, changed logical operations of the SQL and restructured the constructor in a more simple way but the error still stays.
After finishing to set up all parameters I am getting this error:
QSqlDatabasePrivate::removeDatabase: connection 'qt_sql_default_connection' is still in use, all queries will cease to work.
And
QSqlDatabasePrivate::addDatabase: duplicate connection name 'qt_sql_default_connection', old connection removed.
I looked on several sources that describe this error such as this source, this other source. Also this was useful but still nothing happens. Official documentation suggests a "wrong" and "right" way to do that here. but the error still stays. After all these different options I brought back the code to a more concise way and I hope that someone can shed light on this issue.
Below the snipped of code:
mainwindow.h
private:
QString temporaryFolder;
dataInfo *mNewDatabaseImages;
QSqlTableModel *mNewTableImages;
mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
temporaryFolder = "/home/to/Desktop/tempDBFolder/tmp.db";
QFile dbRem(temporaryFolder);
dbRem.remove();
mNewDatabaseImages = new dataInfo(this);
mNewDatabaseImages->initDataBase(temporaryFolder);
mNewDatabaseImages->confDataBase();
mNewTableImages = new QSqlTableModel(this, mNewDatabaseImages->getDatabase());
mNewTableImages->setTable("leftCamTable");
mNewTableImages->select();
ui->bookMarkTableView->setModel(mNewTableImages);
ui->bookMarkTableView->showColumn(true);
}
datainfo.h
#ifndef DATAINFO_H
#define DATAINFO_H
#include <QObject>
#include <QSqlDatabase>
#include "imageparam.h"
class dataInfo : public QObject
{
Q_OBJECT
public:
explicit dataInfo(QObject *parent = nullptr);
bool initDataBase(const QString &nameDB);
bool confDataBase();
bool addItem(ImageParam* imageItem);
QSqlDatabase getDatabase();
private:
QString mError;
QSqlDatabase mDBImages;
};
#endif // DATAINFO_H
datainfo.cpp
#include "datainfo.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
#include <QVariant>
#include <QMessageBox>
#define CREATE_TABLE \
" CREATE TABLE IF NOT EXISTS imageTable" \
" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL" \
" path1 TEXT NOT NULL" \
" path2 TEXT NOT NULL" \
" imageA BLOB NOT NULL" \
" imageB BLOB NOT NULL)"
dataInfo::dataInfo(QObject *parent) : QObject(parent)
{}
bool dataInfo::initDataBase(const QString &nameDB)
{
mDBImages = QSqlDatabase::addDatabase("QSQLITE");
mDBImages.setDatabaseName(nameDB);
bool ok = mDBImages.open();
if(!ok) {
mError = mDBImages.lastError().text();
qDebug() << mError;
}
return ok;
}
bool dataInfo::confDataBase()
{
QSqlQuery qry;
bool ok = qry.exec(CREATE_TABLE);
if(!ok) {
mError = qry.lastError().text();
}
return ok;
}
bool dataInfo::addItem(ImageParam *imageItem)
{
QSqlQuery qry;
qry.prepare("INSERT INTO imageTable (path1, path2, imageA, imageB)" \
" VALUES (?,?,?,?)");
qry.addBindValue(imageItem->path1());
qry.addBindValue(imageItem->path2());
qry.addBindValue(imageItem->image1());
qry.addBindValue(imageItem->image2());
bool ok = qry.exec();
if(!ok) {
mError = qry.lastError().text();
}
return ok;
}
QSqlDatabase dataInfo::getDatabase()
{
return mDBImages;
}
I looked also at this post that suggested to set a name to the databse first but I already do that in the function initDataBase(const QString &nameDB). Here the post that suggested the procedure, which is below:
db->setDatabaseName("name");
if(!db->open()) {
qDebug() << "Error opening ";
return false;
}
Please shed light on the possible solution.
Short answer
You should specify the database you want to run your QSqlQuery on, otherwise they will run on the default database. You can specify the database in QSqlQuery's constructor with
QSqlQuery query(QSqlDatabase::database("my-db"));
You are keeping a copy of the QSqlDatabase as a member of your dataInfo class, which will prevent it prevent closing properly. Instead, just use the static QSqlDatabase::database("name") when needed.
auto db = QSqlDatabase::database("my-db");
Details
Providing the right database to QSqlQuery
Change all your uses of QSqlQuery. For example for confDataBase:
bool dataInfo::confDataBase()
{
// Explicitly provide your database to the query
// Otherwise the default database is used
QSqlQuery qry(getDatabase());
bool ok = qry.exec(CREATE_TABLE);
if(!ok) {
mError = qry.lastError().text();
}
return ok;
}
Not keeping QSqlDatabase attributes
From the documentation:
Warning: It is highly recommended that you do not keep a copy of the QSqlDatabase around as a member of a class, as this will prevent the instance from being correctly cleaned up on shutdown. If you need to access an existing QSqlDatabase, it should be accessed with database(). If you chose to have a QSqlDatabase member variable, this needs to be deleted before the QCoreApplication instance is deleted, otherwise it may lead to undefined behavior.
Store your database's name in your class instead, and change your getDatabase to
dataInfo.cpp
bool dataInfo::initDataBase(const QString &nameDB)
{
// Save database's name
mDBName = nameDB;
// Use the database locally, without storing it
auto dbImages = QSqlDatabase::addDatabase("QSQLITE", nameDB);
bool ok = dbImages.open();
if(!ok) {
mError = dbImages.lastError().text();
qDebug() << mError;
}
return ok;
}
QSqlDatabase dataInfo::getDatabase()
{
return QSqlDatabase::database(mDBName);
}
dataInfo.h
private:
QString mError;
QString mDBName;
Qt's code generating the warning
To have a look at the actual code producing the error: https://code.woboq.org/qt5/qtbase/src/sql/kernel/qsqldatabase.cpp.html#170
invalidateDb is used when connections are added or removed, and will trigger the error if the reference count > 1. As you are holding onto one, this will trigger the error.

QWebEngineView request body interception

A user using QWebEngineView in my application fills some form. This form uses post method to submit data to server. How can I get params from user's body request?
I've found such thing as QWebEngineUrlRequestInterceptor, but it works only for urls.
You can use QWebEnginePage::acceptNavigationRequest.
Whenever a form is submitted, you can get the contents of input by using JavaScript and then accept the request to proceed as usual.
Like Anmol Gautam said, you need to reimplement QWebEnginePage::acceptNavigationRequest function and get needed data using JavaScript.
Here is an example how to do it:
mywebpage.h
#include <QWebEnginePage>
class MyWebPage : public QWebEnginePage
{
Q_OBJECT
public:
explicit MyWebPage(QWebEngineProfile *profile = Q_NULLPTR, QObject *parent = Q_NULLPTR);
protected:
bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame);
}
mywebpage.cpp
MyWebPage::MyWebPage(QWebEngineProfile *profile, QObject *parent):QWebEnginePage(profile, parent),
{
//...
}
bool MyWebPage::acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame)
{
if(type == QWebEnginePage::NavigationTypeFormSubmitted)
{
qDebug() << "[FORMS] Submitted" << url.toString();
QString jsform = "function getformsvals()"
"{var result;"
"for(var i = 0; i < document.forms.length; i++){"
"for(var x = 0; x < document.forms[i].length; x++){"
"result += document.forms[i].elements[x].name + \" = \" +document.forms[i].elements[x].value;"
"}}"
"return result;} getformsvals();";
this->runJavaScript(jsform, [](const QVariant &result){ qDebug() << "[FORMS] found: " << result; });
}
return true;
}
use QWebEngineView::setPage to set your WebPage subclass to WebView before you call WebViews load function.
Here is a link for more info about HTML DOM forms Collection

Using Qt Framework create an API call in c++ class

I am new to the Qt Framework and I have to implement an API call to Login the users. I created an ApiManager class where I want to create only the calls that the app will use, so that they are accessible from C++ classes and also the QML.
I created a local function that simulates a login, but I want to implement the real API call now. I just don't know how to implement it and what kind of libraries I have to import or include.
Header file apimanager.h
#ifndef APIMANAGER_H
#define APIMANAGER_H
#include <QObject>
class ApiManager : public QObject
{
Q_OBJECT
Q_ENUMS(UserLevel)
public:
enum UserLevel {
UL_Unknown = 0,
UL_Master = 1,
UL_Administrator = 2,
UL_Operator = 3
};
explicit ApiManager(QObject *parent = nullptr);
signals:
void loginComplete(bool logged, UserLevel userLevel);
public slots:
void login(QString serverAddress, QString username, QString
password);
};
#endif // APIMANAGER_H
Class apimanager.cpp
#include "apimanager.h"
#include <QDebug>
ApiManager::ApiManager(QObject *parent) : QObject(parent)
{
}
void ApiManager::login(QString serverAddress, QString username,
QString password)
{
qDebug() << "loggin into" << serverAddress << "with user" <<
username;
bool logged = false;
UserLevel ul = UserLevel::UL_Unknown;
if (username=="master" && password=="123") {
logged = true;
ul = UserLevel::UL_Master;
} else if (username=="admin" && password=="123") {
logged = true;
ul = UserLevel::UL_Administrator;
} else if (username=="operator" && password=="123") {
logged = true;
ul = UserLevel::UL_Operator;
}
emit loginComplete(logged, ul);
}
If anyone can tell me how to write an API call with a similar structure of my "fake" login function I'll be thankful.
Thanks to all
#Marco,
Here is the skeletal Http request response example.
It is using Network classes from Qt to send the request and receive the response for the same.
https://github.com/ramkumarrammohan/Qt_HttpNetworkRequest
If you have any doubts about this please let me know, Thanks

Custom widget in Qt Designer with pre-existing layout in frame

A project uses custom frame widget that implements a kind of "cover" that hides widgets (think of it as of security cover that prevents pressing buttons).This is a required visual design. Qt version is 4.6
#ifndef CQFRAME_H
#define CQFRAME_H
#include <QFrame>
#include <QtDesigner/QDesignerExportWidget>
//! [0] //! [1]
class CQFrame : public QFrame
// our agreement about "custom" widgets is to start them with CQ
{
Q_OBJECT
Q_ENUMS(FrameColorStyle)
Q_PROPERTY(FrameColorStyle colorStyle READ getColorStyle WRITE setColorStyle)
Q_PROPERTY(bool border READ border WRITE setBorder)
//! [0]
private:
bool curCover;
QFrame *frameCover;
public:
enum FrameColorStyle {fcDark, fcLight, fcTransparent, fcSystemDefault, fcRed, fcGreen, fcBlue};
CQFrame(QWidget *parent = 0);
void setCoverPropertie();
void setCover(bool state);
protected:
void resizeEvent(QResizeEvent *event);
FrameColorStyle m_colorStyle;
QString pstylebord;
bool m_border;
//! [2]
};
//! [1] //! [2]
#endif
I omitted getters and setters that are irrelevant to the problem. Here is implementation:
void CQFrame::setCoverPropertie()
{
QString str, strAlpha, gradient1, gradient2;
strAlpha.setNum(200);
gradient1 = "rgba("+str.setNum(cwDisableColor.red())+", "
+str.setNum(cwDisableColor.green())
+", "+str.setNum(cwDisableColor.blue())
+" ," +strAlpha+ " )";
gradient2 = "rgba("+str.setNum(cwLbColor.red())+", "
+str.setNum(cwLbColor.green())+", "
+str.setNum(cwLbColor.blue())+" ," +strAlpha+ " )";
QStackedLayout *stackedLayout = new QStackedLayout(this);
frameCover = new QFrame(this);
frameCover->setGeometry(rect());
frameCover->setStyleSheet("QFrame{border:5px solid "+strLbColor+"; "
"border-radius: 10px; background-color: "
"qlineargradient(x1: 0, y1: 0, x2: 0, y2: 0.5, stop: 0 "
+gradient1+" , stop: 1 "+gradient2+"); }");
stackedLayout->addWidget(frameCover);
stackedLayout->setStackingMode(QStackedLayout::StackAll);
}
void CQFrame::setCover(bool state)
{
frameCover->setVisible(curCover = state);
}
void CQFrame::resizeEvent(QResizeEvent *event)
{
if (curCover)
frameCover->setGeometry(rect());
}
The design isn't mine, I was asked to fix strange visual glitches it experiences. This "frame" is used in Qt designer as one of widgets. After a while suddenly everything resizes, which prompted question "what is wrong with this code". Qt fires warning about attempt to add layout while one already exist: I suppose that may cause a problem, because a frame must have only one layout at time? Code generated by Qt Creator looks something like
void setupUi(CQFrame *CQWnd1T2SeparateONForm)
{
if (CQWnd1T2SeparateONForm->objectName().isEmpty())
CQWnd1T2SeparateONForm->setObjectName(QString::fromUtf8("CQWnd1T2SeparateONForm"));
CQWnd1T2SeparateONForm->resize(735, 241);
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
sizePolicy.setHorizontalStretch(0);
sizePolicy.setVerticalStretch(0);
sizePolicy.setHeightForWidth(CQWnd1T2SeparateONForm->sizePolicy().hasHeightForWidth());
CQWnd1T2SeparateONForm->setSizePolicy(sizePolicy);
gridLayout = new QGridLayout(CQWnd1T2SeparateONForm); // warning here
}
There is similar problem with standard QMainWindow which always got own "special" layout, which Qt Creator solves automatically, by adding a central widget to the layout and everything else is added to that widget. What I don't know that is how to simulate same behavior with a custom widget with Qt Creator plugin.
Or what alternative design for CQFrame can be used. CQFrame reused in dozen project, in about 30+ panels, so reuse of code for them all is a strict requirement.
Current plugin is very basic:
class QDESIGNER_WIDGET_EXPORT CQFramePlugin : public QObject,
public QDesignerCustomWidgetInterface
{
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetInterface)
public:
CQFramePlugin(QObject *parent = 0);
bool isContainer() const;
bool isInitialized() const;
QIcon icon() const;
QString domXml() const;
QString group() const;
QString includeFile() const;
QString name() const;
QString toolTip() const;
QString whatsThis() const;
QWidget *createWidget(QWidget *parent);
void initialize(QDesignerFormEditorInterface *core);
private:
bool initialized;
};
.cpp for it:
#include "cqframe.h"
#include "cqframeplugin.h"
#include <QtPlugin>
CQFramePlugin::CQFramePlugin(QObject *parent)
: QObject(parent)
{
initialized = false;
}
void CQFramePlugin::initialize(QDesignerFormEditorInterface * /* core */)
{
if (initialized)
return;
initialized = true;
}
bool CQFramePlugin::isInitialized() const
{
return initialized;
}
QWidget *CQFramePlugin::createWidget(QWidget *parent)
{
return new CQFrame(parent);
}
QString CQFramePlugin::name() const
{
return "CQFrame";
}
QString CQFramePlugin::group() const
{
return "CustomWidgets";
}
QIcon CQFramePlugin::icon() const
{
return QIcon(":/Resources/frame_icon.png");
}
QString CQFramePlugin::toolTip() const
{
return "";
}
QString CQFramePlugin::whatsThis() const
{
return "";
}
bool CQFramePlugin::isContainer() const
{
return true;
}
QString CQFramePlugin::domXml() const
{
return "<ui language=\"c++\">\n"
" <widget class=\"CQFrame\" name=\"Frame\">\n"
" <property name=\"geometry\">\n"
" <rect>\n"
" <x>0</x>\n"
" <y>0</y>\n"
" <width>120</width>\n"
" <height>80</height>\n"
" </rect>\n"
" </property>\n"
" </widget>\n"
"</ui>";
}
QString CQFramePlugin::includeFile() const
{
return "cqframe.h";
}
So I haven't worked with QT yet, but I'm going to gie it a try. First thing, I think, is that you should use the QStackedLayout to cover the widgets (source and QT manual). But you need to have a single stack.
So the stack should be a private member of CQFrame. E.g.
class CQFrame : public QFrame
{
[...]
private:
QWidget* _frame; // Frame to cover.
QFrame* _frameCover;
QStackedLayout* _stackedLayout;
[...]
And probably:
CQFrame::~CQFrame()
{
delete _stackedLayout;
delete _frameCover;
}
Then you could already initialize everything in the constructor
CQFrame::CQFrame(QWidget* parent = 0, QWidget* frame)
: QFrame(parent)
, _frame(frame)
, _frameCover(new QFrame(this))
, _stackedLayout(new QStackedLayout(this))
{
_frameCover->setGeometry(rect());
[...]
_stackedLayout->addWidget(frame);
_stackedLayout->addWidget(frameCover);
//default:_stackedLayout->setStackingMode(QStackedLayout::StackOne);
}
You could then switch between widgets in the stack using
void CQFrame::SetCover(bool state)
{
if (state) _stackedLayout->setCurrentWidget(_frameCover);
else _stackedLayout->setCurrentWidget(_frame);
}
Maybe this helps you.
edit2:
I removed this code, as it was incorrect, both in coding format, as in idea
So I checked the QT sources QStackedLayout.cpp and QLayout and it seems a QWidget can have only one layout. If you add another layout, you get an error.
Furthermore, a QStackedLayout is a QLayout, is QObject. That could indicate it is automatically removed?
I'm not sure of the cover is implemented in QT as it should. It seems like you have a QWidget you want to cover, on top of which you put a QStackedLayout that is not used as designed i.e. switching stack objects, on top of which you put a new QWidget that is made visible or not.
And maybe that bottom QWidget is already in a (stacked)layout, etc.