988 lines
32 KiB
C++
988 lines
32 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2009 Jürgen Riegel <juergen.riegel@web.de> *
|
|
* *
|
|
* This file is part of the FreeCAD CAx development system. *
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License as published by the Free Software Foundation; either *
|
|
* version 2 of the License, or (at your option) any later version. *
|
|
* *
|
|
* This library 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 Library General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Library General Public *
|
|
* License along with this library; see the file COPYING.LIB. If not, *
|
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
|
* Suite 330, Boston, MA 02111-1307, USA *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#include "PreCompiled.h"
|
|
#ifndef _PreComp_
|
|
#include <vector>
|
|
|
|
#include <QApplication>
|
|
#include <QDesktopServices>
|
|
#include <QFileInfo>
|
|
#include <QLatin1String>
|
|
#include <QLineEdit>
|
|
#include <QMenu>
|
|
#include <QMessageBox>
|
|
#include <QMouseEvent>
|
|
#include <QNetworkRequest>
|
|
#include <QRegularExpression>
|
|
#include <QRegularExpressionMatch>
|
|
#include <QSignalMapper>
|
|
#include <QStatusBar>
|
|
#endif
|
|
|
|
#if defined(QTWEBENGINE)
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
#include <QWebEngineContextMenuData>
|
|
#else
|
|
#include <QWebEngineContextMenuRequest>
|
|
#endif
|
|
#include <QWebEnginePage>
|
|
#include <QWebEngineProfile>
|
|
#include <QWebEngineSettings>
|
|
#include <QWebEngineUrlRequestInfo>
|
|
#include <QWebEngineUrlRequestInterceptor>
|
|
#include <QWebEngineView>
|
|
#elif defined(QTWEBKIT)
|
|
#include <QNetworkAccessManager>
|
|
#include <QWebFrame>
|
|
#include <QWebSettings>
|
|
#include <QWebView>
|
|
using QWebEngineView = QWebView;
|
|
using QWebEnginePage = QWebPage;
|
|
#endif
|
|
|
|
#include <App/Document.h>
|
|
#include <Base/Exception.h>
|
|
#include <Base/Parameter.h>
|
|
#include <Base/Tools.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/Command.h>
|
|
#include <Gui/DownloadManager.h>
|
|
#include <Gui/MDIViewPy.h>
|
|
#include <Gui/MainWindow.h>
|
|
#include <Gui/ProgressBar.h>
|
|
#include <Gui/TextDocumentEditorView.h>
|
|
|
|
#include "BrowserView.h"
|
|
#include "CookieJar.h"
|
|
|
|
|
|
using namespace WebGui;
|
|
using namespace Gui;
|
|
|
|
namespace WebGui
|
|
{
|
|
enum WebAction
|
|
{
|
|
OpenLink = 0,
|
|
OpenLinkInNewWindow = 1,
|
|
ViewSource = 2 // QWebView doesn't have a ViewSource option
|
|
};
|
|
|
|
#ifdef QTWEBENGINE
|
|
class WebEngineUrlRequestInterceptor: public QWebEngineUrlRequestInterceptor
|
|
{
|
|
public:
|
|
explicit WebEngineUrlRequestInterceptor(BrowserView* parent)
|
|
: QWebEngineUrlRequestInterceptor(parent)
|
|
, m_parent(parent)
|
|
{}
|
|
|
|
void interceptRequest(QWebEngineUrlRequestInfo& info) override
|
|
{
|
|
// do something with this resource, click or get img for example
|
|
if (info.navigationType() == QWebEngineUrlRequestInfo::NavigationTypeLink) {
|
|
// wash out windows file:///C:/something ->file://C:/something
|
|
QUrl url = info.requestUrl();
|
|
// match & catch drive letter forward
|
|
QRegularExpression re(QLatin1String("^/([a-zA-Z]\\:.*)"));
|
|
QRegularExpressionMatch match = re.match(url.path());
|
|
|
|
if (url.host().isEmpty() && url.isLocalFile() && match.hasMatch()) {
|
|
// clip / in file urs ie /C:/something -> C:/something
|
|
url.setPath(match.captured(1));
|
|
}
|
|
|
|
// invoke thread safe.
|
|
QMetaObject::invokeMethod(m_parent, "urlFilter", Q_ARG(QUrl, url));
|
|
}
|
|
}
|
|
|
|
private:
|
|
BrowserView* m_parent;
|
|
};
|
|
#endif
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
UrlWidget::UrlWidget(BrowserView* view)
|
|
: QLineEdit(view)
|
|
, m_view(view)
|
|
{
|
|
setText(QLatin1String("https://"));
|
|
hide();
|
|
}
|
|
|
|
UrlWidget::~UrlWidget() = default;
|
|
|
|
void UrlWidget::keyPressEvent(QKeyEvent* keyEvt)
|
|
{
|
|
switch (keyEvt->key()) {
|
|
case Qt::Key_Escape:
|
|
hide();
|
|
break;
|
|
case Qt::Key_Return:
|
|
case Qt::Key_Enter:
|
|
m_view->load(text().toLatin1());
|
|
hide();
|
|
break;
|
|
default:
|
|
QLineEdit::keyPressEvent(keyEvt);
|
|
}
|
|
}
|
|
|
|
void UrlWidget::display()
|
|
{
|
|
setFixedWidth(m_view->size().width());
|
|
setText(m_view->url().toString());
|
|
show();
|
|
setFocus(Qt::ActiveWindowFocusReason);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
class BrowserViewPy: public Py::PythonExtension<BrowserViewPy>
|
|
{
|
|
public:
|
|
using BaseType = Py::PythonExtension<BrowserViewPy>;
|
|
static void init_type(); // announce properties and methods
|
|
|
|
explicit BrowserViewPy(BrowserView* view);
|
|
~BrowserViewPy() override;
|
|
|
|
Py::Object repr() override;
|
|
Py::Object getattr(const char*) override;
|
|
Py::Object cast_to_base(const Py::Tuple&);
|
|
|
|
Py::Object setHtml(const Py::Tuple&);
|
|
Py::Object load(const Py::Tuple&);
|
|
Py::Object stop(const Py::Tuple&);
|
|
Py::Object url(const Py::Tuple&);
|
|
|
|
BrowserView* getBrowserViewPtr();
|
|
|
|
private:
|
|
Gui::MDIViewPy base;
|
|
};
|
|
|
|
void BrowserViewPy::init_type()
|
|
{
|
|
behaviors().name("BrowserView");
|
|
behaviors().doc("Python interface class to BrowserView");
|
|
// you must have overwritten the virtual functions
|
|
behaviors().supportRepr();
|
|
behaviors().supportGetattr();
|
|
behaviors().supportSetattr();
|
|
behaviors().readyType();
|
|
|
|
add_varargs_method("setHtml", &BrowserViewPy::setHtml, "setHtml(str)");
|
|
add_varargs_method("load", &BrowserViewPy::load, "load(url)");
|
|
add_varargs_method("stop", &BrowserViewPy::stop, "stop()");
|
|
add_varargs_method("url", &BrowserViewPy::url, "url()");
|
|
add_varargs_method("cast_to_base",
|
|
&BrowserViewPy::cast_to_base,
|
|
"cast_to_base() cast to MDIView class");
|
|
}
|
|
|
|
BrowserViewPy::BrowserViewPy(BrowserView* view)
|
|
: base(view)
|
|
{}
|
|
|
|
BrowserViewPy::~BrowserViewPy() = default;
|
|
|
|
BrowserView* BrowserViewPy::getBrowserViewPtr()
|
|
{
|
|
return qobject_cast<BrowserView*>(base.getMDIViewPtr());
|
|
}
|
|
|
|
Py::Object BrowserViewPy::cast_to_base(const Py::Tuple&)
|
|
{
|
|
return Gui::MDIViewPy::create(base.getMDIViewPtr());
|
|
}
|
|
|
|
Py::Object BrowserViewPy::repr()
|
|
{
|
|
std::stringstream s;
|
|
s << "<BrowserView at " << this << ">";
|
|
return Py::String(s.str());
|
|
}
|
|
|
|
// Since with PyCXX it's not possible to make a sub-class of MDIViewPy
|
|
// a trick is to use MDIViewPy as class member and override getattr() to
|
|
// join the attributes of both classes. This way all methods of MDIViewPy
|
|
// appear for SheetViewPy, too.
|
|
Py::Object BrowserViewPy::getattr(const char* attr)
|
|
{
|
|
if (!getBrowserViewPtr()) {
|
|
std::ostringstream s_out;
|
|
s_out << "Cannot access attribute '" << attr << "' of deleted object";
|
|
throw Py::RuntimeError(s_out.str());
|
|
}
|
|
std::string name(attr);
|
|
if (name == "__dict__" || name == "__class__") {
|
|
Py::Dict dict_self(BaseType::getattr("__dict__"));
|
|
Py::Dict dict_base(base.getattr("__dict__"));
|
|
for (const auto& it : dict_base) {
|
|
dict_self.setItem(it.first, it.second);
|
|
}
|
|
return dict_self;
|
|
}
|
|
|
|
try {
|
|
return BaseType::getattr(attr);
|
|
}
|
|
catch (Py::AttributeError& e) {
|
|
e.clear();
|
|
return base.getattr(attr);
|
|
}
|
|
}
|
|
|
|
Py::Object BrowserViewPy::setHtml(const Py::Tuple& args)
|
|
{
|
|
char* HtmlCode;
|
|
char* BaseUrl;
|
|
if (!PyArg_ParseTuple(args.ptr(), "et|s", "utf-8", &HtmlCode, &BaseUrl)) {
|
|
throw Py::Exception();
|
|
}
|
|
|
|
std::string EncodedHtml = std::string(HtmlCode);
|
|
PyMem_Free(HtmlCode);
|
|
|
|
getBrowserViewPtr()->setHtml(QString::fromUtf8(EncodedHtml.c_str()),
|
|
QUrl(QString::fromUtf8(BaseUrl)));
|
|
return Py::None();
|
|
}
|
|
|
|
Py::Object BrowserViewPy::load(const Py::Tuple& args)
|
|
{
|
|
char* BaseUrl;
|
|
if (!PyArg_ParseTuple(args.ptr(), "s", &BaseUrl)) {
|
|
throw Py::Exception();
|
|
}
|
|
|
|
getBrowserViewPtr()->load(BaseUrl);
|
|
return Py::None();
|
|
}
|
|
|
|
Py::Object BrowserViewPy::stop(const Py::Tuple& args)
|
|
{
|
|
if (!PyArg_ParseTuple(args.ptr(), "")) {
|
|
throw Py::Exception();
|
|
}
|
|
|
|
getBrowserViewPtr()->stop();
|
|
return Py::None();
|
|
}
|
|
|
|
Py::Object BrowserViewPy::url(const Py::Tuple& args)
|
|
{
|
|
if (!PyArg_ParseTuple(args.ptr(), "")) {
|
|
throw Py::Exception();
|
|
}
|
|
|
|
QUrl url = getBrowserViewPtr()->url();
|
|
return Py::String(url.toString().toStdString());
|
|
}
|
|
|
|
} // namespace WebGui
|
|
|
|
/**
|
|
* Constructs a WebView widget which can be zoomed with Ctrl+Mousewheel
|
|
*
|
|
*/
|
|
|
|
WebView::WebView(QWidget* parent)
|
|
: QWebEngineView(parent)
|
|
{
|
|
#ifdef QTWEBKIT
|
|
// Increase html font size for high DPI displays
|
|
QRect mainScreenSize = QApplication::primaryScreen()->geometry();
|
|
if (mainScreenSize.width() > 1920) {
|
|
setTextSizeMultiplier(mainScreenSize.width() / 1920.0);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void WebView::mousePressEvent(QMouseEvent* event)
|
|
{
|
|
#ifdef QTWEBKIT
|
|
if (event->button() == Qt::MiddleButton) {
|
|
QWebHitTestResult r = page()->mainFrame()->hitTestContent(event->pos());
|
|
if (!r.linkUrl().isEmpty()) {
|
|
openLinkInNewWindow(r.linkUrl());
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
QWebEngineView::mousePressEvent(event);
|
|
}
|
|
|
|
void WebView::wheelEvent(QWheelEvent* event)
|
|
{
|
|
if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
|
|
qreal factor = zoomFactor() + (-event->angleDelta().y() / 800.0);
|
|
setZoomFactor(factor);
|
|
event->accept();
|
|
return;
|
|
}
|
|
QWebEngineView::wheelEvent(event);
|
|
}
|
|
|
|
void WebView::contextMenuEvent(QContextMenuEvent* event)
|
|
{
|
|
#ifdef QTWEBENGINE
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
const QWebEngineContextMenuData r = page()->contextMenuData();
|
|
QUrl linkUrl = r.linkUrl();
|
|
#else
|
|
const QWebEngineContextMenuRequest* r = this->lastContextMenuRequest();
|
|
QUrl linkUrl = r->linkUrl();
|
|
#endif
|
|
#else
|
|
QWebHitTestResult r = page()->mainFrame()->hitTestContent(event->pos());
|
|
QUrl linkUrl = r.linkUrl();
|
|
#endif
|
|
if (!linkUrl.isEmpty()) {
|
|
QMenu menu(this);
|
|
|
|
// building a custom signal for external browser action
|
|
QSignalMapper* signalMapper = new QSignalMapper(&menu);
|
|
signalMapper->setProperty("url", QVariant(linkUrl));
|
|
|
|
QAction* extAction = menu.addAction(tr("Open in External Browser"));
|
|
signalMapper->setMapping(extAction, WebAction::OpenLink);
|
|
|
|
QAction* newAction = menu.addAction(tr("Open in new window"));
|
|
signalMapper->setMapping(newAction, WebAction::OpenLinkInNewWindow);
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
|
connect(signalMapper,
|
|
qOverload<int>(&QSignalMapper::mapped),
|
|
this,
|
|
&WebView::triggerContextMenuAction);
|
|
#else
|
|
connect(signalMapper, &QSignalMapper::mappedInt, this, &WebView::triggerContextMenuAction);
|
|
#endif
|
|
connect(extAction, &QAction::triggered, signalMapper, qOverload<>(&QSignalMapper::map));
|
|
connect(newAction, &QAction::triggered, signalMapper, qOverload<>(&QSignalMapper::map));
|
|
|
|
menu.addAction(pageAction(QWebEnginePage::DownloadLinkToDisk));
|
|
menu.addAction(pageAction(QWebEnginePage::CopyLinkToClipboard));
|
|
menu.exec(mapToGlobal(event->pos()));
|
|
return;
|
|
}
|
|
#if defined(QTWEBENGINE)
|
|
else { // for view source
|
|
// QWebEngine caches standardContextMenu, guard so we only add signalmapper once
|
|
static bool firstRun = true;
|
|
if (firstRun) {
|
|
firstRun = false;
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
QMenu* menu = page()->createStandardContextMenu();
|
|
#else
|
|
QMenu* menu = this->createStandardContextMenu();
|
|
#endif
|
|
QList<QAction*> actions = menu->actions();
|
|
for (QAction* ac : actions) {
|
|
if (ac->data().toInt() == WebAction::ViewSource) {
|
|
QSignalMapper* signalMapper = new QSignalMapper(this);
|
|
signalMapper->setProperty("url", QVariant(linkUrl));
|
|
signalMapper->setMapping(ac, WebAction::ViewSource);
|
|
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
|
connect(signalMapper,
|
|
qOverload<int>(&QSignalMapper::mapped),
|
|
this,
|
|
&WebView::triggerContextMenuAction);
|
|
#else
|
|
connect(signalMapper,
|
|
&QSignalMapper::mappedInt,
|
|
this,
|
|
&WebView::triggerContextMenuAction);
|
|
#endif
|
|
connect(ac,
|
|
&QAction::triggered,
|
|
signalMapper,
|
|
qOverload<>(&QSignalMapper::map));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
else {
|
|
QMenu* menu = page()->createStandardContextMenu();
|
|
QAction* ac = menu->addAction(tr("View source"));
|
|
ac->setData(WebAction::ViewSource);
|
|
QSignalMapper* signalMapper = new QSignalMapper(this);
|
|
signalMapper->setProperty("url", QVariant(linkUrl));
|
|
signalMapper->setMapping(ac, WebAction::ViewSource);
|
|
connect(signalMapper, SIGNAL(mapped(int)), this, SLOT(triggerContextMenuAction(int)));
|
|
connect(ac, SIGNAL(triggered()), signalMapper, SLOT(map()));
|
|
menu->exec(event->globalPos());
|
|
}
|
|
#endif
|
|
QWebEngineView::contextMenuEvent(event);
|
|
}
|
|
|
|
void WebView::triggerContextMenuAction(int id)
|
|
{
|
|
QObject* s = sender();
|
|
QUrl url = s->property("url").toUrl();
|
|
|
|
switch (id) {
|
|
case WebAction::OpenLink:
|
|
Q_EMIT openLinkInExternalBrowser(url);
|
|
break;
|
|
case WebAction::OpenLinkInNewWindow:
|
|
Q_EMIT openLinkInNewWindow(url);
|
|
break;
|
|
case WebAction::ViewSource:
|
|
Q_EMIT viewSource(url);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
/* TRANSLATOR Gui::BrowserView */
|
|
|
|
TYPESYSTEM_SOURCE_ABSTRACT(WebGui::BrowserView, Gui::MDIView)
|
|
|
|
/**
|
|
* Constructs a BrowserView which is a child of 'parent', with the
|
|
* name 'name'.
|
|
*/
|
|
BrowserView::BrowserView(QWidget* parent)
|
|
: MDIView(nullptr, parent, Qt::WindowFlags())
|
|
, WindowParameter("Browser")
|
|
, isLoading(false)
|
|
{
|
|
#if defined(QTWEBENGINE)
|
|
// Otherwise cause crash on exit, probably due to double deletion
|
|
setAttribute(Qt::WA_DeleteOnClose, false);
|
|
#endif
|
|
|
|
view = new WebView(this);
|
|
setCentralWidget(view);
|
|
view->setAttribute(Qt::WA_OpaquePaintEvent, true);
|
|
|
|
urlWgt = new UrlWidget(this);
|
|
|
|
#ifdef QTWEBKIT
|
|
textSizeMultiplier = 1.0;
|
|
|
|
view->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
|
|
view->page()->setForwardUnsupportedContent(true);
|
|
|
|
// set our custom cookie manager
|
|
FcCookieJar* cookiejar = new FcCookieJar(this);
|
|
view->page()->networkAccessManager()->setCookieJar(cookiejar);
|
|
|
|
// enable local storage so we can store stuff across sessions (startpage)
|
|
QWebSettings* settings = view->settings();
|
|
settings->setAttribute(QWebSettings::LocalStorageEnabled, true);
|
|
settings->setLocalStoragePath(
|
|
QString::fromUtf8((App::Application::getUserAppDataDir() + "webdata").c_str()));
|
|
|
|
// setting background to white
|
|
QPalette palette = view->palette();
|
|
palette.setBrush(QPalette::Base, Qt::white);
|
|
view->page()->setPalette(palette);
|
|
|
|
connect(view->page(),
|
|
SIGNAL(linkHovered(const QString&, const QString&, const QString&)),
|
|
this,
|
|
SLOT(onLinkHovered(const QString&, const QString&, const QString&)));
|
|
connect(view, SIGNAL(linkClicked(const QUrl&)), this, SLOT(urlFilter(const QUrl&)));
|
|
connect(view->page(),
|
|
SIGNAL(downloadRequested(const QNetworkRequest&)),
|
|
this,
|
|
SLOT(onDownloadRequested(const QNetworkRequest&)));
|
|
connect(view->page(),
|
|
SIGNAL(unsupportedContent(QNetworkReply*)),
|
|
this,
|
|
SLOT(onUnsupportedContent(QNetworkReply*)));
|
|
|
|
#else // QTWEBENGINE
|
|
// QWebEngine doesn't support direct access to network
|
|
// nor rendering access
|
|
QWebEngineProfile* profile = view->page()->profile();
|
|
QString basePath =
|
|
QString::fromStdString(App::Application::getUserAppDataDir()) + QLatin1String("webdata/");
|
|
profile->setPersistentStoragePath(basePath + QLatin1String("persistent"));
|
|
profile->setCachePath(basePath + QLatin1String("cache"));
|
|
|
|
interceptLinks = new WebEngineUrlRequestInterceptor(this);
|
|
|
|
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
|
|
profile->setUrlRequestInterceptor(interceptLinks);
|
|
#else
|
|
profile->setRequestInterceptor(interceptLinks);
|
|
#endif
|
|
|
|
view->settings()->setAttribute(QWebEngineSettings::AutoLoadIconsForPage, true);
|
|
view->settings()->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, false);
|
|
|
|
connect(view->page()->profile(),
|
|
&QWebEngineProfile::downloadRequested,
|
|
this,
|
|
&BrowserView::onDownloadRequested);
|
|
connect(view->page(), &QWebEnginePage::iconChanged, this, &BrowserView::setWindowIcon);
|
|
connect(view->page(), &QWebEnginePage::linkHovered, this, &BrowserView::onLinkHovered);
|
|
#endif
|
|
connect(view, &WebView::viewSource, this, &BrowserView::onViewSource);
|
|
connect(view, &WebView::loadStarted, this, &BrowserView::onLoadStarted);
|
|
connect(view, &WebView::loadProgress, this, &BrowserView::onLoadProgress);
|
|
connect(view, &WebView::loadFinished, this, &BrowserView::onLoadFinished);
|
|
connect(view,
|
|
&WebView::openLinkInExternalBrowser,
|
|
this,
|
|
&BrowserView::onOpenLinkInExternalBrowser);
|
|
connect(view, &WebView::openLinkInNewWindow, this, &BrowserView::onOpenLinkInNewWindow);
|
|
connect(view, &WebView::loadStarted, this, &BrowserView::onUpdateBrowserActions);
|
|
connect(view, &WebView::loadFinished, this, &BrowserView::onUpdateBrowserActions);
|
|
}
|
|
|
|
/** Destroys the object and frees any allocated resources */
|
|
BrowserView::~BrowserView()
|
|
{
|
|
#ifdef QTWEBENGINE
|
|
delete interceptLinks; // cleanup not handled implicitly
|
|
#endif
|
|
delete view;
|
|
}
|
|
|
|
void BrowserView::urlFilter(const QUrl& url)
|
|
{
|
|
QString scheme = url.scheme();
|
|
QString host = url.host();
|
|
// QString username = url.userName();
|
|
|
|
// path handling
|
|
QString path = url.path();
|
|
QUrl exturl(url);
|
|
|
|
// query
|
|
QString q;
|
|
if (url.hasQuery()) {
|
|
q = url.query();
|
|
}
|
|
|
|
// QString fragment = url. fragment();
|
|
|
|
#ifdef QTWEBKIT
|
|
if (scheme == QString::fromLatin1("http") || scheme == QString::fromLatin1("https")) {
|
|
load(url);
|
|
}
|
|
#endif
|
|
// Small trick to force opening a link in an external browser: use exthttp or exthttps
|
|
// Write your URL as exthttp://www.example.com
|
|
else if (scheme == QString::fromLatin1("exthttp")) {
|
|
exturl.setScheme(QString::fromLatin1("http"));
|
|
QDesktopServices::openUrl(exturl);
|
|
stop(); // stop qwebengine, should do nothing in qwebkit at this point
|
|
}
|
|
else if (scheme == QString::fromLatin1("exthttps")) {
|
|
exturl.setScheme(QString::fromLatin1("https"));
|
|
QDesktopServices::openUrl(exturl);
|
|
stop(); // stop qwebengine, should do nothing in qwebkit at this point
|
|
}
|
|
// run scripts if not from somewhere else!
|
|
if ((scheme.size() < 2 || scheme == QString::fromLatin1("file")) && host.isEmpty()) {
|
|
QFileInfo fi(path);
|
|
if (fi.exists()) {
|
|
QString ext = fi.completeSuffix();
|
|
if (ext == QString::fromLatin1("py")) {
|
|
stop(); // stop qwebengine, should do nothing in qwebkit at this point
|
|
|
|
try {
|
|
if (!q.isEmpty()) {
|
|
// encapsulate the value in quotes
|
|
q = q.replace(QString::fromLatin1("="), QString::fromLatin1("=\""))
|
|
+ QString::fromLatin1("\"");
|
|
q = q.replace(QString::fromLatin1("%"), QString::fromLatin1("%%"));
|
|
// url queries in the form of somescript.py?key=value, the first key=value
|
|
// will be printed in the py console as key="value"
|
|
Gui::Command::doCommand(Gui::Command::Gui, q.toStdString().c_str());
|
|
}
|
|
// Gui::Command::doCommand(Gui::Command::Gui,"execfile('%s')",(const char*)
|
|
// fi.absoluteFilePath(). toLocal8Bit());
|
|
QString filename = Base::Tools::escapeEncodeFilename(fi.absoluteFilePath());
|
|
// Set flag indicating that this load/restore has been initiated by the user
|
|
// (not by a macro)
|
|
Gui::Application::Instance->setStatus(
|
|
Gui::Application::UserInitiatedOpenDocument,
|
|
true);
|
|
Gui::Command::doCommand(Gui::Command::Gui,
|
|
"with open('%s') as file:\n\texec(file.read())",
|
|
(const char*)filename.toUtf8());
|
|
Gui::Application::Instance->setStatus(
|
|
Gui::Application::UserInitiatedOpenDocument,
|
|
false);
|
|
}
|
|
catch (const Base::Exception& e) {
|
|
QMessageBox::critical(this, tr("Error"), QString::fromUtf8(e.what()));
|
|
}
|
|
|
|
App::Document* doc = BaseView::getAppDocument();
|
|
if (doc && doc->testStatus(App::Document::PartialRestore)) {
|
|
QMessageBox::critical(
|
|
this,
|
|
tr("Error"),
|
|
tr("There were errors while loading the file. Some data might have been "
|
|
"modified or not recovered at all. Look in the report view for more "
|
|
"specific information about the objects involved."));
|
|
}
|
|
|
|
if (doc && doc->testStatus(App::Document::RestoreError)) {
|
|
QMessageBox::critical(
|
|
this,
|
|
tr("Error"),
|
|
tr("There were serious errors while loading the file. Some data might have "
|
|
"been modified or not recovered at all. Saving the project will most "
|
|
"likely result in loss of data."));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
QMessageBox::warning(Gui::getMainWindow(),
|
|
QObject::tr("File does not exist!"),
|
|
fi.absoluteFilePath());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool BrowserView::chckHostAllowed(const QString& host)
|
|
{
|
|
// only check if a local file, later we can do here a dialog to ask the user if
|
|
return host.isEmpty();
|
|
}
|
|
|
|
#ifdef QTWEBENGINE
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
void BrowserView::onDownloadRequested(QWebEngineDownloadItem* request)
|
|
#else
|
|
void BrowserView::onDownloadRequested(QWebEngineDownloadRequest* request)
|
|
#endif
|
|
{
|
|
QUrl url = request->url();
|
|
if (!url.isLocalFile()) {
|
|
request->accept();
|
|
Gui::Dialog::DownloadManager::getInstance()->download(request->url());
|
|
}
|
|
else {
|
|
request->cancel();
|
|
Gui::getMainWindow()->loadUrls(App::GetApplication().getActiveDocument(),
|
|
QList<QUrl>() << url);
|
|
}
|
|
}
|
|
|
|
void BrowserView::setWindowIcon(const QIcon& icon)
|
|
{
|
|
Gui::MDIView::setWindowIcon(icon);
|
|
}
|
|
|
|
void BrowserView::onLinkHovered(const QString& url)
|
|
{
|
|
Gui::getMainWindow()->statusBar()->showMessage(url);
|
|
}
|
|
|
|
void BrowserView::onViewSource(const QUrl& url)
|
|
{
|
|
Q_UNUSED(url);
|
|
view->page()->toHtml([=](const QString& pageSource) {
|
|
QPlainTextEdit* editorWidget = new QPlainTextEdit {};
|
|
App::TextDocument* txtDoc = new App::TextDocument;
|
|
TextDocumentEditorView* textDocView =
|
|
new TextDocumentEditorView {txtDoc, editorWidget, getMainWindow()};
|
|
editorWidget->setReadOnly(true);
|
|
editorWidget->setPlainText(pageSource);
|
|
getMainWindow()->addWindow(textDocView);
|
|
});
|
|
}
|
|
#else
|
|
void BrowserView::onDownloadRequested(const QNetworkRequest& request)
|
|
{
|
|
QUrl url = request.url();
|
|
if (!url.isLocalFile()) {
|
|
Gui::Dialog::DownloadManager::getInstance()->download(request);
|
|
}
|
|
else {
|
|
Gui::getMainWindow()->loadUrls(App::GetApplication().getActiveDocument(),
|
|
QList<QUrl>() << url);
|
|
}
|
|
}
|
|
|
|
void BrowserView::onUnsupportedContent(QNetworkReply* reply)
|
|
{
|
|
// Do not call handleUnsupportedContent() directly otherwise we won't get
|
|
// the metaDataChanged() signal of the reply.
|
|
Gui::Dialog::DownloadManager::getInstance()->download(reply->url());
|
|
// Due to setting the policy QWebPage::DelegateAllLinks the urlFilter()
|
|
// slot is called even when clicking on a downloadable file but the page
|
|
// then fails to load. Thus, we reload the previous url.
|
|
view->reload();
|
|
}
|
|
|
|
void BrowserView::onLinkHovered(const QString& link,
|
|
const QString& title,
|
|
const QString& textContent)
|
|
{
|
|
Q_UNUSED(title)
|
|
Q_UNUSED(textContent)
|
|
QUrl url = QUrl::fromEncoded(link.toLatin1());
|
|
QString str = url.isValid() ? url.toString() : link;
|
|
Gui::getMainWindow()->statusBar()->showMessage(str);
|
|
}
|
|
|
|
void BrowserView::onViewSource(const QUrl& url)
|
|
{
|
|
Q_UNUSED(url);
|
|
if (!view->page() || !view->page()->currentFrame()) {
|
|
return;
|
|
}
|
|
QString pageSource = view->page()->currentFrame()->toHtml();
|
|
QPlainTextEdit* editorWidget = new QPlainTextEdit {};
|
|
App::TextDocument* txtDoc = new App::TextDocument;
|
|
TextDocumentEditorView* textDocView =
|
|
new TextDocumentEditorView {txtDoc, editorWidget, getMainWindow()};
|
|
editorWidget->setReadOnly(true);
|
|
editorWidget->setPlainText(pageSource);
|
|
getMainWindow()->addWindow(textDocView);
|
|
}
|
|
#endif
|
|
|
|
void BrowserView::load(const char* URL)
|
|
{
|
|
QUrl url = QUrl::fromUserInput(QString::fromUtf8(URL));
|
|
load(url);
|
|
}
|
|
|
|
void BrowserView::load(const QUrl& url)
|
|
{
|
|
if (isLoading) {
|
|
stop();
|
|
}
|
|
|
|
urlWgt->setText(url.toString());
|
|
|
|
view->load(url);
|
|
view->setUrl(url);
|
|
if (url.scheme().size() < 2) {
|
|
QString path = url.path();
|
|
QFileInfo fi(path);
|
|
QString name = fi.baseName();
|
|
|
|
setWindowTitle(name);
|
|
}
|
|
else {
|
|
setWindowTitle(url.host());
|
|
}
|
|
|
|
#ifdef QTWEBKIT
|
|
setWindowIcon(QWebSettings::iconForUrl(url));
|
|
#endif
|
|
}
|
|
|
|
void BrowserView::setHtml(const QString& HtmlCode, const QUrl& BaseUrl)
|
|
{
|
|
if (isLoading) {
|
|
stop();
|
|
}
|
|
|
|
view->setHtml(HtmlCode, BaseUrl);
|
|
#ifdef QTWEBKIT
|
|
setWindowIcon(QWebSettings::iconForUrl(BaseUrl));
|
|
#endif
|
|
}
|
|
|
|
void BrowserView::stop()
|
|
{
|
|
view->stop();
|
|
}
|
|
|
|
QUrl BrowserView::url() const
|
|
{
|
|
return view->url();
|
|
}
|
|
|
|
void BrowserView::onLoadStarted()
|
|
{
|
|
QProgressBar* bar = Gui::SequencerBar::instance()->getProgressBar();
|
|
bar->setRange(0, 100);
|
|
bar->show();
|
|
Gui::getMainWindow()->showMessage(tr("Loading %1...").arg(view->url().toString()));
|
|
isLoading = true;
|
|
}
|
|
|
|
void BrowserView::onLoadProgress(int step)
|
|
{
|
|
QProgressBar* bar = Gui::SequencerBar::instance()->getProgressBar();
|
|
bar->setValue(step);
|
|
}
|
|
|
|
void BrowserView::onLoadFinished(bool ok)
|
|
{
|
|
Q_UNUSED(ok)
|
|
|
|
QProgressBar* bar = SequencerBar::instance()->getProgressBar();
|
|
bar->setValue(100);
|
|
bar->hide();
|
|
Gui::MainWindow* win = Gui::getMainWindow();
|
|
if (win) {
|
|
win->showMessage(QString());
|
|
}
|
|
isLoading = false;
|
|
}
|
|
|
|
void BrowserView::onOpenLinkInExternalBrowser(const QUrl& url)
|
|
{
|
|
QDesktopServices::openUrl(url);
|
|
}
|
|
|
|
void BrowserView::onOpenLinkInNewWindow(const QUrl& url)
|
|
{
|
|
BrowserView* view = new WebGui::BrowserView(Gui::getMainWindow());
|
|
view->setWindowTitle(QObject::tr("Browser"));
|
|
view->resize(400, 300);
|
|
view->load(url);
|
|
Gui::getMainWindow()->addWindow(view);
|
|
Gui::getMainWindow()->setActiveWindow(this);
|
|
}
|
|
|
|
void BrowserView::onUpdateBrowserActions()
|
|
{
|
|
CommandManager& mgr = Application::Instance->commandManager();
|
|
std::vector<const char*> cmds = {"Web_BrowserBack",
|
|
"Web_BrowserNext",
|
|
"Web_BrowserRefresh",
|
|
"Web_BrowserStop",
|
|
"Web_BrowserZoomIn",
|
|
"Web_BrowserZoomOut",
|
|
"Web_BrowserSetURL"};
|
|
for (const auto& it : cmds) {
|
|
Gui::Command* cmd = mgr.getCommandByName(it);
|
|
if (cmd) {
|
|
cmd->testActive();
|
|
}
|
|
}
|
|
}
|
|
|
|
void BrowserView::OnChange(Base::Subject<const char*>& rCaller, const char* rcReason)
|
|
{
|
|
Q_UNUSED(rCaller);
|
|
Q_UNUSED(rcReason);
|
|
}
|
|
|
|
/**
|
|
* Runs the action specified by \a pMsg.
|
|
*/
|
|
bool BrowserView::onMsg(const char* pMsg, const char**)
|
|
{
|
|
if (strcmp(pMsg, "Back") == 0) {
|
|
view->back();
|
|
return true;
|
|
}
|
|
else if (strcmp(pMsg, "Next") == 0) {
|
|
view->forward();
|
|
return true;
|
|
}
|
|
else if (strcmp(pMsg, "Refresh") == 0) {
|
|
view->reload();
|
|
return true;
|
|
}
|
|
else if (strcmp(pMsg, "Stop") == 0) {
|
|
stop();
|
|
return true;
|
|
}
|
|
else if (strcmp(pMsg, "ZoomIn") == 0) {
|
|
qreal factor = view->zoomFactor();
|
|
view->setZoomFactor(factor + 0.2);
|
|
return true;
|
|
}
|
|
else if (strcmp(pMsg, "ZoomOut") == 0) {
|
|
qreal factor = view->zoomFactor();
|
|
view->setZoomFactor(factor - 0.2);
|
|
return true;
|
|
}
|
|
else if (strcmp(pMsg, "SetURL") == 0) {
|
|
if (urlWgt->isVisible()) {
|
|
urlWgt->hide();
|
|
}
|
|
else {
|
|
urlWgt->display();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks if the action \a pMsg is available. This is for enabling/disabling
|
|
* the corresponding buttons or menu items for this action.
|
|
*/
|
|
bool BrowserView::onHasMsg(const char* pMsg) const
|
|
{
|
|
if (strcmp(pMsg, "Back") == 0) {
|
|
return view->page()->action(QWebEnginePage::Back)->isEnabled();
|
|
}
|
|
if (strcmp(pMsg, "Next") == 0) {
|
|
return view->page()->action(QWebEnginePage::Forward)->isEnabled();
|
|
}
|
|
if (strcmp(pMsg, "Refresh") == 0) {
|
|
return !isLoading;
|
|
}
|
|
if (strcmp(pMsg, "Stop") == 0) {
|
|
return isLoading;
|
|
}
|
|
if (strcmp(pMsg, "ZoomIn") == 0) {
|
|
return true;
|
|
}
|
|
if (strcmp(pMsg, "ZoomOut") == 0) {
|
|
return true;
|
|
}
|
|
if (strcmp(pMsg, "SetURL") == 0) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/** Checking on close state. */
|
|
bool BrowserView::canClose()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
PyObject* BrowserView::getPyObject()
|
|
{
|
|
static bool init = false;
|
|
if (!init) {
|
|
init = true;
|
|
BrowserViewPy::init_type();
|
|
}
|
|
|
|
return new BrowserViewPy(this);
|
|
}
|
|
#include "moc_BrowserView.cpp"
|