QWebEngineView scroll with touch - qt

I have an application ported from an old Qt 4.7.4 to Qt5, and as I understood, QWebView became QWebEngineView, and with a QWebView I used FlickCharm, that was in Qt examples, it still works fine with QScrollArea (such as QListWidget, QTableWidget, ... and of course base QScrollArea), but no more with QWebEngineView, here is the code to activate FlickCharm, that was working on Qt4:
void FlickCharm::activateOn(QWidget *widget, QWidget* p_viewport)
{
QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
if (scrollArea) {
// Widget is a scroll area
QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(widget);
if(itemView)
{
itemView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
itemView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
}
QWidget *viewport = scrollArea->viewport();
if ( p_viewport )
{
viewport = p_viewport;
}
else
{
scrollArea->installEventFilter(this);
}
viewport->installEventFilter(this);
QHash<QWidget*, FlickData*>::iterator oldViewport;
if ( ( oldViewport = d->flickData.find(viewport) ) != d->flickData.end() )
{
delete oldViewport.value();
}
d->flickData.remove(viewport);
d->flickData[viewport] = new FlickData;
d->flickData[viewport]->widget = widget;
d->flickData[viewport]->state = FlickData::Steady;
d->flickData[viewport]->customViewPort = (viewport != scrollArea->viewport());
return;
}
QWebView *webView = qobject_cast<QWebView*>(widget);
if (webView) {
// Widget is a web view
webView->installEventFilter(this);
QHash<QWidget*, FlickData*>::iterator oldViewport;
if ( ( oldViewport = d->flickData.find(webView) ) != d->flickData.end() )
{
delete oldViewport.value();
}
d->flickData.remove(webView);
d->flickData[webView] = new FlickData;
d->flickData[webView]->widget = webView;
d->flickData[webView]->state = FlickData::Steady;
return;
}
qWarning() << "FlickCharm only works on QAbstractScrollArea (and derived classes)";
qWarning() << "or QWebView (and derived classes)";
}
And in FlickData class there is following function that does the scroll:
bool scrollWidget(const int dx, const int dy)
{
{
QAbstractScrollArea *scrollArea = qobject_cast<QAbstractScrollArea*>(widget);
if (scrollArea) {
const int x = scrollArea->horizontalScrollBar()->value();
const int y = scrollArea->verticalScrollBar()->value();
scrollArea->horizontalScrollBar()->setValue(x - dx);
scrollArea->verticalScrollBar()->setValue(y - dy);
return (scrollArea->horizontalScrollBar()->value() != x
|| scrollArea->verticalScrollBar()->value() != y);
}
}
{
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
const QPointF position = webEngineView->page()->scrollPosition();
const QPointF newPosition = position - QPointF(dx, dy);
webEngineView->page()->runJavaScript(QString("window.scrollTo(%1, %2);").arg(newPosition.x()).arg(newPosition.y()));
return webEngineView->page()->scrollPosition() != position;
}
}
return false;
}
In Qt5 I tried applying directly to QWebEngineView as above:
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
webEngineView->installEventFilter(this);
QHash<QWidget*, FlickData*>::iterator oldViewport;
if ( ( oldViewport = d->flickData.find(webEngineView) ) != d->flickData.end() )
{
delete oldViewport.value();
}
d->flickData.remove(webEngineView);
d->flickData[webEngineView] = new FlickData;
d->flickData[webEngineView]->widget = webEngineView;
d->flickData[webEngineView]->state = FlickData::Steady;
return;
}
And also tried to page view that I assume was viewport:
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
QWidget *viewport = webEngineView->page()->view();
webEngineView->installEventFilter(this);
viewport->installEventFilter(this);
QHash<QWidget*, FlickData*>::iterator oldViewport;
if ( ( oldViewport = d->flickData.find(viewport) ) != d->flickData.end() )
{
delete oldViewport.value();
}
d->flickData.remove(viewport);
d->flickData[viewport] = new FlickData;
d->flickData[viewport]->widget = webEngineView;
d->flickData[viewport]->state = FlickData::Steady;
return;
}
In QWebEngineView::page() (that is a QWebEnginePage), there is a scrollPosition() function but this is from a Q_PROPERTY but with no write accessor function, but I found a code peace to scroll with javascript I tried using:
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
const QPointF position = webEngineView->page()->scrollPosition();
const QPointF newPosition = position - QPointF(dx, dy);
webEngineView->page()->runJavaScript(QString("window.scrollTo(%1, %2);").arg(newPosition.x()).arg(newPosition.y()));
return webEngineView->page()->scrollPosition() != position;
}
But after adding some logs I see I never pass in scrollWidget for a QWebEngineView, and, as I have my own class that inherits QApplication that is instanciated, I can see this is not directly the QWebEngineView that is clicked but a RenderWidgetHostViewQtDelegateWidget (that has QWebEngineView as parent) that is not accessible from anywhere, I see this because I reimplemented MyApplication::notify to log on which widget clicks are made:
bool MyApplication::notify(QObject* p_object, QEvent* p_event)
{
[...]
if ( p_event->type() == QEvent::MouseButtonPress )
{
QWidget* widget = dynamic_cast<QWidget*>(p_object);
if (widget != 0)
{
qDebug().nospace() << "Mouse pressed on [" << (widget->isEnabled() ? "enabled" :"disabled") << "] widget: "
<< p_object
<< ", parent: " << p_object->parent();
}
else
{
qDebug().nospace() << "Mouse pressed on object: " << p_object
<< ", parent: " << p_object->parent();
}
}
else if ( p_event->type() == QEvent::MouseButtonRelease )
{
QWidget* widget = dynamic_cast<QWidget*>(p_object);
if (widget != 0)
{
qDebug().nospace() << "Mouse release on [" << (widget->isEnabled() ? "enabled" :"disabled") << "] widget: "
<< p_object
<< ", parent: " << p_object->parent();
}
else
{
qDebug().nospace() << "Mouse release on object: " << p_object
<< ", parent: " << p_object->parent();
}
}
[...]
}
Also, when I try to scroll on the QWebEngineView, text is selected instead.
Here is the full code of flickcharm: https://doc.qt.io/archives/qt-4.8/qt-demos-embedded-anomaly-src-flickcharm-cpp.html
Also, I've seen there is a solution to make a WebEngineView flickable but only for QtQuick QML: https://stackoverflow.com/a/42817245
Anyone knows how to have touch scrolling on a QWebEngineView ?
Thanks
Edit: since RenderWidgetHostViewQtDelegateWidget is a child of the QWebEngineView, I tried to access it (at least as a QWidget if it was possible) from webEngineView->children() but unsuccessfully since its only children are its QVBoxLayout (that is empty) and the FlickCharm.

I finally managed to do what I want, in FlickCharm::activateOn for QWebEngineView I put:
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
QLayout* webEngineViewLayout = webEngineView->layout();
QWidget* webEngineViewChildWidget = 0;
for (int i = 0; i < webEngineViewLayout->count(); ++i)
{
webEngineViewChildWidget = qobject_cast<QWidget*>(webEngineViewLayout->itemAt(i)->widget());
if (webEngineViewChildWidget != nullptr)
{
// There should be only one QWidget under QWebEngineView, but break just in case
break;
}
}
if (webEngineViewChildWidget != nullptr)
{
// Install event filter on widget found in QWebEngineView layout
webEngineViewChildWidget->installEventFilter(this);
QHash<QWidget*, FlickData*>::iterator oldViewport;
if ( ( oldViewport = d->flickData.find(webEngineViewChildWidget) ) != d->flickData.end() )
{
delete oldViewport.value();
}
d->flickData.remove(webEngineViewChildWidget);
d->flickData[webEngineViewChildWidget] = new FlickData;
d->flickData[webEngineViewChildWidget]->widget = webEngineView;
d->flickData[webEngineViewChildWidget]->state = FlickData::Steady;
}
else
{
// Web engine view has not yet a page loaded, activate "again" when a page has been loaded
connect(webEngineView, &QWebEngineView::loadFinished, this, &FlickCharm::ReactToWebEngineViewLoaded);
}
return;
}
And FlickCharm::ReactToWebEngineViewLoaded() is:
void FlickCharm::ReactToWebEngineViewLoaded()
{
QWebEngineView* webEngineView = qobject_cast<QWebEngineView*>(sender());
if (webEngineView != nullptr)
{
// We need to pass only once there then we can disconnect now
disconnect(webEngineView, &QWebEngineView::loadFinished, this, &FlickCharm::ReactToWebEngineViewLoaded);
// Activate "again" so that view actually uses flick charm
activateOn(webEngineView);
}
else
{
LOG_ERROR("Web engine view is NULL");
assert(webEngineView != nullptr);
}
}
With of course void ReactToWebEngineViewLoaded() declared as slot in flickcharm.h.
And in FlickData::scrollWidget for QWebEngineView (this is tricky, and I don't like the qApp->processEvents calls but they are mandatory if we want to actually compare values for return of the function):
QWebEngineView *webEngineView = qobject_cast<QWebEngineView*>(widget);
if (webEngineView) {
webEngineView->page()->runJavaScript(QString("window.scrollBy(%1, %2);").arg(-dx).arg(-dy));
return true;
}

Related

Testing Qt application with Qt Test

I have looked at the 5 Qt testing examples including the one about GUI events, but these examples are way too simple.
I want to test my program by launching it, simulating some clicks, and checking the value of instance variables that have been changed by those clicks.
I assume that this test below is illogical: a.exec() blocks the thread until the program is closed, and when the program is closed w has been deleted I think (or will be deleted later?).
So how to write system/GUI tests?
My test:
void LaunchProgramTest::LaunchProgramTestFunction() {
QApplication a(argc, argv);
MainWindow *w = new MainWindow();
w->show();
a.exec();
int testResult = w->myTestFunction();
qDebug() << testResult; //Prints big numbers like "-17891602" or "1753770528" as if testResult was not initialized
QVERIFY2(testResult == 3, "Incorrectly changed");
}
In mainWindow.h I declared a variable:
int testValue;
Mainwindow.cpp is the class for the main GUI of the program. In the constructor I added
testValue = 2;
Then in a function that is executed upon events I wrote
void MainWindow::on_actionTest_clicked() {
testValue = 3;
}
enter code hereSo)) you need to add QTest, add .pro
QT += testlib
and
#include <QTest>
I will show an example of my implementation for MousePress, the rest you can do yourself))
struct EventMK
{
int type;
QString m_widPath;
int _timer;
int width;
int height;
QPoint p;
QPoint g;
int button;
int buttons;
int modifiers;
int _key;
QString text;
void print(){
qDebug()<<"{ \n"
<<"type "<< type<<","<<"\n"
<<"Widget_Path "<< m_widPath<<","<<"\n"
<<"Timer "<< _timer<<","<<"\n"
<<"Width "<< width<<","<<"\n"
<<"Height "<< height<<","<<"\n"
<<"Pos_x "<< p.x()<<","<<"\n"
<<"Pos_y "<< p.y()<<","<<"\n"
<<"Global_x "<< g.x()<<","<<"\n"
<<"Global_y "<< g.y()<<","<<"\n"
<<"Button "<< button<<","<<"\n"
<<"Buttons "<< buttons<<","<<"\n"
<<"Modifiers "<< modifiers<<","<<"\n"
<<"Key "<< _key<<","<<"\n"
<<"Text "<< text<<"\n"
<<"}\n";
}
};
QWidget * _getWidget(EventMK ev)
{
QString wname = ev.m_widPath;
QStringList wpath = wname.split ( "/" );
return QWidgetUtils::getAWidget(&wpath);
}
void _postExecution(EventMK ev, QWidget *widget)
{
if (widget){
//set focus
QWidgetUtils::setFocusOnWidget(widget);
//end simulation
widget->setUpdatesEnabled ( true );
widget->update();
}
}
QPoint adaptedPosition(EventMK ev, QWidget *w)
{
if (w == nullptr)
return QPoint(ev.p.x(), ev.p.y());
int orig_w = ev.width;
int orig_h = ev.height;
int curr_w = w->width();
int curr_h = w->height();
int new_x = ev.p.x() * curr_w / orig_w;
int new_y = ev.p.y() * curr_h / orig_h;
return QPoint(new_x, new_y);
}
and function implementation
void executeMousePressEvent(EventMK ev)
{
QWidget* widget = _getWidget(ev);
if ( widget == nullptr )
{
qDebug()<<"error: "<<__LINE__<<__FUNCTION__;
return;
}
// _preExecutionWithMouseMove(ev, widget);
if (widget){
QTest::mousePress ( widget, (Qt::MouseButton)ev.button ,
(Qt::KeyboardModifier) ev.modifiers,
adaptedPosition(ev,widget));
}
_postExecution(ev, widget);
}
now left to fill struct EventMK , you need to populate it from MouseButtonPress events.
Here is my example
bool eventFilter(QObject *obj, QEvent *event)
{
///
/// process control
///
//window events
if (event->type() == QEvent::KeyPress)
{
handleKeyPressEvent(obj, event);
}
//mouse events
else if (event->type() == QEvent::MouseButtonPress)
{
handleMousePressEvent(obj, event);
}
else if (event->type() == QEvent::MouseButtonRelease)
{
handleMouseReleaseEvent(obj, event);
}
else if (event->type() == QEvent::MouseButtonDblClick)
{
handleMouseDoubleEvent(obj, event);
}
else if (event->type() == QEvent::Wheel)
{
handleWheelEvent(obj, event);
}
//keyboard events
else if (event->type() == QEvent::Close)
{
handleCloseEvent(obj, event);
}
///the event should continue to reach its goal...
return false;
}
and
void handleMousePressEvent(QObject *obj, QEvent *event)
{
QWidget *widget = isValidWidget(obj);
if (!widget){
return;
}
QMouseEvent *me = dynamic_cast< QMouseEvent*> ( event );
//create the event
if (widget != nullptr){
EventMK evkm;
evkm.type = QOE_MOUSE_PRESS; // set your type
evkm._timer = _timer.restart(); // add your QElapsedTimer
evkm.m_widPath = QWidgetUtils::getWidgetPath(widget);
evkm. width = widget->width();
evkm. height = widget->height();
QPoint p ( me->pos() );
QPoint g = widget->mapToGlobal ( p );
evkm. p = p;
evkm. g = g;
evkm. button = me->button();
evkm. buttons = me->buttons();
evkm. modifiers = me->modifiers();
evkm.print();
}
//send event if EventMK is valid
}
so, it turns out we can write a scenario and run what you wanted, thanks

QTreeView: column resize from columns and not from headers?

Is there any way to allow the user to interactively resize the columns when headers are hidden?
You can install an event filter on the table's viewport and implement needed behavior manually. Below is a sample implementation.
Header:
#include <QTableView>
class Table_cell_resizer : public QObject {
Q_OBJECT
public:
explicit Table_cell_resizer(QTableView *view = 0);
protected:
bool eventFilter(QObject* object, QEvent* event);
private:
QTableView* m_view;
//max distance between mouse and a cell, small enough to trigger resize
int m_sensibility;
//variables for saving state while dragging
bool m_drag_in_progress;
Qt::Orientation m_drag_orientation;
int m_drag_section;
int m_drag_previous_pos;
// check if mouse_pos is around right or bottom side of a cell
// (depending on orientation)
// and return the index of that cell if found
QModelIndex index_resizable(QPoint mouse_pos, Qt::Orientation orientation);
};
Source:
#include "Table_cell_resizer.h"
#include <QMouseEvent>
#include <QHeaderView>
Table_cell_resizer::Table_cell_resizer(QTableView* view) :
QObject(view), m_view(view)
{
m_view->viewport()->installEventFilter(this);
m_view->viewport()->setMouseTracking(true);
m_sensibility = 5;
m_drag_in_progress = false;
}
bool Table_cell_resizer::eventFilter(QObject* object, QEvent* event) {
if (object == m_view->viewport()) {
QMouseEvent* mouse_event = dynamic_cast<QMouseEvent*>(event);
if (mouse_event) {
if (mouse_event->type() == QEvent::MouseMove) {
if (m_drag_in_progress) { // apply dragging
int delta;
QHeaderView* header_view;
if (m_drag_orientation == Qt::Vertical) {
delta = mouse_event->pos().y() - m_drag_previous_pos;
header_view = m_view->verticalHeader();
m_drag_previous_pos = mouse_event->pos().y();
} else if (m_drag_orientation == Qt::Horizontal) {
delta = mouse_event->pos().x() - m_drag_previous_pos;
header_view = m_view->horizontalHeader();
m_drag_previous_pos = mouse_event->pos().x();
}
//using minimal size = m_sensibility * 2 to prevent collapsing
header_view->resizeSection(m_drag_section,
qMax(m_sensibility * 2, header_view->sectionSize(m_drag_section) + delta));
return true;
} else { // set mouse cursor shape
if (index_resizable(mouse_event->pos(), Qt::Vertical).isValid()) {
m_view->viewport()->setCursor(Qt::SplitVCursor);
} else if (index_resizable(mouse_event->pos(), Qt::Horizontal).isValid()) {
m_view->viewport()->setCursor(Qt::SplitHCursor);
} else {
m_view->viewport()->setCursor(QCursor());
}
}
} else if (mouse_event->type() == QEvent::MouseButtonPress &&
mouse_event->button() == Qt::LeftButton &&
!m_drag_in_progress) { // start dragging
if (index_resizable(mouse_event->pos(), Qt::Vertical).isValid()) {
m_drag_in_progress = true;
m_drag_orientation = Qt::Vertical;
m_drag_previous_pos = mouse_event->y();
m_drag_section = index_resizable(mouse_event->pos(), Qt::Vertical).row();
return true;
} else if (index_resizable(mouse_event->pos(), Qt::Horizontal).isValid()) {
m_drag_in_progress = true;
m_drag_orientation = Qt::Horizontal;
m_drag_previous_pos = mouse_event->x();
m_drag_section = index_resizable(mouse_event->pos(), Qt::Horizontal).column();
return true;
}
} else if (mouse_event->type() == QEvent::MouseButtonRelease &&
mouse_event->button() == Qt::LeftButton &&
m_drag_in_progress) { // stop dragging
m_drag_in_progress = false;
return true;
}
}
}
return false;
}
QModelIndex Table_cell_resizer::index_resizable(QPoint mouse_pos, Qt::Orientation orientation) {
QModelIndex index = m_view->indexAt(mouse_pos - QPoint(m_sensibility + 1, m_sensibility + 1));
if (index.isValid()) {
if (orientation == Qt::Horizontal) {
if (qAbs(m_view->visualRect(index).right() - mouse_pos.x()) < m_sensibility &&
m_view->horizontalHeader()->sectionResizeMode(index.column()) == QHeaderView::Interactive) {
return index;
}
} else {
if (qAbs(m_view->visualRect(index).bottom() - mouse_pos.y()) < m_sensibility &&
m_view->verticalHeader()->sectionResizeMode(index.row()) == QHeaderView::Interactive) {
return index;
}
}
}
return QModelIndex();
}
Usage:
new Table_cell_resizer(ui->table);
User can now resize rows and columns using cell area in addition to header areas. You can hide headers if you wish. This implementation respects header resize modes, so make sure that resize mode is set to QHeaderView::Interactive for headers which you want to be resizable. For example, you can set horizontal header mode to Interactive and vertical header mode to Fixed, resulting in resizable columns and fixed rows.

CutyCapt not able to generate HTTPS web site screenshot when launched from w3wp.exe

I have updated the latest code from SVN and complie CutyCapt with Qt SDK 4.8.
And I have static-linked libeay32.dll / ssleay32.dll in the same location of the binary.
It works fine if I launch the CutyCapt process in command window.
It works fine if I launch the CutyCapt process from ASP.NET (w3wp) to capture HTTP web page.
it does NOT work if I launch the CutyCapt process from ASP.NET (w3wp) to capture HTTPS web page, always generates a blank image.
I doubted it was because the libeay32.dll / ssleay32.dll dependencies are not loaded, so I copied these 2 dll to system32 / SysWOW64, this does not resolve my problem.
Then I monitor the process by ProcessMonitor, it shows me that libeay32.dll / ssleay32.dll are loaded successfully.
So, the dependencies are not the reason.
Here is my C# code to launch the CutyCapt,
using (Process process = new Process())
{
process.StartInfo.FileName = ti.ExeFile;
process.StartInfo.ErrorDialog = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
process.StartInfo.WorkingDirectory = Path.GetDirectoryName(ti.ExeFile);
process.StartInfo.UseShellExecute = false;
process.StartInfo.Arguments = string.Format(#"--min-width=1024 --delay=1500 --javascript=off --auto-load-images=on --plugins=off --java=off --url={0} --out={1}"
, ti.Url
, destFile
);
process.Start();
process.WaitForExit();
process.Close();
}
Does anyone knows how to allow CutyCapt works from w3wp?
SOLUTION:
I have resolved this problem by modifying the CutyCapt source.
The idea is that, get the HTML of the url then set the HTML to the WebKit.
CutyCapt.hpp
#include <QtWebKit>
class CutyCapt;
class CutyPage : public QWebPage {
Q_OBJECT
public:
void setAttribute(QWebSettings::WebAttribute option, const QString& value);
void setUserAgent(const QString& userAgent);
void setAlertString(const QString& alertString);
void setPrintAlerts(bool printAlerts);
void setCutyCapt(CutyCapt* cutyCapt);
QString getAlertString();
protected:
QString chooseFile(QWebFrame *frame, const QString& suggestedFile);
void javaScriptConsoleMessage(const QString& message, int lineNumber, const QString& sourceID);
bool javaScriptPrompt(QWebFrame* frame, const QString& msg, const QString& defaultValue, QString* result);
void javaScriptAlert(QWebFrame* frame, const QString& msg);
bool javaScriptConfirm(QWebFrame* frame, const QString& msg);
QString userAgentForUrl(const QUrl& url) const;
QString mUserAgent;
QString mAlertString;
bool mPrintAlerts;
CutyCapt* mCutyCapt;
};
class CutyCapt : public QObject {
Q_OBJECT
public:
QString mUrl;
// TODO: This should really be elsewhere and be named differently
enum OutputFormat { SvgFormat, PdfFormat, PsFormat, InnerTextFormat, HtmlFormat,
RenderTreeFormat, PngFormat, JpegFormat, MngFormat, TiffFormat, GifFormat,
BmpFormat, PpmFormat, XbmFormat, XpmFormat, OtherFormat };
CutyCapt(CutyPage* page,
const QString& output,
int delay,
OutputFormat format,
const QString& scriptProp,
const QString& scriptCode);
private slots:
void HtmlDownloadFinished(QNetworkReply * reply);
void HtmlDownloadError(QNetworkReply::NetworkError code);
void DocumentComplete(bool ok);
void InitialLayoutCompleted();
void JavaScriptWindowObjectCleared();
void Timeout();
void Delayed();
private:
void TryDelayedRender();
void saveSnapshot();
bool mSawInitialLayout;
bool mSawDocumentComplete;
protected:
QString mOutput;
int mDelay;
CutyPage* mPage;
OutputFormat mFormat;
QObject* mScriptObj;
QString mScriptProp;
QString mScriptCode;
};
CutyCapt.cpp
////////////////////////////////////////////////////////////////////
//
// CutyCapt - A Qt WebKit Web Page Rendering Capture Utility
//
// Copyright (C) 2003-2010 Bjoern Hoehrmann <bjoern#hoehrmann.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// $Id: CutyCapt.cpp 7 2011-11-30 10:56:34Z hoehrmann $
//
////////////////////////////////////////////////////////////////////
#include <QApplication>
#include <QtWebKit>
#include <QtGui>
#include <QSvgGenerator>
#include <QPrinter>
#include <QTimer>
#include <QByteArray>
#include <QNetworkRequest>
#include <QtCore>
#include "CutyCapt.hpp"
#if QT_VERSION >= 0x040600 && 0
#define CUTYCAPT_SCRIPT 1
#endif
#ifdef STATIC_PLUGINS
Q_IMPORT_PLUGIN(qjpeg)
Q_IMPORT_PLUGIN(qgif)
Q_IMPORT_PLUGIN(qtiff)
Q_IMPORT_PLUGIN(qsvg)
Q_IMPORT_PLUGIN(qmng)
Q_IMPORT_PLUGIN(qico)
#endif
static struct _CutyExtMap {
CutyCapt::OutputFormat id;
const char* extension;
const char* identifier;
} const CutyExtMap[] = {
{ CutyCapt::SvgFormat, ".svg", "svg" },
{ CutyCapt::PdfFormat, ".pdf", "pdf" },
{ CutyCapt::PsFormat, ".ps", "ps" },
{ CutyCapt::InnerTextFormat, ".txt", "itext" },
{ CutyCapt::HtmlFormat, ".html", "html" },
{ CutyCapt::RenderTreeFormat, ".rtree", "rtree" },
{ CutyCapt::JpegFormat, ".jpeg", "jpeg" },
{ CutyCapt::PngFormat, ".png", "png" },
{ CutyCapt::MngFormat, ".mng", "mng" },
{ CutyCapt::TiffFormat, ".tiff", "tiff" },
{ CutyCapt::GifFormat, ".gif", "gif" },
{ CutyCapt::BmpFormat, ".bmp", "bmp" },
{ CutyCapt::PpmFormat, ".ppm", "ppm" },
{ CutyCapt::XbmFormat, ".xbm", "xbm" },
{ CutyCapt::XpmFormat, ".xpm", "xpm" },
{ CutyCapt::OtherFormat, "", "" }
};
QString
CutyPage::chooseFile(QWebFrame* /*frame*/, const QString& /*suggestedFile*/) {
return QString::null;
}
bool
CutyPage::javaScriptConfirm(QWebFrame* /*frame*/, const QString& /*msg*/) {
return true;
}
bool
CutyPage::javaScriptPrompt(QWebFrame* /*frame*/,
const QString& /*msg*/,
const QString& /*defaultValue*/,
QString* /*result*/) {
return true;
}
void
CutyPage::javaScriptConsoleMessage(const QString& /*message*/,
int /*lineNumber*/,
const QString& /*sourceID*/) {
// noop
}
void
CutyPage::javaScriptAlert(QWebFrame* /*frame*/, const QString& msg) {
if (mPrintAlerts)
qDebug() << "[alert]" << msg;
if (mAlertString == msg) {
QTimer::singleShot(10, mCutyCapt, SLOT(Delayed()));
}
}
QString
CutyPage::userAgentForUrl(const QUrl& url) const {
if (!mUserAgent.isNull())
return mUserAgent;
return QWebPage::userAgentForUrl(url);
}
void
CutyPage::setUserAgent(const QString& userAgent) {
mUserAgent = userAgent;
}
void
CutyPage::setAlertString(const QString& alertString) {
mAlertString = alertString;
}
QString
CutyPage::getAlertString() {
return mAlertString;
}
void
CutyPage::setCutyCapt(CutyCapt* cutyCapt) {
mCutyCapt = cutyCapt;
}
void
CutyPage::setPrintAlerts(bool printAlerts) {
mPrintAlerts = printAlerts;
}
void
CutyPage::setAttribute(QWebSettings::WebAttribute option,
const QString& value) {
if (value == "on")
settings()->setAttribute(option, true);
else if (value == "off")
settings()->setAttribute(option, false);
else
(void)0; // TODO: ...
}
// TODO: Consider merging some of main() and CutyCap
CutyCapt::CutyCapt(CutyPage* page, const QString& output, int delay, OutputFormat format,
const QString& scriptProp, const QString& scriptCode) {
mPage = page;
mOutput = output;
mDelay = delay;
mSawInitialLayout = false;
mSawDocumentComplete = false;
mFormat = format;
mScriptProp = scriptProp;
mScriptCode = scriptCode;
mScriptObj = new QObject();
// This is not really nice, but some restructuring work is
// needed anyway, so this should not be that bad for now.
mPage->setCutyCapt(this);
}
void
CutyCapt::InitialLayoutCompleted() {
mSawInitialLayout = true;
if (mSawInitialLayout && mSawDocumentComplete)
TryDelayedRender();
}
void
CutyCapt::DocumentComplete(bool /*ok*/) {
mSawDocumentComplete = true;
if (mSawInitialLayout && mSawDocumentComplete)
TryDelayedRender();
}
void
CutyCapt::JavaScriptWindowObjectCleared() {
if (!mScriptProp.isEmpty()) {
QVariant var = mPage->mainFrame()->evaluateJavaScript(mScriptProp);
QObject* obj = var.value<QObject*>();
if (obj == mScriptObj)
return;
mPage->mainFrame()->addToJavaScriptWindowObject(mScriptProp, mScriptObj);
}
mPage->mainFrame()->evaluateJavaScript(mScriptCode);
}
void CutyCapt::HtmlDownloadFinished(QNetworkReply * reply)
{
QVariant statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute );
qDebug() << statusCode;
if ( !statusCode.isValid() )
{
QApplication::exit();
return;
}
int status = statusCode.toInt();
if( status == 301 || status == 302 )
{
QVariant loc = reply->header(QNetworkRequest::LocationHeader);
qDebug() << "Location :" << loc.toString();
QApplication::exit();
return;
}
if ( status != 200 )
{
QString reason = reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString();
qDebug() << reason;
QApplication::exit();
return;
}
QString html=QString::fromUtf8(reply->readAll());
mPage->mainFrame()->setHtml( html, QUrl(mUrl) );
}
void CutyCapt::HtmlDownloadError(QNetworkReply::NetworkError code)
{
QApplication::exit();
}
void
CutyCapt::TryDelayedRender() {
if (!mPage->getAlertString().isEmpty())
return;
if (mDelay > 0) {
QTimer::singleShot(mDelay, this, SLOT(Delayed()));
return;
}
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::Timeout() {
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::Delayed() {
saveSnapshot();
QApplication::exit();
}
void
CutyCapt::saveSnapshot() {
QWebFrame *mainFrame = mPage->mainFrame();
QPainter painter;
const char* format = NULL;
for (int ix = 0; CutyExtMap[ix].id != OtherFormat; ++ix)
if (CutyExtMap[ix].id == mFormat)
format = CutyExtMap[ix].identifier; //, break;
// TODO: sometimes contents/viewport can have size 0x0
// in which case saving them will fail. This is likely
// the result of the method being called too early. So
// far I've been unable to find a workaround, except
// using --delay with some substantial wait time. I've
// tried to resize multiple time, make a fake render,
// check for other events... This is primarily a problem
// under my Ubuntu virtual machine.
mPage->setViewportSize( mainFrame->contentsSize() );
switch (mFormat) {
case SvgFormat: {
QSvgGenerator svg;
svg.setFileName(mOutput);
svg.setSize(mPage->viewportSize());
painter.begin(&svg);
mainFrame->render(&painter);
painter.end();
break;
}
case PdfFormat:
case PsFormat: {
QPrinter printer;
printer.setPageSize(QPrinter::A4);
printer.setOutputFileName(mOutput);
// TODO: change quality here?
mainFrame->print(&printer);
break;
}
case RenderTreeFormat:
case InnerTextFormat:
case HtmlFormat: {
QFile file(mOutput);
file.open(QIODevice::WriteOnly | QIODevice::Text);
QTextStream s(&file);
s.setCodec("utf-8");
s << (mFormat == RenderTreeFormat ? mainFrame->renderTreeDump() :
mFormat == InnerTextFormat ? mainFrame->toPlainText() :
mFormat == HtmlFormat ? mainFrame->toHtml() :
"bug");
break;
}
default: {
QImage image(mPage->viewportSize(), QImage::Format_ARGB32);
painter.begin(&image);
mainFrame->render(&painter);
painter.end();
// TODO: add quality
image.save(mOutput, format);
}
};
}
void
CaptHelp(void) {
printf("%s",
" -----------------------------------------------------------------------------\n"
" Usage: CutyCapt --url=http://www.example.org/ --out=localfile.png \n"
" -----------------------------------------------------------------------------\n"
" --help Print this help page and exit \n"
" --url=<url> The URL to capture (http:...|file:...|...) \n"
" --out=<path> The target file (.png|pdf|ps|svg|jpeg|...) \n"
" --out-format=<f> Like extension in --out, overrides heuristic \n"
// " --out-quality=<int> Output format quality from 1 to 100 \n"
" --min-width=<int> Minimal width for the image (default: 800) \n"
" --min-height=<int> Minimal height for the image (default: 600) \n"
" --max-wait=<ms> Don't wait more than (default: 90000, inf: 0)\n"
" --delay=<ms> After successful load, wait (default: 0) \n"
// " --user-styles=<url> Location of user style sheet (deprecated) \n"
" --user-style-path=<path> Location of user style sheet file, if any \n"
" --user-style-string=<css> User style rules specified as text \n"
" --header=<name>:<value> request header; repeatable; some can't be set\n"
" --method=<get|post|put> Specifies the request method (default: get) \n"
" --body-string=<string> Unencoded request body (default: none) \n"
" --body-base64=<base64> Base64-encoded request body (default: none) \n"
" --app-name=<name> appName used in User-Agent; default is none \n"
" --app-version=<version> appVers used in User-Agent; default is none \n"
" --user-agent=<string> Override the User-Agent header Qt would set \n"
" --javascript=<on|off> JavaScript execution (default: on) \n"
" --java=<on|off> Java execution (default: unknown) \n"
" --plugins=<on|off> Plugin execution (default: unknown) \n"
" --private-browsing=<on|off> Private browsing (default: unknown) \n"
" --auto-load-images=<on|off> Automatic image loading (default: on) \n"
" --js-can-open-windows=<on|off> Script can open windows? (default: unknown) \n"
" --js-can-access-clipboard=<on|off> Script clipboard privs (default: unknown)\n"
#if QT_VERSION >= 0x040500
" --print-backgrounds=<on|off> Backgrounds in PDF/PS output (default: off) \n"
" --zoom-factor=<float> Page zoom factor (default: no zooming) \n"
" --zoom-text-only=<on|off> Whether to zoom only the text (default: off) \n"
" --http-proxy=<url> Address for HTTP proxy server (default: none)\n"
#endif
#if CUTYCAPT_SCRIPT
" --inject-script=<path> JavaScript that will be injected into pages \n"
" --script-object=<string> Property to hold state for injected script \n"
" --expect-alert=<string> Try waiting for alert(string) before capture \n"
" --debug-print-alerts Prints out alert(...) strings for debugging. \n"
#endif
" -----------------------------------------------------------------------------\n"
" <f> is svg,ps,pdf,itext,html,rtree,png,jpeg,mng,tiff,gif,bmp,ppm,xbm,xpm \n"
" -----------------------------------------------------------------------------\n"
#if CUTYCAPT_SCRIPT
" The `inject-script` option can be used to inject script code into loaded web \n"
" pages. The code is called whenever the `javaScriptWindowObjectCleared` signal\n"
" is received. When `script-object` is set, an object under the specified name \n"
" will be available to the script to maintain state across page loads. When the\n"
" `expect-alert` option is specified, the shot will be taken when a script in- \n"
" vokes alert(string) with the string if that happens before `max-wait`. These \n"
" options effectively allow you to remote control the browser and the web page.\n"
" This an experimental and easily abused and misused feature. Use with caution.\n"
" -----------------------------------------------------------------------------\n"
#endif
" http://cutycapt.sf.net - (c) 2003-2010 Bjoern Hoehrmann - bjoern#hoehrmann.de\n"
"");
}
int
main(int argc, char *argv[]) {
int argHelp = 0;
int argDelay = 0;
int argSilent = 0;
int argMinWidth = 800;
int argMinHeight = 600;
int argMaxWait = 90000;
int argVerbosity = 0;
const char* argUrl = NULL;
const char* argUserStyle = NULL;
const char* argUserStylePath = NULL;
const char* argUserStyleString = NULL;
const char* argIconDbPath = NULL;
const char* argInjectScript = NULL;
const char* argScriptObject = NULL;
QString argOut;
CutyCapt::OutputFormat format = CutyCapt::OtherFormat;
QApplication app(argc, argv, true);
CutyPage page;
QNetworkAccessManager::Operation method =
QNetworkAccessManager::GetOperation;
QByteArray body;
QNetworkRequest req;
QNetworkAccessManager manager;
// Parse command line parameters
for (int ax = 1; ax < argc; ++ax) {
size_t nlen;
const char* s = argv[ax];
const char* value;
// boolean options
if (strcmp("--silent", s) == 0) {
argSilent = 1;
continue;
} else if (strcmp("--help", s) == 0) {
argHelp = 1;
break;
} else if (strcmp("--verbose", s) == 0) {
argVerbosity++;
continue;
#if CUTYCAPT_SCRIPT
} else if (strcmp("--debug-print-alerts", s) == 0) {
page.setPrintAlerts(true);
continue;
#endif
}
value = strchr(s, '=');
if (value == NULL) {
// TODO: error
argHelp = 1;
break;
}
nlen = value++ - s;
// --name=value options
if (strncmp("--url", s, nlen) == 0) {
argUrl = value;
} else if (strncmp("--min-width", s, nlen) == 0) {
// TODO: add error checking here?
argMinWidth = (unsigned int)atoi(value);
} else if (strncmp("--min-height", s, nlen) == 0) {
// TODO: add error checking here?
argMinHeight = (unsigned int)atoi(value);
} else if (strncmp("--delay", s, nlen) == 0) {
// TODO: see above
argDelay = (unsigned int)atoi(value);
} else if (strncmp("--max-wait", s, nlen) == 0) {
// TODO: see above
argMaxWait = (unsigned int)atoi(value);
} else if (strncmp("--out", s, nlen) == 0) {
argOut = value;
if (format == CutyCapt::OtherFormat)
for (int ix = 0; CutyExtMap[ix].id != CutyCapt::OtherFormat; ++ix)
if (argOut.endsWith(CutyExtMap[ix].extension))
format = CutyExtMap[ix].id; //, break;
} else if (strncmp("--user-styles", s, nlen) == 0) {
// This option is provided for backwards-compatibility only
argUserStyle = value;
} else if (strncmp("--user-style-path", s, nlen) == 0) {
argUserStylePath = value;
} else if (strncmp("--user-style-string", s, nlen) == 0) {
argUserStyleString = value;
} else if (strncmp("--icon-database-path", s, nlen) == 0) {
argIconDbPath = value;
} else if (strncmp("--auto-load-images", s, nlen) == 0) {
page.setAttribute(QWebSettings::AutoLoadImages, value);
} else if (strncmp("--javascript", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptEnabled, value);
} else if (strncmp("--java", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavaEnabled, value);
} else if (strncmp("--plugins", s, nlen) == 0) {
page.setAttribute(QWebSettings::PluginsEnabled, value);
} else if (strncmp("--private-browsing", s, nlen) == 0) {
page.setAttribute(QWebSettings::PrivateBrowsingEnabled, value);
} else if (strncmp("--js-can-open-windows", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptCanOpenWindows, value);
} else if (strncmp("--js-can-access-clipboard", s, nlen) == 0) {
page.setAttribute(QWebSettings::JavascriptCanAccessClipboard, value);
} else if (strncmp("--developer-extras", s, nlen) == 0) {
page.setAttribute(QWebSettings::DeveloperExtrasEnabled, value);
} else if (strncmp("--links-included-in-focus-chain", s, nlen) == 0) {
page.setAttribute(QWebSettings::LinksIncludedInFocusChain, value);
#if QT_VERSION >= 0x040500
} else if (strncmp("--print-backgrounds", s, nlen) == 0) {
page.setAttribute(QWebSettings::PrintElementBackgrounds, value);
} else if (strncmp("--zoom-factor", s, nlen) == 0) {
page.mainFrame()->setZoomFactor(QString(value).toFloat());
} else if (strncmp("--zoom-text-only", s, nlen) == 0) {
page.setAttribute(QWebSettings::ZoomTextOnly, value);
} else if (strncmp("--http-proxy", s, nlen) == 0) {
QUrl p = QUrl::fromEncoded(value);
QNetworkProxy proxy = QNetworkProxy(QNetworkProxy::HttpProxy,
p.host(), p.port(80), p.userName(), p.password());
manager.setProxy(proxy);
page.setNetworkAccessManager(&manager);
#endif
#if CUTYCAPT_SCRIPT
} else if (strncmp("--inject-script", s, nlen) == 0) {
argInjectScript = value;
} else if (strncmp("--script-object", s, nlen) == 0) {
argScriptObject = value;
} else if (strncmp("--expect-alert", s, nlen) == 0) {
page.setAlertString(value);
#endif
} else if (strncmp("--app-name", s, nlen) == 0) {
app.setApplicationName(value);
} else if (strncmp("--app-version", s, nlen) == 0) {
app.setApplicationVersion(value);
} else if (strncmp("--body-base64", s, nlen) == 0) {
body = QByteArray::fromBase64(value);
} else if (strncmp("--body-string", s, nlen) == 0) {
body = QByteArray(value);
} else if (strncmp("--user-agent", s, nlen) == 0) {
page.setUserAgent(value);
} else if (strncmp("--out-format", s, nlen) == 0) {
for (int ix = 0; CutyExtMap[ix].id != CutyCapt::OtherFormat; ++ix)
if (strcmp(value, CutyExtMap[ix].identifier) == 0)
format = CutyExtMap[ix].id; //, break;
if (format == CutyCapt::OtherFormat) {
// TODO: error
argHelp = 1;
break;
}
} else if (strncmp("--header", s, nlen) == 0) {
const char* hv = strchr(value, ':');
if (hv == NULL) {
// TODO: error
argHelp = 1;
break;
}
req.setRawHeader(QByteArray(value, hv - value), hv + 1);
} else if (strncmp("--method", s, nlen) == 0) {
if (strcmp("value", "get") == 0)
method = QNetworkAccessManager::GetOperation;
else if (strcmp("value", "put") == 0)
method = QNetworkAccessManager::PutOperation;
else if (strcmp("value", "post") == 0)
method = QNetworkAccessManager::PostOperation;
else if (strcmp("value", "head") == 0)
method = QNetworkAccessManager::HeadOperation;
else
(void)0; // TODO: ...
} else {
// TODO: error
argHelp = 1;
}
}
if (argUrl == NULL || argOut == NULL || argHelp) {
CaptHelp();
return EXIT_FAILURE;
}
// This used to use QUrl(argUrl) but that escapes %hh sequences
// even though it should not, as URLs can assumed to be escaped.
req.setUrl( QUrl::fromEncoded(argUrl) );
QString scriptProp(argScriptObject);
QString scriptCode;
if (argInjectScript) {
QFile file(argInjectScript);
if (file.open(QIODevice::ReadOnly)) {
QTextStream stream(&file);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream.setAutoDetectUnicode(true);
scriptCode = stream.readAll();
file.close();
}
}
CutyCapt main(&page, argOut, argDelay, format, scriptProp, scriptCode);
main.mUrl = argUrl;
app.connect(&page,
SIGNAL(loadFinished(bool)),
&main,
SLOT(DocumentComplete(bool)));
app.connect(page.mainFrame(),
SIGNAL(initialLayoutCompleted()),
&main,
SLOT(InitialLayoutCompleted()));
if (argMaxWait > 0) {
// TODO: Should this also register one for the application?
QTimer::singleShot(argMaxWait, &main, SLOT(Timeout()));
}
if (argUserStyle != NULL)
// TODO: does this need any syntax checking?
page.settings()->setUserStyleSheetUrl( QUrl::fromEncoded(argUserStyle) );
if (argUserStylePath != NULL) {
page.settings()->setUserStyleSheetUrl( QUrl::fromLocalFile(argUserStylePath) );
}
if (argUserStyleString != NULL) {
QUrl data("data:text/css;charset=utf-8;base64," +
QByteArray(argUserStyleString).toBase64());
page.settings()->setUserStyleSheetUrl( data );
}
if (argIconDbPath != NULL)
// TODO: does this need any syntax checking?
page.settings()->setIconDatabasePath(argUserStyle);
// The documentation does not say, but it seems the mainFrame
// will never change, so we can set this here. Otherwise we'd
// have to set this in snapshot and trigger an update, which
// is not currently possible (Qt 4.4.0) as far as I can tell.
page.mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
page.mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);
page.setViewportSize( QSize(argMinWidth, argMinHeight) );
#if CUTYCAPT_SCRIPT
// javaScriptWindowObjectCleared does not get called on the
// initial load unless some JavaScript has been executed.
page.mainFrame()->evaluateJavaScript(QString(""));
app.connect(page.mainFrame(),
SIGNAL(javaScriptWindowObjectCleared()),
&main,
SLOT(JavaScriptWindowObjectCleared()));
#endif
app.connect( &manager,SIGNAL(finished(QNetworkReply *)), &main,SLOT(HtmlDownloadFinished(QNetworkReply *)));
QNetworkReply *reply = manager.get(req);
app.connect( reply,SIGNAL(error(QNetworkReply::NetworkError)), &main,SLOT(HtmlDownloadError(QNetworkReply::NetworkError)));
return app.exec();
}

Redirecting an Qt mouse message in viewportEvent to another window is not working

I want to implement an extended "QScrollArea" widget that supports scrolling the scrollbar (contents) with the mouse by dragging the mouse somewhere within the widget. For this purpose I want to ignore any child widgets so that the focus don't accidently goes to some of the children.
My solution (which is not working) is to set content widget attribute WA_TransparentForMouseEvents so that all the mouse events goes to viewportEvent() method in my QScrollAreaEx. Here I want to handle the mouse messages. For example I could determine if the intention was to drag the scrollbar position or to interact with some of the child widgets (activate, select, move caret, ...).
My first try was to redirect the messages MouseButtonPress and MouseButtonRelease events only to the child widget underneath (widget()->childAt(e->pos())) which had been focused if WA_TransparentForMouseEvents were not set. However that didn't worked, none of the widgetd received the focus. But why?
bool viewportEvent(QEvent* e) {
bool IsMouseEvent = e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease || e->type() == QEvent::MouseMove;
QMouseEvent* me = (QMouseEvent*) e;
bool IsHandled = false;
if (IsMouseEvent) {
if (e->type() == QEvent::MouseButtonPress) {
_PressEvent = *me;
IsHandled = true; // Eating press event
} else if (e->type() == QEvent::MouseButtonRelease) {
QPoint PressPoint = _PressEvent.pos();
widget()->setAttribute(Qt::WA_TransparentForMouseEvents, false);
QWidget* w = widget()->childAt(PressPoint);
if (w) {
QPoint p1 = w->mapFrom(widget(), _PressEvent.pos());
QPoint p2 = w->mapFrom(widget(), me->pos());
QMouseEvent* e1 = new QMouseEvent(QEvent::MouseButtonPress, p1, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QMouseEvent* e2 = new QMouseEvent(QEvent::MouseButtonRelease, p2, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QCoreApplication::postEvent(w, e1);
QCoreApplication::postEvent(w, e2);
}
widget()->setAttribute(Qt::WA_TransparentForMouseEvents, true);
}
}
if (IsHandled) return true;
return QScrollArea::viewportEvent(e);
}
Second test was:
bool viewportEvent(QEvent* e) {
bool IsMouseEvent = e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonRelease || e->type() == QEvent::MouseMove;
QMouseEvent* me = (QMouseEvent*) e;
bool IsHandled = false;
if (IsMouseEvent) {
if (e->type() == QEvent::MouseButtonPress) {
_PressEvent = *me;
IsHandled = true; // Eating press event
} else if (e->type() == QEvent::MouseButtonRelease) {
QPoint PressPoint = _PressEvent.pos();
widget()->setAttribute(Qt::WA_TransparentForMouseEvents, false);
QWidget* w = widget()->childAt(PressPoint);
if (w && w != widget()) {
QCoreApplication::sendEvent(widget(), &_PressEvent);
QCoreApplication::sendEvent(widget(), e);
}
widget()->setAttribute(Qt::WA_TransparentForMouseEvents, true);
}
}
if (IsHandled) return true;
return QScrollArea::viewportEvent(e);
}
None of them was working. Why does the redirection not work. Is there a better solution for my purpose?

How to select child's items checkBoxs in QTreeView when select their parent's checkbox

I want to select/unselect all child's items QCheckBoxs when I select/unselect their parent's item QCheckBox.
i inherit from QTreeView and detect when the QCheckBox is selected then i call function to do selecting/unselecting process.
here my code:
#ifndef MYQTREEVIEW_H
#define MYQTREEVIEW_H
#include <QTreeView>
#include <QMouseEvent>
#include <QDebug>
#include <QStandardItem>
class MyQTreeView: public QTreeView {
public:
MyQTreeView(QWidget* parent=0): QTreeView(parent){}
virtual ~MyQTreeView() {}
protected:
void resettingCheckBox (QModelIndex& parentIndex) {
if ( ! parentIndex.isValid() )
return;
QString text = parentIndex.data( Qt::DisplayRole ).value<QString>();
qDebug() << "parent is: " << text;
if ( model()->hasChildren(parentIndex) ) {
for( int i = 0; i < model()->rowCount(parentIndex) ; i++ ) {
QModelIndex childIndex = model()->index( i, 0, parentIndex );
if ( model()->hasChildren(childIndex) )
resettingCheckBox(childIndex);
else {
QString text = childIndex.data( Qt::DisplayRole ).value<QString>();
qDebug() << "child is: " << text;
QStandardItem *parentItem = static_cast<QStandardItem*> (parentIndex.internalPointer());
QStandardItem *childItem = static_cast<QStandardItem*> (childIndex.internalPointer());
if ( parentItem->checkState() == Qt::Checked ) {
qDebug() << "child item " << childItem->checkState();
childItem->setCheckState( Qt::Unchecked);
}
else {
qDebug() << "child item " << childItem->checkState();
childItem->setCheckState( Qt::Checked);
}
}
}
}
}
void mousePressEvent (QMouseEvent *event) {
QModelIndex index = indexAt(event->pos());
if(index.isValid()) {
QStyleOptionButton opt;
opt.QStyleOption::operator=(viewOptions());
opt.rect = visualRect(index);
QRect rect = style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt);
if (rect.contains(event->pos())) {
resettingCheckBox(index);
}
QTreeView::mousePressEvent(event);
}
}
};
#endif // MYQTREEVIEW_H
the code is not working probably when i select/unselect parent checkBox (subchilds is not selected/unselected).
Thanks in advance.
I believe the best way to manipulate treeview items is through the model. It looks like you're using QStandardItemModel; so you can override your model's setData method and reset child items values for the item index passed as a parameter to this method. Below is a small example:
class TestModel : public QStandardItemModel
{
public:
TestModel() {}
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole)
{
QStandardItem *item = itemFromIndex(index);
for( int i = 0; i < item->rowCount() ; i++ )
{
QStandardItem *childItem = item->child(i);
setData(childItem->index(), value, role);
}
return QStandardItemModel::setData(index, value, role);
}
};
here's how this model gets initialized:
QStandardItemModel* tableModel = new TestModel();
QStandardItem* parentItem = tableModel->invisibleRootItem();
for (int i = 0; i < 4; ++i)
{
QStandardItem *item = new QStandardItem(QString("item %0").arg(i));
item->setCheckable(true);
parentItem->appendRow(item);
parentItem = item;
}
treeView->setModel(tableModel);
hope this helps, regards
It seems to me that you should call QTreeView::mousePressEvent(event) before resettingCheckBox(index), to let QTreeView update the checkState.
override mouseReleaseEvent() instead of mousePressEvent()!
because the checkState changes when mouse Release not mouse - press!

Resources