Compare commits

...

19 Commits

Author SHA1 Message Date
Jacopo De Simoi d7593e0023 Tarmak-3 marker keys 3 years ago
Jacopo De Simoi 5d16bea0d4 Tarmak-2 marker keys 3 years ago
Jacopo De Simoi 71a731ce8a Change markers 3 years ago
Jacopo De Simoi ceeb8ad2e6 Do not trigger markers with modifiers 3 years ago
Jacopo De Simoi 8824ac3a1e Steal keypresses when markers are active. 3 years ago
Jacopo De Simoi fd84f45f77 Lunarize the markers 3 years ago
Jacopo De Simoi 9f8133299a Reset the label when the markers disappear 3 years ago
Jacopo De Simoi 9608654b89 Move marker handling to keypress 3 years ago
Jacopo De Simoi 873e7ac5ee Put in a working implementation 3 years ago
Jacopo De Simoi fdfc5a0f04 Attempt to reverse the string order 3 years ago
Jacopo De Simoi 2c070b0b0f Clear link markers when scrolling or resizing 3 years ago
Jacopo De Simoi 0b982ab788 Use show/hide in methods 3 years ago
Jacopo De Simoi 58ab781083 Add method to cleanup link markers 3 years ago
Jacopo De Simoi e5b1ef6559 Load markers and test 3 years ago
Jacopo De Simoi b1fd53de47 Hardcode symbols for now 3 years ago
Jacopo De Simoi 59e15bc539 Add marker.js from eaf 3 years ago
Jacopo De Simoi d7ceeb8ec5 Back/Forward with c/v 3 years ago
Jacopo De Simoi 1bce476a2b Zoom in with = too 3 years ago
Jacopo De Simoi 05288a5f93 Zoom in/out with Plus and Minus 3 years ago
  1. 2
      src/lib/app/browserwindow.cpp
  2. 1
      src/lib/data/html.qrc
  3. 287
      src/lib/data/html/marker.js
  4. 21
      src/lib/webengine/webpage.cpp
  5. 6
      src/lib/webengine/webpage.h
  6. 52
      src/lib/webengine/webview.cpp
  7. 2
      src/lib/webengine/webview.h

@ -1270,6 +1270,7 @@ void BrowserWindow::keyPressEvent(QKeyEvent* event)
switch (event->key()) {
case Qt::Key_Back:
case Qt::Key_C:
if (view) {
view->back();
event->accept();
@ -1277,6 +1278,7 @@ void BrowserWindow::keyPressEvent(QKeyEvent* event)
break;
case Qt::Key_Forward:
case Qt::Key_V:
if (view) {
view->forward();
event->accept();

@ -11,6 +11,7 @@
<file>html/config.html</file>
<file>html/restore.html</file>
<file>html/restore.user.js</file>
<file>html/marker.js</file>
<file>html/tabcrash.html</file>
<file>html/close.svg</file>
<file>html/configure.svg</file>

@ -0,0 +1,287 @@
(function(_) {
let self;
_.Marker = self = {};
function getVisibleElements(filter) {
let all = Array.from(document.documentElement.getElementsByTagName("*"));
let visibleElements = [];
for (let i = 0; i < all.length; i++) {
let e = all[i];
// include elements in a shadowRoot.
if (e.shadowRoot) {
let cc = e.shadowRoot.querySelectorAll('*');
for (let j = 0; j < cc.length; j++) {
all.push(cc[j]);
}
}
let rect = e.getBoundingClientRect();
if ( (rect.top <= window.innerHeight) && (rect.bottom >= 0)
&& (rect.left <= window.innerWidth) && (rect.right >= 0)
&& rect.height > 0
&& getComputedStyle(e).visibility !== 'hidden'
) {
filter(e, visibleElements);
}
}
return visibleElements;
}
function moveCursorToEnd(el) {
if (typeof el.selectionStart == "number") {
el.selectionStart = el.selectionEnd = el.value.length;
} else if (typeof el.createTextRange != "undefined") {
el.focus();
let range = el.createTextRange();
range.collapse(false);
range.select();
}
}
function cssSelector(el) {
let path = [], parent;
while (parent = el.parentNode) {
path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el)+1})`);
el = parent;
}
return `${path.join(' > ')}`.toLowerCase();
}
function getCoords(node){
if (node.getBoundingClientRect){
let rect = node.getBoundingClientRect();
return [ rect.top, rect.left, rect.right, rect.bottom, cssSelector(node) ];
}
return getCoords(node.parentNode); // TextNode not define getBoundingClientRect
}
function isElementOnScreen(rect){
let clientHeight = document.documentElement.clientHeight;
let clientWidth = document.documentElement.clientWidth;
return (rect[0] >= 0 && rect[0] <= clientHeight &&
rect[1] >= 0 && rect[1] <= clientWidth &&
rect[2] != 0 && rect[3] != 0);
}
function isElementOnTop(element, rect){
let topElement = document.elementFromPoint((rect[1] + rect[2])/2, (rect[0] + rect[3])/2);
return topElement != undefined && (element.isSameNode(topElement) || element.contains(topElement) || topElement.contains(element));
}
function hasCopy(validRects, rect){
for(let i = 0; i < validRects.length; i++) {
let each = validRects[i];
if(each[0] === rect[0] && each[1] === rect[1]){
return true;
}
}
return false;
}
function addElementToRects(validRects, elements){
let rect;
for(let i = 0; i < elements.length; i++) {
rect = getCoords(elements[i]);
if(!hasCopy(validRects, rect) &&
isElementOnScreen(rect) &&
isElementOnTop(elements[i], rect)){
validRects.push(rect);
}
}
}
function cAdd1(keyCounter, index, maxDigit){
if(keyCounter[index] + 1 == maxDigit){
keyCounter[index] = 0;
cAdd1(keyCounter, index + 1, maxDigit);
} else {
keyCounter[index]++;
}
}
function generateKeys(markerContainer) {
let lettersString = "ARSTDHNELFI";
let letters = lettersString.split("");
let nodeNum = markerContainer.children.length;
let keyLen = nodeNum == 1 ? 1 : Math.ceil(Math.log(nodeNum)/Math.log(letters.length));
let keyCounter = [];
for(let i = 0; i < keyLen; i++) keyCounter[i] = 0;
for(let l = 0; l < nodeNum; l++) {
let keyStr = '';
for(let k = 0; k < keyLen; k++) {
let key = letters[keyCounter[k]];
keyStr = key + keyStr;
cAdd1(keyCounter, 0, letters.length);
}
let mark = document.createElement('span');
mark.setAttribute('class', 'eaf-mark');
mark.textContent = keyStr;
markerContainer.children[l].appendChild(mark);
markerContainer.children[l].id = keyStr;
console.log("added:"+keyStr);
}
}
self.generateMarker = (selectors) => {
let style = document.createElement('style');
document.head.appendChild(style);
style.type = 'text/css';
style.setAttribute('class', 'eaf-style');
style.appendChild(document.createTextNode('\
.eaf-mark {\
background: none;\
border: none;\
bottom: auto;\
box-shadow: none;\
color: #969696!important;\
cursor: auto;\
display: inline;\
float: none;\
font-size: inherit;\
font-variant: normal;\
font-weight: bold;\
height: auto;\
left: auto;\
letter-spacing: 0;\
line-height: 100%;\
margin: 0;\
max-height: none;\
max-width: none;\
min-height: 0;\
min-width: 0;\
opacity: 1;\
padding: 0;\
position: static;\
right: auto;\
text-align: left;\
text-decoration: none;\
text-indent: 0;\
text-shadow: none;\
text-transform: none;\
top: auto;\
vertical-align: baseline;\
white-space: normal;\
width: auto;\
z-index: 100000;\
}'));
style.appendChild(document.createTextNode('\
.eaf-marker {\
position: fixed;\
display: block;\
white-space: nowrap;\
overflow: hidden;\
font-size: 14px;\
background: #F6F6F6;\
padding-left: 6px;\
padding-right: 6px;\
border: 1px solid #262626;\
border-radius: 1px;\
z-index: 100000;\
}'));
let validRects = [];
if (typeof(selectors)=="function"){
addElementToRects(validRects, selectors());
}else if (typeof(selectors) == "string"){
selectors = selectors.split(",");
selectors.forEach((s)=>addElementToRects(validRects, document.querySelectorAll(s.trim())));
}
let body = document.querySelector('body');
let markerContainer = document.createElement('div');
markerContainer.setAttribute('class', 'eaf-marker-container');
body.insertAdjacentElement('afterend', markerContainer);
for(let i = 0; i < validRects.length; i++) {
let marker = document.createElement('div');
marker.setAttribute('class', 'eaf-marker');
marker.setAttribute('style', 'left: ' + validRects[i][1] + 'px; top: ' + validRects[i][0] + 'px;');
marker.setAttribute('pointed-link', validRects[i][4]);
markerContainer.appendChild(marker);
}
generateKeys(markerContainer);
}
self.gotoMarker = (key, callback)=>{
let markers = document.querySelectorAll('.eaf-marker');
let match;
for(let i = 0; i < markers.length; i++) {
if(markers[i].id === key.toUpperCase()) {
match = markers[i];
break;
}
}
if (match !== undefined && callback !== undefined){
let selectors = match.getAttribute('pointed-link');
let node = document.querySelector(selectors);
return callback(node);
} else
return "";
}
// this is callback function which call by core/browser.py get_mark_link
self.getMarkerLink = (node) => {
if(node.nodeName.toLowerCase() === 'select'){
node.focus();
}else if(node.nodeName.toLowerCase() === 'input' ||
node.nodeName.toLowerCase() === 'textarea') {
if((node.getAttribute('type') === 'submit') ||
(node.getAttribute('type') === 'checkbox')){
node.click();
} else {
node.focus(); // focus
node.click(); // show blink cursor
moveCursorToEnd(node); // move cursor to the end of line after focus.
}
} else if((node.nodeName.toLowerCase() === 'button') || // normal button
(node.nodeName.toLowerCase() === 'summary') || // summary button
(node.hasAttribute('aria-haspopup')) || // menu button
(node.getAttribute('role') === 'button') || // role="button" buttons
(node.hasAttribute('ng-click')) || // ng-click buttons
(node.classList.contains('btn')) || // class="btn" buttons
(node.classList.contains('gap')) || // class="gap" links
(node.getAttribute('href') === '') || // special href button
(node.getAttribute('href') === '#')){ // special href # button
node.click();
} else if(node.nodeName.toLowerCase() === 'p'||
node.nodeName.toLowerCase() === 'span') { // select text section
window.getSelection().selectAllChildren(node);
} else if(node.href != undefined && node.href != '' && node.getAttribute('href') != ''){
return node.href;
} else if(node.nodeName.toLowerCase() === 'a') {
node.click(); // most general a tag without href
}
return "";
}
self.generateTextNodeMarker = () => {
let elements = getVisibleElements(function(e, v) {
let aa = e.childNodes;
for (let i = 0, len = aa.length; i < len; i++) {
if (aa[i].nodeType == Node.TEXT_NODE && aa[i].data.length > 0) {
v.push(e);
break;
}
}
});
elements = Array.prototype.concat.apply([], elements.map(function (e) {
let aa = e.childNodes;
let bb = [];
for (let i = 0, len = aa.length; i < len; i++) {
if (aa[i].nodeType == Node.TEXT_NODE && aa[i].data.trim().length > 1) {
bb.push(aa[i]);
}
}
return bb;
}));
return elements;
}
})(window);

@ -98,6 +98,10 @@ WebPage::WebPage(QObject* parent)
mApp->networkManager()->proxyAuthentication(proxyHost, auth, view());
});
connect(this, &QWebEnginePage::scrollPositionChanged, this, &WebPage::hideLinkMarkers);
connect(this, &QWebEnginePage::contentsSizeChanged, this, &WebPage::hideLinkMarkers);
// Workaround QWebEnginePage not scrolling to anchors when opened in background tab
m_contentsResizedConnection = connect(this, &QWebEnginePage::contentsSizeChanged, this, [this]() {
const QString fragment = url().fragment();
@ -295,6 +299,9 @@ void WebPage::finished()
// AutoFill
m_autoFillUsernames = mApp->autoFill()->completePage(this, url());
// inject marker.js
runJavaScript(QzTools::readAllFileContents(QSL(":html/marker.js")));
}
void WebPage::watchedFileChanged(const QString &file)
@ -687,6 +694,20 @@ void WebPage::javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, cons
std::cout << qPrintable(sourceID) << ":" << lineNumber << " " << qPrintable(message);
}
void WebPage::followLinkMarker(QString label) {
runJavaScript("Marker.gotoMarker('"+label+"',Marker.getMarkerLink)",[this](const QVariant& v) { QString url=v.toString(); if (!url.isEmpty()) load(v.toUrl());});
}
void WebPage::showLinkMarkers() {
runJavaScript("Marker.generateMarker('a, input, button, [class*=\"btn\"], [aria-haspopup], [role=\"button\"], textarea, select, summary, [class=\"gap\"], [ng-click]')");
}
void WebPage::hideLinkMarkers() {
runJavaScript("document.querySelector('.eaf-marker-container').remove();");
runJavaScript("document.querySelector('.eaf-style').remove();");
emit linkMarkersCleared();
}
QWebEnginePage* WebPage::createWindow(QWebEnginePage::WebWindowType type)
{
auto *tView = qobject_cast<TabbedWebView*>(view());

@ -77,14 +77,20 @@ public:
static void addSupportedScheme(const QString &scheme);
static void removeSupportedScheme(const QString &scheme);
void showLinkMarkers();
void followLinkMarker(QString);
void markerCallback(const QVariant &);
Q_SIGNALS:
void privacyChanged(bool status);
void printRequested();
void navigationRequestAccepted(const QUrl &url, QWebEnginePage::NavigationType type, bool isMainFrame);
void linkMarkersCleared();
protected Q_SLOTS:
void progress(int prog);
void finished();
void hideLinkMarkers();
private Q_SLOTS:
void urlChanged(const QUrl &url);

@ -60,6 +60,8 @@ WebView::WebView(QWidget* parent)
, m_backgroundActivity(false)
, m_page(0)
, m_firstLoad(false)
, m_followLink(false)
, m_currentLinkMarkerLabel()
{
connect(this, &QWebEngineView::loadStarted, this, &WebView::slotLoadStarted);
connect(this, &QWebEngineView::loadProgress, this, &WebView::slotLoadProgress);
@ -161,6 +163,8 @@ void WebView::setPage(WebPage *page)
connect(m_page, &WebPage::privacyChanged, this, &WebView::privacyChanged);
connect(m_page, &WebPage::printRequested, this, &WebView::printPage);
connect(m_page, &WebPage::linkMarkersCleared,
[=]() {m_followLink = false;m_currentLinkMarkerLabel=QString(); });
// Set default zoom level
zoomReset();
@ -1157,29 +1161,49 @@ void WebView::_keyPressEvent(QKeyEvent *event)
return;
}
if (m_followLink) {
// We are following links; therefore we need to process the
// key
switch (event->key()) {
case Qt::Key_ZoomIn:
zoomIn();
case Qt::Key_Enter:
case Qt::Key_Return:
// Accept current label
page()->followLinkMarker(m_currentLinkMarkerLabel);
case Qt::Key_Escape:
page()->hideLinkMarkers();
m_currentLinkMarkerLabel=QString();
m_followLink = false;
event->accept();
break;
case Qt::Key_ZoomOut:
zoomOut();
default:
m_currentLinkMarkerLabel += event->text();
qDebug() << "process letter" << m_currentLinkMarkerLabel;
event->accept();
break;
}
} else {
switch (event->key()) {
case Qt::Key_T:
if (!event->modifiers()) {
page()->showLinkMarkers();
qDebug() << "markers activated";
m_followLink = true;
event->accept();
}
break;
case Qt::Key_ZoomIn:
case Qt::Key_Plus:
if (event->modifiers() & Qt::ControlModifier) {
case Qt::Key_Equal:
zoomIn();
event->accept();
}
break;
case Qt::Key_ZoomOut:
case Qt::Key_Minus:
if (event->modifiers() & Qt::ControlModifier) {
zoomOut();
event->accept();
}
break;
case Qt::Key_0:
@ -1199,6 +1223,7 @@ void WebView::_keyPressEvent(QKeyEvent *event)
default:
break;
}
}
}
void WebView::_keyReleaseEvent(QKeyEvent *event)
@ -1214,12 +1239,12 @@ void WebView::_keyReleaseEvent(QKeyEvent *event)
event->accept();
}
break;
default:
break;
}
}
void WebView::_contextMenuEvent(QContextMenuEvent *event)
{
Q_UNUSED(event)
@ -1258,6 +1283,9 @@ void WebView::loadRequest(const LoadRequest &req)
bool WebView::eventFilter(QObject *obj, QEvent *event)
{
//the first time the event is called for the rwhvqt,
// then it is called for the parent object
// Keyboard events are sent to parent widget
if (obj == this && event->type() == QEvent::ParentChange && parentWidget()) {
parentWidget()->installEventFilter(this);
@ -1307,7 +1335,9 @@ bool WebView::eventFilter(QObject *obj, QEvent *event)
}
}
if (obj == parentWidget()) {
if (obj == parentWidget()
|| ((obj == m_rwhvqt) && (m_followLink)))
{
switch (event->type()) {
case QEvent::KeyPress:
HANDLE_EVENT(_keyPressEvent, QKeyEvent);
@ -1342,9 +1372,7 @@ bool WebView::eventFilter(QObject *obj, QEvent *event)
break;
}
}
const bool res = QWebEngineView::eventFilter(obj, event);
if (obj == m_rwhvqt) {
switch (event->type()) {
case QEvent::FocusIn:

@ -188,6 +188,8 @@ private:
WebPage* m_page;
bool m_firstLoad;
bool m_followLink;
QString m_currentLinkMarkerLabel;
QPointer<QWidget> m_rwhvqt;
WheelHelper m_wheelHelper;

Loading…
Cancel
Save