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;
}
I want to make a process list window, so I traverse the process list, but I don’t know how to get the icon and display it in the listWidget
This is my code:
#include <windows.h>
#include <psapi.h>
#include <tlhelp32.h>
#include <iostream>
HANDLE hSnap = CreateToolhelp32Snapshot(PROCESS_ALL_ACCESS, 0);
if( hSnap != INVALID_HANDLE_VALUE )
{
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
if( Process32First(hSnap, &pe) )
{
DWORD curID = GetCurrentThreadId();
do{
if(pe.th32ProcessID && pe.th32ProcessID != curID)
{
QListWidgetItem *it = new QListWidgetItem(
/* icon */
PaddingZero(QString::number(pe.th32ProcessID, 16)).toUpper() +"-"+ QString::fromWCharArray(pe.szExeFile),
ui->listWidget
);
ui->listWidget->addItem(it);
//printf("name: %ls, id: %d\n",pe.szExeFile,pe.th32ProcessID);
}
}while( Process32Next(hSnap, &pe) );
}
}
CloseHandle(hSnap);
How can I get the ico of each process.
After a few days, I finally found the answer
HANDLE hSnap = CreateToolhelp32Snapshot(PROCESS_ALL_ACCESS, 0);
if( hSnap != INVALID_HANDLE_VALUE )
{
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
if( Process32First(hSnap, &pe) )
{
do{
if(pe.th32ProcessID != GetCurrentProcessId())
{
// https://forum.qt.io/topic/62866/getting-icon-from-external-applications/5
// https://stackoverflow.com/questions/63653786/error-using-getmodulefilenameexa-function-in-qt?noredirect=1#comment112561229_63653786
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
wchar_t lpFilename[1024];
GetModuleFileNameExW(hProcess, NULL, lpFilename, sizeof(lpFilename));
CloseHandle(hProcess);
QFileInfo fin( QString::fromWCharArray(lpFilename) );
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(fin.path());
QIcon ic = model->fileIcon(model->index(fin.filePath()));
QListWidgetItem *it = new QListWidgetItem(
ic,
PaddingZero(QString::number(pe.th32ProcessID, 16)).toUpper() +
"-" +
QString::fromWCharArray(pe.szExeFile),
ui->list
);
ui->list->addItem(it);
//printf("name: %ls, id: %d\n",pe.szExeFile,pe.th32ProcessID);
}
}while( Process32Next(hSnap, &pe) );
}
}
CloseHandle(hSnap);
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();
}
I have a file which has absolute filepaths listed, 1 per line. The listed files are in order, so all files in e.g. the /Documents/ dir will be listed after eachother in the file.
What I want to do is to place all these files in a QTreeWidget in a nice hierarchic structure, just like a normal filesystem. How would I do that from my file of absolute paths I have?
This is how far I've gotten with my coding on this:
QFile file(FILENAME_ENCRYPTED);
QString line;
QDir dir;
QTreeWidgetItem *item;
if (file.open(QIODevice::ReadOnly)) {
QTextStream stream( &file );
do {
line = stream.readLine();
if (!line.isNull()) {
dir = QDir(line);
item = new QTreeWidgetItem();
item->setText(0, dir.dirName());
this->ui->treeWidget->addTopLevelItem(item);
}
} while (!line.isNull());
}
file.close();
This works fine, but it only lists all the filenames after eachother. I guess I have to do some recursive function but recursion is not my best friend, I prefer iteration! Could someone give me a push in the right direction? :)
No recursion should be necessary. You can use QString::split() to split the file path into separate QStrings in a QStringList based on a separator (i.e., "/"), then iterate through each QString to determine the file structure.
EDIT: Here is an example:
#include <QtGui>
const QString s1 = "Docs/Testing/textFile1.txt";
const QString s2 = "Docs/Testing/textFile2.txt";
const QString s3 = "Docs/Testing/textFile3.txt";
const QString s4 = "Docs/Testing/AnotherFolder/textFile4.txt";
const QString s5 = "ThisIsGonnaBeCrazy/WholeNewFolder/AndAnother/file.pdf";
const QString s6 = "ThisIsGonnaBeCrazy/file.doc";
class MainWindow : public QMainWindow
{
public:
MainWindow()
{
QTreeWidget *treeWidget = new QTreeWidget;
QStringList fileNames;
fileNames << s1 << s2 << s3 << s4 << s5 << s6;
QTreeWidgetItem *topLevelItem = NULL;
foreach (const QString &fileName, fileNames)
{
QStringList splitFileName = fileName.split("/");
// add root folder as top level item if treeWidget doesn't already have it
if (treeWidget->findItems(splitFileName[0], Qt::MatchFixedString).isEmpty())
{
topLevelItem = new QTreeWidgetItem;
topLevelItem->setText(0, splitFileName[0]);
treeWidget->addTopLevelItem(topLevelItem);
}
QTreeWidgetItem *parentItem = topLevelItem;
// iterate through non-root directories (file name comes after)
for (int i = 1; i < splitFileName.size() - 1; ++i)
{
// iterate through children of parentItem to see if this directory exists
bool thisDirectoryExists = false;
for (int j = 0; j < parentItem->childCount(); ++j)
{
if (splitFileName[i] == parentItem->child(j)->text(0))
{
thisDirectoryExists = true;
parentItem = parentItem->child(j);
break;
}
}
if (!thisDirectoryExists)
{
parentItem = new QTreeWidgetItem(parentItem);
parentItem->setText(0, splitFileName[i]);
}
}
QTreeWidgetItem *childItem = new QTreeWidgetItem(parentItem);
childItem->setText(0, splitFileName.last());
}
setCentralWidget(treeWidget);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
Note that you can use QTreeWidgetItem::setData() to set the file name for each file if you like. My example does not do that, though.
This works for me:
QStringList tokens = path.split ( '/' );
QTreeWidgetItem* treeWidgetItem = NULL;
for ( int32_t j = 0 ; j < packageTreeWidget->topLevelItemCount(); ++j )
{
if ( packageTreeWidget->topLevelItem ( j )->text ( 0 ) == tokens.at ( 0 ) )
{
treeWidgetItem = packageTreeWidget->topLevelItem ( j );
break;
}
}
if ( treeWidgetItem == NULL )
{
treeWidgetItem = new QTreeWidgetItem;
treeWidgetItem->setText ( 0, tokens.at ( 0 ) );
packageTreeWidget->addTopLevelItem ( treeWidgetItem );
}
for ( int32_t j = 1; j < tokens.size(); ++j )
{
int32_t k;
for ( k = 0 ; k < treeWidgetItem->childCount(); ++k )
{
if ( treeWidgetItem->child ( k )->text ( 0 ) == tokens.at ( j ) )
{
treeWidgetItem = treeWidgetItem->child ( k );
break;
}
}
if ( k == treeWidgetItem->childCount() )
{
QTreeWidgetItem* newTreeWidgetItem = new QTreeWidgetItem;
newTreeWidgetItem->setText ( 0, tokens.at ( j ) );
treeWidgetItem->addChild ( newTreeWidgetItem );
treeWidgetItem = newTreeWidgetItem;
}
}
The index 'i' is missing from the loops because is the one I used to iterate the file paths, replace 'packageTreeWidget' with the name of your Tree Widget, I am using this to display the contents of a zip-like package file.
I am looking for a widget in Qt which is similar to MFC's IP address control. Does anyone know of such a widget, or perhaps how I can create one?
I have no idea what's an MFC IP Widget, but looks like it is a Widget to enter an IP address.
You need to use a QLineEdit with a inputMask "000.000.000.000;_"
QLineEdit *ipEdit = new QLineEdit();
ipEdit->setInputMask("000.000.000.000;_");
ipEdit->show();
A little improvement of jpo38's code...
#include <QFrame>
#include <QLineEdit>
#include <QIntValidator>
#include "stdint.h"
#include <QHBoxLayout>
#include <QFont>
#include <QLabel>
#include <QKeyEvent>
class IPCtrl : public QFrame
{
Q_OBJECT
public:
IPCtrl(QWidget *parent = 0);
~IPCtrl();
virtual bool eventFilter( QObject *obj, QEvent *event );
public slots:
void slotTextChanged( QLineEdit* pEdit );
signals:
void signalTextChanged( QLineEdit* pEdit );
private:
enum
{
QTUTL_IP_SIZE = 4,// число октетов IP адресе
MAX_DIGITS = 3 // число символов в LineEdit
};
QLineEdit *(m_pLineEdit[QTUTL_IP_SIZE]);
void MoveNextLineEdit (int i);
void MovePrevLineEdit (int i);
};
IPCtrl::IPCtrl(QWidget *parent) : QFrame(parent)
{
setFrameShape( QFrame::StyledPanel );
setFrameShadow( QFrame::Sunken );
QHBoxLayout* pLayout = new QHBoxLayout( this );
setLayout( pLayout );
pLayout->setContentsMargins( 0, 0, 0, 0 );
pLayout->setSpacing( 0 );
for ( int i = 0; i != QTUTL_IP_SIZE; ++i )
{
if ( i != 0 )
{
QLabel* pDot = new QLabel( ".", this );
pDot->setStyleSheet( "background: white" );
pLayout->addWidget( pDot );
pLayout->setStretch( pLayout->count(), 0 );
}
m_pLineEdit[i] = new QLineEdit( this );
QLineEdit* pEdit = m_pLineEdit[i];
pEdit->installEventFilter( this );
pLayout->addWidget( pEdit );
pLayout->setStretch( pLayout->count(), 1 );
pEdit->setFrame( false );
pEdit->setAlignment( Qt::AlignCenter );
QFont font = pEdit->font();
font.setStyleHint( QFont::Monospace );
font.setFixedPitch( true );
pEdit->setFont( font );
QRegExp rx ( "^(0|[1-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$" );
QValidator *validator = new QRegExpValidator(rx, pEdit);
pEdit->setValidator( validator );
}
setMaximumWidth( 30 * QTUTL_IP_SIZE );
connect( this, SIGNAL(signalTextChanged(QLineEdit*)),
this, SLOT(slotTextChanged(QLineEdit*)),
Qt::QueuedConnection );
}
IPCtrl::~IPCtrl()
{
}
void IPCtrl::slotTextChanged( QLineEdit* pEdit )
{
for ( unsigned int i = 0; i != QTUTL_IP_SIZE; ++i )
{
if ( pEdit == m_pLineEdit[i] )
{
if ( ( pEdit->text().size() == MAX_DIGITS && pEdit->text().size() == pEdit->cursorPosition() ) || ( pEdit->text() == "0") )
{
// auto-move to next item
if ( i+1 != QTUTL_IP_SIZE )
{
m_pLineEdit[i+1]->setFocus();
m_pLineEdit[i+1]->selectAll();
}
}
}
}
}
bool IPCtrl::eventFilter(QObject *obj, QEvent *event)
{
bool bRes = QFrame::eventFilter(obj, event);
if ( event->type() == QEvent::KeyPress )
{
QKeyEvent* pEvent = dynamic_cast<QKeyEvent*>( event );
if ( pEvent )
{
for ( unsigned int i = 0; i != QTUTL_IP_SIZE; ++i )
{
QLineEdit* pEdit = m_pLineEdit[i];
if ( pEdit == obj )
{
switch ( pEvent->key() )
{
case Qt::Key_Left:
if ( pEdit->cursorPosition() == 0 )
{
// user wants to move to previous item
MovePrevLineEdit(i);
}
break;
case Qt::Key_Right:
if ( pEdit->text().isEmpty() || (pEdit->text().size() == pEdit->cursorPosition()) )
{
// user wants to move to next item
MoveNextLineEdit(i);
}
break;
case Qt::Key_0:
if ( pEdit->text().isEmpty() || pEdit->text() == "0" )
{
pEdit->setText("0");
// user wants to move to next item
MoveNextLineEdit(i);
}
emit signalTextChanged( pEdit );
break;
case Qt::Key_Backspace:
if ( pEdit->text().isEmpty() || pEdit->cursorPosition() == 0)
{
// user wants to move to previous item
MovePrevLineEdit(i);
}
break;
case Qt::Key_Comma:
case Qt::Key_Period:
MoveNextLineEdit(i);
break;
default:
emit signalTextChanged( pEdit );
break;
}
}
}
}
}
return bRes;
}
void IPCtrl::MoveNextLineEdit(int i)
{
if ( i+1 != QTUTL_IP_SIZE )
{
m_pLineEdit[i+1]->setFocus();
m_pLineEdit[i+1]->setCursorPosition( 0 );
m_pLineEdit[i+1]->selectAll();
}
}
void IPCtrl::MovePrevLineEdit(int i)
{
if ( i != 0 )
{
m_pLineEdit[i-1]->setFocus();
m_pLineEdit[i-1]->setCursorPosition( m_pLineEdit[i-1]->text().size() );
//m_pLineEdit[i-1]->selectAll();
}
}
I agree with little_su: QLineEdit with input mask does not look and behaves as nice as the standard Windows IP control. I worked out a complete QWidget-based IP control embedding 4 QLineEdit and 3 QLabel (for dots). It just looks and behaves perfectly as MFC/Windows IP controls.
Here is the code:
class IPCtrl : public QFrame
{
typedef QFrame baseClass;
Q_OBJECT
public:
IPCtrl(QWidget *parent);
~IPCtrl();
#define QTUTL_IP_SIZE 4
virtual bool eventFilter( QObject *obj, QEvent *event );
public slots:
void slotTextChanged( QLineEdit* pEdit );
signals:
void signalTextChanged( QLineEdit* pEdit );
private:
QLineEdit *(m_pLineEdit[QTUTL_IP_SIZE]);
static std::string getIPItemStr( unsigned char item );
};
class IPItemValidator : public QIntValidator
{
public:
IPItemValidator( QObject* parent ) : QIntValidator( parent )
{
setRange( 0, UCHAR_MAX );
}
~IPItemValidator() {}
virtual void fixup( QString & input ) const
{
if ( input.isEmpty() )
input = "0";
}
};
IPCtrl::IPCtrl(QWidget *parent) : baseClass(parent)
{
setFrameShape( QFrame::StyledPanel );
setFrameShadow( QFrame::Sunken );
QHBoxLayout* pLayout = new QHBoxLayout( this );
setLayout( pLayout );
pLayout->setContentsMargins( 0, 0, 0, 0 );
pLayout->setSpacing( 0 );
for ( int i = 0; i != QTUTL_IP_SIZE; ++i )
{
if ( i != 0 )
{
QLabel* pDot = new QLabel( ".", this );
pDot->setStyleSheet( "background: white" );
pLayout->addWidget( pDot );
pLayout->setStretch( pLayout->count(), 0 );
}
m_pLineEdit[i] = new QLineEdit( this );
QLineEdit* pEdit = m_pLineEdit[i];
pEdit->installEventFilter( this );
pLayout->addWidget( pEdit );
pLayout->setStretch( pLayout->count(), 1 );
pEdit->setFrame( false );
pEdit->setAlignment( Qt::AlignCenter );
QFont font = pEdit->font();
font.setStyleHint( QFont::Monospace );
font.setFixedPitch( true );
pEdit->setFont( font );
pEdit->setValidator( new IPItemValidator( pEdit ) );
}
setMaximumWidth( 30 * QTUTL_IP_SIZE );
connect( this, SIGNAL(signalTextChanged(QLineEdit*)),
this, SLOT(slotTextChanged(QLineEdit*)),
Qt::QueuedConnection );
}
IPCtrl::~IPCtrl()
{
}
std::string IPCtrl::getIPItemStr( unsigned char item )
{
std::strstream str;
str << (int) item;
str << std::ends;
return str.str();
}
void IPCtrl::slotTextChanged( QLineEdit* pEdit )
{
for ( unsigned int i = 0; i != QTUTL_IP_SIZE; ++i )
{
if ( pEdit == m_pLineEdit[i] )
{
if ( pEdit->text().size() == getIPItemStr( UCHAR_MAX ).size() &&
pEdit->text().size() == pEdit->cursorPosition() )
{
// auto-move to next item
if ( i+1 != QTUTL_IP_SIZE )
{
m_pLineEdit[i+1]->setFocus();
m_pLineEdit[i+1]->selectAll();
}
}
}
}
}
bool IPCtrl::eventFilter(QObject *obj, QEvent *event)
{
bool bRes = baseClass::eventFilter(obj, event);
if ( event->type() == QEvent::KeyPress )
{
QKeyEvent* pEvent = dynamic_cast<QKeyEvent*>( event );
if ( pEvent )
{
for ( unsigned int i = 0; i != QTUTL_IP_SIZE; ++i )
{
QLineEdit* pEdit = m_pLineEdit[i];
if ( pEdit == obj )
{
switch ( pEvent->key() )
{
case Qt::Key_Left:
{
if ( pEdit->cursorPosition() == 0 )
{
// user wants to move to previous item
if ( i != 0 )
{
m_pLineEdit[i-1]->setFocus();
m_pLineEdit[i-1]->setCursorPosition( m_pLineEdit[i-1]->text().size() );
}
}
break;
}
case Qt::Key_Right:
{
if ( pEdit->text().isEmpty() ||
(pEdit->text().size() == pEdit->cursorPosition()) )
{
// user wants to move to next item
if ( i+1 != QTUTL_IP_SIZE )
{
m_pLineEdit[i+1]->setFocus();
m_pLineEdit[i+1]->setCursorPosition( 0 );
}
}
break;
}
default:
{
emit signalTextChanged( pEdit );
}
}
break;
}
}
}
}
return bRes;
}
A little improvement of Tugo's code... I cannot comment so...
classic array to std::array
setter and getter
remove styleSheet, u can use your own
ipctrl.h :
#pragma once
#include <QFrame>
#include <array>
/// Thanx to https://stackoverflow.com/a/11358560/8524139
class QLineEdit;
class IPCtrl : public QFrame
{
Q_OBJECT
enum
{
QTUTL_IP_SIZE = 4, // число октетов IP адресе
MAX_DIGITS = 3 // число символов в LineEdit
};
public:
IPCtrl(QWidget *parent = 0);
~IPCtrl();
virtual bool eventFilter(QObject *obj, QEvent *event);
std::array<quint8, QTUTL_IP_SIZE> getIP() const;
void setIP(std::array<quint8, QTUTL_IP_SIZE> ipAddr);
signals:
void signalTextChanged(QLineEdit *pEdit);
private:
std::array<QLineEdit *, QTUTL_IP_SIZE> m_pLineEdit;
void slotTextChanged(QLineEdit *pEdit);
void moveNextLineEdit(int i);
void movePrevLineEdit(int i);
};
ipctrl.cpp :
#include "ipctrl.h"
#include <QHBoxLayout>
#include <QIntValidator>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
IPCtrl::IPCtrl(QWidget *parent) : QFrame(parent)
{
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Sunken);
QHBoxLayout *pLayout = new QHBoxLayout(this);
setLayout(pLayout);
pLayout->setContentsMargins(0, 0, 1, 0);
pLayout->setSpacing(0);
for (int i = 0; i != QTUTL_IP_SIZE; ++i)
{
if (i != 0)
{
QLabel *pDot = new QLabel(".", this);
pLayout->addWidget(pDot);
pLayout->setStretch(pLayout->count(), 0);
}
m_pLineEdit.at(i) = new QLineEdit(this);
QLineEdit *pEdit = m_pLineEdit.at(i);
pEdit->installEventFilter(this);
pLayout->addWidget(pEdit);
pLayout->setStretch(pLayout->count(), 1);
pEdit->setFrame(false);
pEdit->setAlignment(Qt::AlignCenter);
QFont font = pEdit->font();
font.setStyleHint(QFont::Monospace);
font.setFixedPitch(true);
pEdit->setFont(font);
QRegExp rx("^(0|[1-9]|[1-9][0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$");
QValidator *validator = new QRegExpValidator(rx, pEdit);
pEdit->setValidator(validator);
}
setMaximumWidth(30 * QTUTL_IP_SIZE);
connect(this, &IPCtrl::signalTextChanged, this, &IPCtrl::slotTextChanged, Qt::QueuedConnection);
}
IPCtrl::~IPCtrl()
{
}
void IPCtrl::slotTextChanged(QLineEdit *pEdit)
{
for (unsigned int i = 0; i != QTUTL_IP_SIZE; ++i)
{
if (pEdit == m_pLineEdit.at(i))
{
if ((pEdit->text().size() == MAX_DIGITS && pEdit->text().size() == pEdit->cursorPosition())
|| (pEdit->text() == "0"))
{
// auto-move to next item
if (i + 1 != QTUTL_IP_SIZE)
{
m_pLineEdit.at(i + 1)->setFocus();
m_pLineEdit.at(i + 1)->selectAll();
}
}
}
}
}
bool IPCtrl::eventFilter(QObject *obj, QEvent *event)
{
bool bRes = QFrame::eventFilter(obj, event);
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *pEvent = dynamic_cast<QKeyEvent *>(event);
if (pEvent)
{
for (unsigned int i = 0; i != QTUTL_IP_SIZE; ++i)
{
QLineEdit *pEdit = m_pLineEdit[i];
if (pEdit == obj)
{
switch (pEvent->key())
{
case Qt::Key_Left:
if (pEdit->cursorPosition() == 0)
{
// user wants to move to previous item
movePrevLineEdit(i);
}
break;
case Qt::Key_Right:
if (pEdit->text().isEmpty() || (pEdit->text().size() == pEdit->cursorPosition()))
{
// user wants to move to next item
moveNextLineEdit(i);
}
break;
case Qt::Key_0:
if (pEdit->text().isEmpty() || pEdit->text() == "0")
{
pEdit->setText("0");
// user wants to move to next item
moveNextLineEdit(i);
}
emit signalTextChanged(pEdit);
break;
case Qt::Key_Backspace:
if (pEdit->text().isEmpty() || pEdit->cursorPosition() == 0)
{
// user wants to move to previous item
movePrevLineEdit(i);
}
break;
case Qt::Key_Comma:
case Qt::Key_Period:
moveNextLineEdit(i);
break;
default:
emit signalTextChanged(pEdit);
break;
}
}
}
}
}
return bRes;
}
std::array<quint8, IPCtrl::QTUTL_IP_SIZE> IPCtrl::getIP() const
{
std::array<quint8, QTUTL_IP_SIZE> ipAddr;
std::transform(m_pLineEdit.cbegin(), m_pLineEdit.cend(), ipAddr.begin(),
[](const QLineEdit *lineEdit) -> quint8 { return lineEdit->text().toUInt(); });
return ipAddr;
}
void IPCtrl::setIP(std::array<quint8, IPCtrl::QTUTL_IP_SIZE> ipAddr)
{
for (auto i = 0; i != QTUTL_IP_SIZE; ++i)
{
m_pLineEdit.at(i)->setText(QString::number(ipAddr.at(i)));
}
}
void IPCtrl::moveNextLineEdit(int i)
{
if (i + 1 != QTUTL_IP_SIZE)
{
m_pLineEdit.at(i + 1)->setFocus();
m_pLineEdit.at(i + 1)->setCursorPosition(0);
m_pLineEdit.at(i + 1)->selectAll();
}
}
void IPCtrl::movePrevLineEdit(int i)
{
if (i != 0)
{
m_pLineEdit.at(i - 1)->setFocus();
m_pLineEdit.at(i - 1)->setCursorPosition(m_pLineEdit[i - 1]->text().size());
// m_pLineEdit[i-1]->selectAll();
}
}