mirror of
https://gitee.com/vnotex/vnote.git
synced 2025-07-05 13:59:52 +08:00
484 lines
14 KiB
C++
484 lines
14 KiB
C++
#include "vwebview.h"
|
|
|
|
#include <QMenu>
|
|
#include <QPoint>
|
|
#include <QContextMenuEvent>
|
|
#include <QWebEnginePage>
|
|
#include <QAction>
|
|
#include <QList>
|
|
#include <QClipboard>
|
|
#include <QMimeData>
|
|
#include <QApplication>
|
|
#include <QImage>
|
|
#include <QFileInfo>
|
|
#include "vfile.h"
|
|
#include "utils/vclipboardutils.h"
|
|
#include "utils/viconutils.h"
|
|
#include "vconfigmanager.h"
|
|
#include "utils/vwebutils.h"
|
|
#include "utils/vutils.h"
|
|
|
|
extern VConfigManager *g_config;
|
|
|
|
extern VWebUtils *g_webUtils;
|
|
|
|
// We set the property of the clipboard to mark that the URL copied in the
|
|
// clipboard has been altered.
|
|
static const QString c_ClipboardPropertyMark = "CopiedImageURLAltered";
|
|
|
|
VWebView::VWebView(VFile *p_file, QWidget *p_parent)
|
|
: QWebEngineView(p_parent),
|
|
m_file(p_file),
|
|
m_copyImageUrlActionHooked(false),
|
|
m_afterCopyImage(false),
|
|
m_inPreview(false)
|
|
{
|
|
setAcceptDrops(false);
|
|
|
|
connect(QApplication::clipboard(), &QClipboard::changed,
|
|
this, &VWebView::handleClipboardChanged);
|
|
}
|
|
|
|
void VWebView::contextMenuEvent(QContextMenuEvent *p_event)
|
|
{
|
|
QMenu *menu = page()->createStandardContextMenu();
|
|
menu->setToolTipsVisible(true);
|
|
|
|
const QList<QAction *> actions = menu->actions();
|
|
QAction *firstAction = actions.isEmpty() ? NULL : actions[0];
|
|
|
|
bool selection = hasSelection();
|
|
|
|
#if defined(Q_OS_WIN)
|
|
if (!m_copyImageUrlActionHooked) {
|
|
// "Copy Image URL" action will put the encoded URL to the clipboard as text
|
|
// and the URL as URLs. If the URL contains Chinese, OneNote or Word could not
|
|
// recognize it.
|
|
// We need to change it to only-space-encoded text.
|
|
QAction *copyImageUrlAct = pageAction(QWebEnginePage::CopyImageUrlToClipboard);
|
|
if (actions.contains(copyImageUrlAct)) {
|
|
connect(copyImageUrlAct, &QAction::triggered,
|
|
this, &VWebView::handleCopyImageUrlAction);
|
|
|
|
m_copyImageUrlActionHooked = true;
|
|
|
|
qDebug() << "hooked CopyImageUrl action" << copyImageUrlAct;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!selection) {
|
|
if (m_inPreview) {
|
|
QAction *expandAct = new QAction(tr("Expand/Restore Preview Area"), menu);
|
|
VUtils::fixTextWithCaptainShortcut(expandAct, "ExpandLivePreview");
|
|
connect(expandAct, &QAction::triggered,
|
|
this, &VWebView::requestExpandRestorePreviewArea);
|
|
menu->insertAction(firstAction, expandAct);
|
|
|
|
initPreviewTunnelMenu(firstAction, menu);
|
|
|
|
if (firstAction) {
|
|
menu->insertSeparator(firstAction);
|
|
}
|
|
} else {
|
|
if (m_file && m_file->isModifiable()) {
|
|
QAction *editAct= new QAction(VIconUtils::menuIcon(":/resources/icons/edit_note.svg"),
|
|
tr("&Edit"),
|
|
menu);
|
|
editAct->setToolTip(tr("Edit current note"));
|
|
connect(editAct, &QAction::triggered,
|
|
this, &VWebView::handleEditAction);
|
|
menu->insertAction(firstAction, editAct);
|
|
if (firstAction) {
|
|
menu->insertSeparator(firstAction);
|
|
}
|
|
}
|
|
|
|
QAction *savePageAct = new QAction(QWebEnginePage::tr("Save &Page"), menu);
|
|
connect(savePageAct, &QAction::triggered,
|
|
this, &VWebView::requestSavePage);
|
|
menu->addAction(savePageAct);
|
|
}
|
|
}
|
|
|
|
// Add Copy As menu.
|
|
{
|
|
QAction *copyAct = pageAction(QWebEnginePage::Copy);
|
|
if (actions.contains(copyAct) && !m_inPreview) {
|
|
initCopyAsMenu(copyAct, menu);
|
|
}
|
|
}
|
|
|
|
// We need to replace the "Copy Image" action:
|
|
// - the default one use the fully-encoded URL to fetch the image while
|
|
// Windows seems to not recognize it.
|
|
// - We need to remove the html to let it be recognized by some web pages.
|
|
QAction *defaultCopyImageAct = pageAction(QWebEnginePage::CopyImageToClipboard);
|
|
if (actions.contains(defaultCopyImageAct)) {
|
|
QAction *copyImageAct = new QAction(defaultCopyImageAct->text(), menu);
|
|
copyImageAct->setToolTip(defaultCopyImageAct->toolTip());
|
|
connect(copyImageAct, &QAction::triggered,
|
|
this, &VWebView::copyImage);
|
|
menu->insertAction(defaultCopyImageAct, copyImageAct);
|
|
defaultCopyImageAct->setVisible(false);
|
|
}
|
|
|
|
// Add Copy All As menu.
|
|
if (!m_inPreview) {
|
|
initCopyAllAsMenu(menu);
|
|
}
|
|
|
|
hideUnusedActions(menu);
|
|
|
|
p_event->accept();
|
|
|
|
bool valid = false;
|
|
for (auto act : menu->actions()) {
|
|
if (act->isVisible()) {
|
|
valid = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (valid) {
|
|
menu->exec(p_event->globalPos());
|
|
}
|
|
|
|
delete menu;
|
|
}
|
|
|
|
void VWebView::handleEditAction()
|
|
{
|
|
emit editNote();
|
|
}
|
|
|
|
void VWebView::copyImage()
|
|
{
|
|
#if defined(Q_OS_WIN)
|
|
Q_ASSERT(m_copyImageUrlActionHooked);
|
|
// triggerPageAction(QWebEnginePage::CopyImageUrlToClipboard) will not really
|
|
// trigger the corresponding action. It just do the stuff directly.
|
|
QAction *copyImageUrlAct = pageAction(QWebEnginePage::CopyImageUrlToClipboard);
|
|
copyImageUrlAct->trigger();
|
|
|
|
QCoreApplication::processEvents();
|
|
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
if (clipboard->property(c_ClipboardPropertyMark.toLatin1()).toBool()) {
|
|
const QMimeData *mimeData = clipboard->mimeData();
|
|
QString imgPath;
|
|
if (mimeData->hasUrls()) {
|
|
QList<QUrl> urls = mimeData->urls();
|
|
if (!urls.isEmpty() && urls[0].isLocalFile()) {
|
|
imgPath = urls[0].toLocalFile();
|
|
}
|
|
}
|
|
|
|
if (!imgPath.isEmpty()) {
|
|
QImage img = VUtils::imageFromFile(imgPath);
|
|
if (!img.isNull()) {
|
|
m_afterCopyImage = false;
|
|
VClipboardUtils::setImageToClipboard(clipboard, img, QClipboard::Clipboard);
|
|
qDebug() << "clipboard copy image via URL" << imgPath;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
m_afterCopyImage = true;
|
|
|
|
// Fall back.
|
|
triggerPageAction(QWebEnginePage::CopyImageToClipboard);
|
|
}
|
|
|
|
void VWebView::handleCopyImageUrlAction()
|
|
{
|
|
// To avoid failure of setting clipboard mime data.
|
|
QCoreApplication::processEvents();
|
|
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
const QMimeData *mimeData = clipboard->mimeData();
|
|
clipboard->setProperty(c_ClipboardPropertyMark.toLatin1(), false);
|
|
if (clipboard->ownsClipboard()
|
|
&& mimeData->hasText()
|
|
&& mimeData->hasUrls()) {
|
|
QString text = mimeData->text();
|
|
QList<QUrl> urls = mimeData->urls();
|
|
if (urls.size() == 1
|
|
&& urls[0].isLocalFile()
|
|
&& urls[0].toEncoded() == text) {
|
|
QString spaceOnlyText = urls[0].toString(QUrl::EncodeSpaces);
|
|
if (spaceOnlyText != text) {
|
|
// Set new mime data.
|
|
QMimeData *data = new QMimeData();
|
|
data->setUrls(urls);
|
|
data->setText(spaceOnlyText);
|
|
VClipboardUtils::setMimeDataToClipboard(clipboard, data, QClipboard::Clipboard);
|
|
|
|
clipboard->setProperty(c_ClipboardPropertyMark.toLatin1(), true);
|
|
qDebug() << "clipboard copy image URL altered" << spaceOnlyText;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void VWebView::hideUnusedActions(QMenu *p_menu)
|
|
{
|
|
QList<QAction *> unusedActions;
|
|
|
|
// QWebEnginePage uses different actions of Back/Forward/Reload.
|
|
// [Woboq](https://code.woboq.org/qt5/qtwebengine/src/webenginewidgets/api/qwebenginepage.cpp.html#1652)
|
|
// We tell these three actions by name.
|
|
const QStringList actionNames({QWebEnginePage::tr("&Back"),
|
|
QWebEnginePage::tr("&Forward"),
|
|
QWebEnginePage::tr("&Reload")});
|
|
|
|
const QList<QAction *> actions = p_menu->actions();
|
|
for (auto it : actions) {
|
|
if (actionNames.contains(it->text())) {
|
|
unusedActions.append(it);
|
|
}
|
|
}
|
|
|
|
// ViewSource.
|
|
QAction *act = pageAction(QWebEnginePage::ViewSource);
|
|
unusedActions.append(act);
|
|
|
|
// DownloadImageToDisk.
|
|
act = pageAction(QWebEnginePage::DownloadImageToDisk);
|
|
unusedActions.append(act);
|
|
|
|
// DownloadLinkToDisk.
|
|
act = pageAction(QWebEnginePage::DownloadLinkToDisk);
|
|
unusedActions.append(act);
|
|
|
|
for (auto it : unusedActions) {
|
|
if (it) {
|
|
it->setVisible(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool VWebView::removeStyles(QString &p_html)
|
|
{
|
|
bool changed = false;
|
|
|
|
const QStringList &styles = g_config->getStylesToRemoveWhenCopied();
|
|
if (styles.isEmpty()) {
|
|
return changed;
|
|
}
|
|
|
|
QRegExp tagReg("(<[^>]+\\sstyle=[^>]*>)");
|
|
|
|
int pos = 0;
|
|
while (pos < p_html.size()) {
|
|
int idx = p_html.indexOf(tagReg, pos);
|
|
if (idx == -1) {
|
|
break;
|
|
}
|
|
|
|
QString styleStr = tagReg.cap(1);
|
|
QString alteredStyleStr = styleStr;
|
|
|
|
QString regPatt("(\\s|\")%1:[^;]+;");
|
|
for (auto const & sty : styles) {
|
|
QRegExp reg(regPatt.arg(sty));
|
|
alteredStyleStr.replace(reg, "\\1");
|
|
}
|
|
|
|
pos = idx + tagReg.matchedLength();
|
|
if (styleStr != alteredStyleStr) {
|
|
pos = pos + alteredStyleStr.size() - styleStr.size();
|
|
p_html.replace(idx, tagReg.matchedLength(), alteredStyleStr);
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
void VWebView::handleClipboardChanged(QClipboard::Mode p_mode)
|
|
{
|
|
if (!hasFocus()
|
|
|| p_mode != QClipboard::Clipboard) {
|
|
return;
|
|
}
|
|
|
|
QClipboard *clipboard = QApplication::clipboard();
|
|
if (!clipboard->ownsClipboard()) {
|
|
return;
|
|
}
|
|
|
|
const QMimeData *mimeData = clipboard->mimeData();
|
|
|
|
QString copyTarget = m_copyTarget;
|
|
m_copyTarget.clear();
|
|
|
|
if (m_afterCopyImage) {
|
|
m_afterCopyImage = false;
|
|
removeHtmlFromImageData(clipboard, mimeData);
|
|
} else {
|
|
alterHtmlMimeData(clipboard, mimeData, copyTarget);
|
|
}
|
|
}
|
|
|
|
void VWebView::alterHtmlMimeData(QClipboard *p_clipboard,
|
|
const QMimeData *p_mimeData,
|
|
const QString &p_copyTarget)
|
|
{
|
|
if (!p_mimeData->hasHtml()
|
|
|| p_mimeData->hasImage()
|
|
|| p_copyTarget.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QString html = p_mimeData->html();
|
|
if (!g_webUtils->alterHtmlAsTarget(url(), html, p_copyTarget)) {
|
|
return;
|
|
}
|
|
|
|
// Set new mime data.
|
|
QMimeData *data = VClipboardUtils::cloneMimeData(p_mimeData);
|
|
data->setHtml(html);
|
|
|
|
VClipboardUtils::setMimeDataToClipboard(p_clipboard, data, QClipboard::Clipboard);
|
|
qDebug() << "altered clipboard's Html";
|
|
}
|
|
|
|
void VWebView::removeHtmlFromImageData(QClipboard *p_clipboard,
|
|
const QMimeData *p_mimeData)
|
|
{
|
|
if (!p_mimeData->hasImage()) {
|
|
return;
|
|
}
|
|
|
|
if (p_mimeData->hasHtml()) {
|
|
qDebug() << "remove html from image data" << p_mimeData->html();
|
|
QMimeData *data = new QMimeData();
|
|
data->setImageData(p_mimeData->imageData());
|
|
VClipboardUtils::setMimeDataToClipboard(p_clipboard, data, QClipboard::Clipboard);
|
|
}
|
|
}
|
|
|
|
void VWebView::initCopyAsMenu(QAction *p_after, QMenu *p_menu)
|
|
{
|
|
QStringList targets = g_webUtils->getCopyTargetsName();
|
|
if (targets.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QMenu *subMenu = new QMenu(tr("Copy As"), p_menu);
|
|
subMenu->setToolTipsVisible(true);
|
|
for (auto const & target : targets) {
|
|
QAction *act = new QAction(target, subMenu);
|
|
act->setData(target);
|
|
act->setToolTip(tr("Copy selected content using rules specified by target %1").arg(target));
|
|
|
|
subMenu->addAction(act);
|
|
}
|
|
|
|
connect(subMenu, &QMenu::triggered,
|
|
this, &VWebView::handleCopyAsAction);
|
|
|
|
QAction *menuAct = p_menu->insertMenu(p_after, subMenu);
|
|
p_menu->removeAction(p_after);
|
|
p_menu->insertAction(menuAct, p_after);
|
|
}
|
|
|
|
void VWebView::handleCopyAsAction(QAction *p_act)
|
|
{
|
|
if (!p_act) {
|
|
return;
|
|
}
|
|
|
|
m_copyTarget = p_act->data().toString();
|
|
|
|
triggerPageAction(QWebEnginePage::Copy);
|
|
}
|
|
|
|
void VWebView::initCopyAllAsMenu(QMenu *p_menu)
|
|
{
|
|
QStringList targets = g_webUtils->getCopyTargetsName();
|
|
if (targets.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QMenu *subMenu = new QMenu(tr("Copy All As"), p_menu);
|
|
subMenu->setToolTipsVisible(true);
|
|
for (auto const & target : targets) {
|
|
QAction *act = new QAction(target, subMenu);
|
|
act->setData(target);
|
|
act->setToolTip(tr("Copy all content using rules specified by target %1").arg(target));
|
|
|
|
subMenu->addAction(act);
|
|
}
|
|
|
|
connect(subMenu, &QMenu::triggered,
|
|
this, &VWebView::handleCopyAllAsAction);
|
|
|
|
p_menu->addSeparator();
|
|
p_menu->addMenu(subMenu);
|
|
}
|
|
|
|
void VWebView::handleCopyAllAsAction(QAction *p_act)
|
|
{
|
|
if (!p_act) {
|
|
return;
|
|
}
|
|
|
|
triggerPageAction(QWebEnginePage::SelectAll);
|
|
|
|
m_copyTarget = p_act->data().toString();
|
|
|
|
triggerPageAction(QWebEnginePage::Copy);
|
|
|
|
triggerPageAction(QWebEnginePage::Unselect);
|
|
}
|
|
|
|
void VWebView::initPreviewTunnelMenu(QAction *p_before, QMenu *p_menu)
|
|
{
|
|
QMenu *subMenu = new QMenu(tr("Live Preview Tunnel"), p_menu);
|
|
|
|
int config = g_config->getSmartLivePreview();
|
|
|
|
QActionGroup *ag = new QActionGroup(subMenu);
|
|
QAction *act = ag->addAction(tr("Disabled"));
|
|
act->setData(SmartLivePreview::Disabled);
|
|
act->setCheckable(true);
|
|
if (act->data().toInt() == config) {
|
|
act->setChecked(true);
|
|
}
|
|
|
|
act = ag->addAction(tr("Editor -> Preview"));
|
|
act->setData(SmartLivePreview::EditorToWeb);
|
|
act->setCheckable(true);
|
|
if (act->data().toInt() == config) {
|
|
act->setChecked(true);
|
|
}
|
|
|
|
act = ag->addAction(tr("Preview -> Editor"));
|
|
act->setData(SmartLivePreview::WebToEditor);
|
|
act->setCheckable(true);
|
|
if (act->data().toInt() == config) {
|
|
act->setChecked(true);
|
|
}
|
|
|
|
act = ag->addAction(tr("Bidirectional"));
|
|
act->setData(SmartLivePreview::EditorToWeb | SmartLivePreview::WebToEditor);
|
|
act->setCheckable(true);
|
|
if (act->data().toInt() == config) {
|
|
act->setChecked(true);
|
|
}
|
|
|
|
connect(ag, &QActionGroup::triggered,
|
|
this, [](QAction *p_act) {
|
|
int data = p_act->data().toInt();
|
|
g_config->setSmartLivePreview(data);
|
|
});
|
|
|
|
subMenu->addActions(ag->actions());
|
|
|
|
p_menu->insertMenu(p_before, subMenu);
|
|
}
|