diff --git a/src/khtmlview.cpp b/src/khtmlview.cpp index cd69bb4..a82afca 100644 --- a/src/khtmlview.cpp +++ b/src/khtmlview.cpp @@ -1,4523 +1,4529 @@ /* This file is part of the KDE project * * Copyright (C) 1998, 1999 Torben Weis * 1999 Lars Knoll * 1999 Antti Koivisto * 2000-2004 Dirk Mueller * 2003 Leo Savernik * 2003-2008 Apple Computer, Inc. * 2008 Allan Sandfeld Jensen * 2006-2008 Germain Garand * * 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "khtmlview.h" #include "khtml_part.h" #include "khtml_events.h" #include #if HAVE_X11 #include #endif #include "html/html_documentimpl.h" #include "html/html_inlineimpl.h" #include "html/html_formimpl.h" #include "html/htmltokenizer.h" #include "editing/editor.h" #include "rendering/render_arena.h" #include "rendering/render_canvas.h" #include "rendering/render_frames.h" #include "rendering/render_replaced.h" #include "rendering/render_form.h" #include "rendering/render_layer.h" #include "rendering/render_line.h" #include "rendering/render_table.h" // removeme #define protected public #include "rendering/render_text.h" #undef protected #include "xml/dom2_eventsimpl.h" #include "css/cssstyleselector.h" #include "misc/loader.h" #include "khtml_settings.h" #include "khtml_printsettings.h" #include "khtmlpart_p.h" #include #include "khtml_debug.h" #include #include #include #include <../khtml_version.h> #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define DEBUG_FLICKER #include #if HAVE_X11 #include #include #elif defined(Q_OS_WIN) #include #endif #if 0 namespace khtml { void dumpLineBoxes(RenderFlow *flow); } #endif using namespace DOM; using namespace khtml; #ifndef NDEBUG static const int sFirstLayoutDelay = 520; static const int sParsingLayoutsInterval = 380; static const int sLayoutAttemptDelay = 300; #else static const int sFirstLayoutDelay = 280; static const int sParsingLayoutsInterval = 320; static const int sLayoutAttemptDelay = 200; #endif static const int sLayoutAttemptIncrement = 20; static const int sParsingLayoutsIncrement = 60; static const int sSmoothScrollTime = 128; static const int sSmoothScrollTick = 16; static const int sSmoothScrollMinStaticPixels = 320 * 200; static const int sMaxMissedDeadlines = 12; static const int sWayTooMany = -1; class KHTMLViewPrivate { friend class KHTMLView; public: enum PseudoFocusNodes { PFNone, PFTop, PFBottom }; enum StaticBackgroundState { SBNone = 0, SBPartial, SBFull }; enum CompletedState { CSNone = 0, CSFull, CSActionPending }; KHTMLViewPrivate(KHTMLView *v) : underMouse(nullptr), underMouseNonShared(nullptr), oldUnderMouse(nullptr) { postponed_autorepeat = nullptr; scrollingFromWheelTimerId = 0; smoothScrollMode = KHTMLView::SSMWhenEfficient; reset(); vpolicy = Qt::ScrollBarAsNeeded; hpolicy = Qt::ScrollBarAsNeeded; formCompletions = nullptr; prevScrollbarVisible = true; possibleTripleClick = false; emitCompletedAfterRepaint = CSNone; cursorIconWidget = nullptr; cursorIconType = KHTMLView::LINK_NORMAL; m_mouseScrollTimer = nullptr; m_mouseScrollIndicator = nullptr; contentsX = 0; contentsY = 0; view = v; } ~KHTMLViewPrivate() { delete formCompletions; delete postponed_autorepeat; if (underMouse) { underMouse->deref(); } if (underMouseNonShared) { underMouseNonShared->deref(); } if (oldUnderMouse) { oldUnderMouse->deref(); } delete cursorIconWidget; delete m_mouseScrollTimer; delete m_mouseScrollIndicator; } void reset() { if (underMouse) { underMouse->deref(); } underMouse = nullptr; if (underMouseNonShared) { underMouseNonShared->deref(); } underMouseNonShared = nullptr; if (oldUnderMouse) { oldUnderMouse->deref(); } oldUnderMouse = nullptr; linkPressed = false; staticWidget = SBNone; fixedObjectsCount = 0; staticObjectsCount = 0; tabMovePending = false; lastTabbingDirection = true; pseudoFocusNode = PFNone; zoomLevel = 100; #ifndef KHTML_NO_SCROLLBARS //We don't turn off the toolbars here //since if the user turns them //off, then chances are they want them turned //off always - even after a reset. #else vpolicy = ScrollBarAlwaysOff; hpolicy = ScrollBarAlwaysOff; #endif scrollBarMoved = false; contentsMoving = false; ignoreWheelEvents = false; scrollingFromWheel = QPoint(-1, -1); borderX = 30; borderY = 30; steps = 0; dx = dy = 0; paged = false; clickX = -1; clickY = -1; clickCount = 0; isDoubleClick = false; scrollingSelf = false; delete postponed_autorepeat; postponed_autorepeat = nullptr; layoutTimerId = 0; repaintTimerId = 0; scrollTimerId = 0; scrollSuspended = false; scrollSuspendPreActivate = false; smoothScrolling = false; smoothScrollModeIsDefault = true; shouldSmoothScroll = false; smoothScrollMissedDeadlines = 0; hasFrameset = false; complete = false; firstLayoutPending = true; #ifdef SPEED_DEBUG firstRepaintPending = true; #endif needsFullRepaint = true; dirtyLayout = false; layoutSchedulingEnabled = true; painting = false; layoutCounter = 0; layoutAttemptCounter = 0; scheduledLayoutCounter = 0; updateRegion = QRegion(); m_dialogsAllowed = true; accessKeysActivated = false; accessKeysPreActivate = false; // the view might have been built before the part it will be assigned to, // so exceptionally, we need to directly ref/deref KHTMLGlobal to // account for this transitory case. KHTMLGlobal::ref(); accessKeysEnabled = KHTMLGlobal::defaultHTMLSettings()->accessKeysEnabled(); KHTMLGlobal::deref(); emitCompletedAfterRepaint = CSNone; m_mouseEventsTarget = nullptr; m_clipHolder = nullptr; } void newScrollTimer(QWidget *view, int tid) { //qCDebug(KHTML_LOG) << "newScrollTimer timer " << tid; view->killTimer(scrollTimerId); scrollTimerId = tid; scrollSuspended = false; } enum ScrollDirection { ScrollLeft, ScrollRight, ScrollUp, ScrollDown }; void adjustScroller(QWidget *view, ScrollDirection direction, ScrollDirection oppositedir) { static const struct { int msec, pixels; } timings [] = { {320, 1}, {224, 1}, {160, 1}, {112, 1}, {80, 1}, {56, 1}, {40, 1}, {28, 1}, {20, 1}, {20, 2}, {20, 3}, {20, 4}, {20, 6}, {20, 8}, {0, 0} }; if (!scrollTimerId || (static_cast(scrollDirection) != direction && (static_cast(scrollDirection) != oppositedir || scrollSuspended))) { scrollTiming = 6; scrollBy = timings[scrollTiming].pixels; scrollDirection = direction; newScrollTimer(view, view->startTimer(timings[scrollTiming].msec)); } else if (scrollDirection == direction && timings[scrollTiming + 1].msec && !scrollSuspended) { scrollBy = timings[++scrollTiming].pixels; newScrollTimer(view, view->startTimer(timings[scrollTiming].msec)); } else if (scrollDirection == oppositedir) { if (scrollTiming) { scrollBy = timings[--scrollTiming].pixels; newScrollTimer(view, view->startTimer(timings[scrollTiming].msec)); } } scrollSuspended = false; } bool haveZoom() const { return zoomLevel != 100; } void startScrolling() { smoothScrolling = true; smoothScrollTimer.start(sSmoothScrollTick); shouldSmoothScroll = false; } void stopScrolling() { smoothScrollTimer.stop(); dx = dy = 0; steps = 0; updateContentsXY(); smoothScrolling = false; shouldSmoothScroll = false; } void updateContentsXY() { contentsX = QApplication::isRightToLeft() ? view->horizontalScrollBar()->maximum() - view->horizontalScrollBar()->value() : view->horizontalScrollBar()->value(); contentsY = view->verticalScrollBar()->value(); } void scrollAccessKeys(int dx, int dy) { QList wl = view->widget()->findChildren("KHTMLAccessKey"); foreach (QLabel *w, wl) { w->move(w->pos() + QPoint(dx, dy)); } } void scrollExternalWidgets(int dx, int dy) { if (visibleWidgets.isEmpty()) { return; } QHashIterator it(visibleWidgets); while (it.hasNext()) { it.next(); it.value()->move(it.value()->pos() + QPoint(dx, dy)); } } NodeImpl *underMouse; NodeImpl *underMouseNonShared; NodeImpl *oldUnderMouse; // Do not adjust bitfield enums sizes. // They are oversized because they are signed on some platforms. bool tabMovePending: 1; bool lastTabbingDirection: 1; PseudoFocusNodes pseudoFocusNode: 3; bool scrollBarMoved: 1; bool contentsMoving: 1; Qt::ScrollBarPolicy vpolicy; Qt::ScrollBarPolicy hpolicy; bool prevScrollbarVisible: 1; bool linkPressed: 1; bool ignoreWheelEvents: 1; StaticBackgroundState staticWidget: 3; int staticObjectsCount; int fixedObjectsCount; int zoomLevel; int borderX, borderY; int dx, dy; int steps; KConfig *formCompletions; int clickX, clickY, clickCount; bool isDoubleClick; bool paged; bool scrollingSelf; int contentsX, contentsY; int layoutTimerId; QKeyEvent *postponed_autorepeat; int repaintTimerId; int scrollTimerId; int scrollTiming; int scrollBy; ScrollDirection scrollDirection : 3; bool scrollSuspended : 1; bool scrollSuspendPreActivate : 1; KHTMLView::SmoothScrollingMode smoothScrollMode : 3; bool smoothScrolling : 1; bool smoothScrollModeIsDefault : 1; bool shouldSmoothScroll : 1; bool hasFrameset : 1; bool complete : 1; bool firstLayoutPending : 1; #ifdef SPEED_DEBUG bool firstRepaintPending : 1; #endif bool layoutSchedulingEnabled : 1; bool needsFullRepaint : 1; bool painting : 1; bool possibleTripleClick : 1; bool dirtyLayout : 1; bool m_dialogsAllowed : 1; short smoothScrollMissedDeadlines; int layoutCounter; int layoutAttemptCounter; int scheduledLayoutCounter; QRegion updateRegion; QTimer smoothScrollTimer; QTime smoothScrollStopwatch; QHash visibleWidgets; bool accessKeysEnabled; bool accessKeysActivated; bool accessKeysPreActivate; CompletedState emitCompletedAfterRepaint; QLabel *cursorIconWidget; KHTMLView::LinkCursor cursorIconType; // scrolling activated by MMB short m_mouseScroll_byX; short m_mouseScroll_byY; QPoint scrollingFromWheel; int scrollingFromWheelTimerId; QTimer *m_mouseScrollTimer; QWidget *m_mouseScrollIndicator; QPointer m_mouseEventsTarget; QStack *m_clipHolder; KHTMLView *view; }; #ifndef QT_NO_TOOLTIP /** calculates the client-side image map rectangle for the given image element * @param img image element * @param scrollOfs scroll offset of viewport in content coordinates * @param p position to be probed in viewport coordinates * @param r returns the bounding rectangle in content coordinates * @param s returns the title string * @return true if an appropriate area was found -- only in this case r and * s are valid, false otherwise */ static bool findImageMapRect(HTMLImageElementImpl *img, const QPoint &scrollOfs, const QPoint &p, QRect &r, QString &s) { HTMLMapElementImpl *map; if (img && img->document()->isHTMLDocument() && (map = static_cast(img->document())->getMap(img->imageMap()))) { RenderObject::NodeInfo info(true, false); RenderObject *rend = img->renderer(); int ax, ay; if (!rend || !rend->absolutePosition(ax, ay)) { return false; } // we're a client side image map bool inside = map->mapMouseEvent(p.x() - ax + scrollOfs.x(), p.y() - ay + scrollOfs.y(), rend->contentWidth(), rend->contentHeight(), info); if (inside && info.URLElement()) { HTMLAreaElementImpl *area = static_cast(info.URLElement()); Q_ASSERT(area->id() == ID_AREA); s = area->getAttribute(ATTR_TITLE).string(); QRegion reg = area->cachedRegion(); if (!s.isEmpty() && !reg.isEmpty()) { r = reg.boundingRect(); r.translate(ax, ay); return true; } } } return false; } bool KHTMLView::event(QEvent *e) { switch (e->type()) { case QEvent::ToolTip: { QHelpEvent *he = static_cast(e); QPoint p = he->pos(); DOM::NodeImpl *node = d->underMouseNonShared; QRect region; while (node) { if (node->isElementNode()) { DOM::ElementImpl *e = static_cast(node); QRect r; QString s; bool found = false; // for images, check if it is part of a client-side image map, // and query the s' title attributes, too if (e->id() == ID_IMG && !e->getAttribute(ATTR_USEMAP).isEmpty()) { found = findImageMapRect(static_cast(e), viewportToContents(QPoint(0, 0)), p, r, s); } if (!found) { s = e->getAttribute(ATTR_TITLE).string().trimmed(); r = node->getRect(); } region |= QRect(contentsToViewport(r.topLeft()), r.size()); if (!s.isEmpty()) { QToolTip::showText(he->globalPos(), Qt::convertFromPlainText(s, Qt::WhiteSpaceNormal), widget(), region); break; } } node = node->parentNode(); } // Qt makes tooltip events happen nearly immediately when a preceding one was processed in the past few seconds. // We don't want that feature to apply to web tootlips however, as it clashes with dhtml menus. // So we'll just pretend we did not process that event. return false; } case QEvent::DragEnter: case QEvent::DragMove: case QEvent::DragLeave: case QEvent::Drop: // In Qt4, one needs to both call accept() on the DND event and return // true on ::event for the candidate widget for the drop to be possible. // Apps hosting us, such as konq, can do the former but not the later. // We will do the second bit, as it's a no-op unless someone else explicitly // accepts the event. We need to skip the scrollarea to do that, // since it will just skip the events, both killing the drop, and // not permitting us to forward it up the part hiearchy in our dragEnterEvent, // etc. handlers return QWidget::event(e); case QEvent::StyleChange: case QEvent::LayoutRequest: { updateScrollBars(); return QAbstractScrollArea::event(e); } case QEvent::PaletteChange: slotPaletteChanged(); return QScrollArea::event(e); default: return QScrollArea::event(e); } } #endif KHTMLView::KHTMLView(KHTMLPart *part, QWidget *parent) : QScrollArea(parent), d(new KHTMLViewPrivate(this)) { m_medium = "screen"; m_part = part; QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy); initWidget(); widget()->setMouseTracking(true); } KHTMLView::~KHTMLView() { closeChildDialogs(); if (m_part) { DOM::DocumentImpl *doc = m_part->xmlDocImpl(); if (doc) { doc->detach(); } } delete d; } void KHTMLView::setPart(KHTMLPart *part) { assert(part && !m_part); m_part = part; } void KHTMLView::initWidget() { // Do not access the part here. It might not be fully constructed. setFrameStyle(QFrame::NoFrame); setFocusPolicy(Qt::StrongFocus); viewport()->setFocusProxy(this); _marginWidth = -1; // undefined _marginHeight = -1; _width = 0; _height = 0; installEventFilter(this); setAcceptDrops(true); if (!widget()) { setWidget(new QWidget(this)); } widget()->setAttribute(Qt::WA_NoSystemBackground); // Do *not* remove this attribute frivolously. // You might not notice a change of behaviour in Debug builds // but removing opaque events will make QWidget::scroll fail horribly // in Release builds. widget()->setAttribute(Qt::WA_OpaquePaintEvent); verticalScrollBar()->setCursor(Qt::ArrowCursor); horizontalScrollBar()->setCursor(Qt::ArrowCursor); connect(&d->smoothScrollTimer, SIGNAL(timeout()), this, SLOT(scrollTick())); } void KHTMLView::resizeContentsToViewport() { QSize s = viewport()->size(); resizeContents(s.width(), s.height()); } // called by KHTMLPart::clear() void KHTMLView::clear() { if (d->accessKeysEnabled && d->accessKeysActivated) { accessKeysTimeout(); } viewport()->unsetCursor(); if (d->cursorIconWidget) { d->cursorIconWidget->hide(); } if (d->smoothScrolling) { d->stopScrolling(); } d->reset(); QAbstractEventDispatcher::instance()->unregisterTimers(this); emit cleared(); QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy); QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); verticalScrollBar()->setEnabled(false); horizontalScrollBar()->setEnabled(false); } void KHTMLView::hideEvent(QHideEvent *e) { QScrollArea::hideEvent(e); } void KHTMLView::showEvent(QShowEvent *e) { QScrollArea::showEvent(e); } void KHTMLView::setMouseEventsTarget(QWidget *w) { d->m_mouseEventsTarget = w; } QWidget *KHTMLView::mouseEventsTarget() const { return d->m_mouseEventsTarget; } void KHTMLView::setClipHolder(QStack *ch) { d->m_clipHolder = ch; } QStack *KHTMLView::clipHolder() const { return d->m_clipHolder; } int KHTMLView::contentsWidth() const { return widget() ? widget()->width() : 0; } int KHTMLView::contentsHeight() const { return widget() ? widget()->height() : 0; } void KHTMLView::resizeContents(int w, int h) { if (!widget()) { return; } widget()->resize(w, h); if (!widget()->isVisible()) { updateScrollBars(); } } int KHTMLView::contentsX() const { return d->contentsX; } int KHTMLView::contentsY() const { return d->contentsY; } int KHTMLView::visibleWidth() const { if (m_kwp->isRedirected()) { // our RenderWidget knows better if (RenderWidget *rw = m_kwp->renderWidget()) { int ret = rw->width() - rw->paddingLeft() - rw->paddingRight() - rw->borderLeft() - rw->borderRight(); if (verticalScrollBar()->isVisible()) { ret -= verticalScrollBar()->sizeHint().width(); ret = qMax(0, ret); } return ret; } } return viewport()->width(); } int KHTMLView::visibleHeight() const { if (m_kwp->isRedirected()) { // our RenderWidget knows better if (RenderWidget *rw = m_kwp->renderWidget()) { int ret = rw->height() - rw->paddingBottom() - rw->paddingTop() - rw->borderTop() - rw->borderBottom(); if (horizontalScrollBar()->isVisible()) { ret -= horizontalScrollBar()->sizeHint().height(); ret = qMax(0, ret); } return ret; } } return viewport()->height(); } void KHTMLView::setContentsPos(int x, int y) { horizontalScrollBar()->setValue(QApplication::isRightToLeft() ? horizontalScrollBar()->maximum() - x : x); verticalScrollBar()->setValue(y); } void KHTMLView::scrollBy(int x, int y) { if (d->scrollTimerId) { d->newScrollTimer(this, 0); } horizontalScrollBar()->setValue(horizontalScrollBar()->value() + x); verticalScrollBar()->setValue(verticalScrollBar()->value() + y); } QPoint KHTMLView::contentsToViewport(const QPoint &p) const { return QPoint(p.x() - contentsX(), p.y() - contentsY()); } void KHTMLView::contentsToViewport(int x, int y, int &cx, int &cy) const { QPoint p(x, y); p = contentsToViewport(p); cx = p.x(); cy = p.y(); } QPoint KHTMLView::viewportToContents(const QPoint &p) const { return QPoint(p.x() + contentsX(), p.y() + contentsY()); } void KHTMLView::viewportToContents(int x, int y, int &cx, int &cy) const { QPoint p(x, y); p = viewportToContents(p); cx = p.x(); cy = p.y(); } void KHTMLView::updateContents(int x, int y, int w, int h) { applyTransforms(x, y, w, h); if (m_kwp->isRedirected()) { QPoint off = m_kwp->absolutePos(); KHTMLView *pview = m_part->parentPart()->view(); pview->updateContents(x + off.x(), y + off.y(), w, h); } else { widget()->update(x, y, w, h); } } void KHTMLView::updateContents(const QRect &r) { updateContents(r.x(), r.y(), r.width(), r.height()); } void KHTMLView::repaintContents(int x, int y, int w, int h) { applyTransforms(x, y, w, h); if (m_kwp->isRedirected()) { QPoint off = m_kwp->absolutePos(); KHTMLView *pview = m_part->parentPart()->view(); pview->repaintContents(x + off.x(), y + off.y(), w, h); } else { widget()->repaint(x, y, w, h); } } void KHTMLView::repaintContents(const QRect &r) { repaintContents(r.x(), r.y(), r.width(), r.height()); } void KHTMLView::applyTransforms(int &x, int &y, int &w, int &h) const { if (d->haveZoom()) { const int z = d->zoomLevel; x = x * z / 100; y = y * z / 100; w = w * z / 100; h = h * z / 100; } x -= contentsX(); y -= contentsY(); } void KHTMLView::revertTransforms(int &x, int &y, int &w, int &h) const { x += contentsX(); y += contentsY(); if (d->haveZoom()) { const int z = d->zoomLevel; x = x * 100 / z; y = y * 100 / z; w = w * 100 / z; h = h * 100 / z; } } void KHTMLView::revertTransforms(int &x, int &y) const { int dummy = 0; revertTransforms(x, y, dummy, dummy); } void KHTMLView::resizeEvent(QResizeEvent * /*e*/) { updateScrollBars(); // If we didn't load anything, make white area as big as the view if (!m_part->xmlDocImpl()) { resizeContentsToViewport(); } // Viewport-dependent media queries may cause us to need completely different style information. if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->styleSelector()->affectedByViewportChange()) { m_part->xmlDocImpl()->updateStyleSelector(); } if (d->layoutSchedulingEnabled) { layout(); } QApplication::sendPostedEvents(viewport(), QEvent::Paint); if (m_part && m_part->xmlDocImpl()) { if (m_part->parentPart()) { // sub-frame : queue the resize event until our toplevel is done layouting khtml::ChildFrame *cf = m_part->parentPart()->frame(m_part); if (cf && !cf->m_partContainerElement.isNull()) { cf->m_partContainerElement.data()->postResizeEvent(); } } else { // toplevel : dispatch sub-frames'resize events before our own HTMLPartContainerElementImpl::sendPostedResizeEvents(); m_part->xmlDocImpl()->dispatchWindowEvent(EventImpl::RESIZE_EVENT, false, false); } } } void KHTMLView::paintEvent(QPaintEvent *e) { QRect r = e->rect(); QRect v(contentsX(), contentsY(), visibleWidth(), visibleHeight()); QPoint off(contentsX(), contentsY()); r.translate(off); r = r.intersected(v); if (!r.isValid() || r.isEmpty()) { return; } QPainter p(widget()); p.translate(-off); if (d->haveZoom()) { p.scale(d->zoomLevel / 100., d->zoomLevel / 100.); r.setX(r.x() * 100 / d->zoomLevel); r.setY(r.y() * 100 / d->zoomLevel); r.setWidth(r.width() * 100 / d->zoomLevel); r.setHeight(r.height() * 100 / d->zoomLevel); r.adjust(-1, -1, 1, 1); } p.setClipRect(r); int ex = r.x(); int ey = r.y(); int ew = r.width(); int eh = r.height(); if (!m_part || !m_part->xmlDocImpl() || !m_part->xmlDocImpl()->renderer()) { p.fillRect(ex, ey, ew, eh, palette().brush(QPalette::Active, QPalette::Base)); return; } else if (d->complete && static_cast(m_part->xmlDocImpl()->renderer())->needsLayout()) { // an external update request happens while we have a layout scheduled unscheduleRelayout(); layout(); } else if (m_part->xmlDocImpl()->tokenizer()) { m_part->xmlDocImpl()->tokenizer()->setNormalYieldDelay(); } if (d->painting) { // qCDebug(KHTML_LOG) << "WARNING: paintEvent reentered! "; return; } d->painting = true; m_part->xmlDocImpl()->renderer()->layer()->paint(&p, r); if (d->hasFrameset) { NodeImpl *body = static_cast(m_part->xmlDocImpl())->body(); if (body && body->renderer() && body->id() == ID_FRAMESET) { static_cast(body->renderer())->paintFrameSetRules(&p, r); } else { d->hasFrameset = false; } } khtml::DrawContentsEvent event(&p, ex, ey, ew, eh); QApplication::sendEvent(m_part, &event); if (d->contentsMoving && !d->smoothScrolling && widget()->underMouse()) { QMouseEvent *tempEvent = new QMouseEvent(QEvent::MouseMove, widget()->mapFromGlobal(QCursor::pos()), Qt::NoButton, Qt::NoButton, Qt::NoModifier); QApplication::postEvent(widget(), tempEvent); } #ifdef SPEED_DEBUG if (d->firstRepaintPending && !m_part->parentPart()) { qCDebug(KHTML_LOG) << "FIRST PAINT:" << m_part->d->m_parsetime.elapsed(); } d->firstRepaintPending = false; #endif d->painting = false; } void KHTMLView::setMarginWidth(int w) { // make it update the rendering area when set _marginWidth = w; } void KHTMLView::setMarginHeight(int h) { // make it update the rendering area when set _marginHeight = h; } void KHTMLView::layout() { if (m_part && m_part->xmlDocImpl()) { DOM::DocumentImpl *document = m_part->xmlDocImpl(); khtml::RenderCanvas *canvas = static_cast(document->renderer()); if (!canvas) { return; } d->layoutSchedulingEnabled = false; d->dirtyLayout = true; // the reference object for the overflow property on canvas RenderObject *ref = nullptr; RenderObject *root = document->documentElement() ? document->documentElement()->renderer() : nullptr; if (document->isHTMLDocument()) { NodeImpl *body = static_cast(document)->body(); if (body && body->renderer() && body->id() == ID_FRAMESET) { QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); body->renderer()->setNeedsLayout(true); d->hasFrameset = true; } else if (root) { // only apply body's overflow to canvas if root has a visible overflow ref = (!body || root->style()->hidesOverflow()) ? root : body->renderer(); } } else { ref = root; } if (ref) { if (ref->style()->overflowX() == OHIDDEN) { if (d->hpolicy == Qt::ScrollBarAsNeeded) { QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } } else if (ref->style()->overflowX() == OSCROLL) { if (d->hpolicy == Qt::ScrollBarAsNeeded) { QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } else if (horizontalScrollBarPolicy() != d->hpolicy) { QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy); } if (ref->style()->overflowY() == OHIDDEN) { if (d->vpolicy == Qt::ScrollBarAsNeeded) { QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } } else if (ref->style()->overflowY() == OSCROLL) { if (d->vpolicy == Qt::ScrollBarAsNeeded) { QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); } } else if (verticalScrollBarPolicy() != d->vpolicy) { QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); } } d->needsFullRepaint = d->firstLayoutPending; if (_height != visibleHeight() || _width != visibleWidth()) { ; d->needsFullRepaint = true; _height = visibleHeight(); _width = visibleWidth(); } canvas->layout(); emit finishedLayout(); if (d->firstLayoutPending) { // make sure firstLayoutPending is set to false now in case this layout // wasn't scheduled d->firstLayoutPending = false; verticalScrollBar()->setEnabled(true); horizontalScrollBar()->setEnabled(true); } d->layoutCounter++; if (d->accessKeysEnabled && d->accessKeysActivated) { emit hideAccessKeys(); displayAccessKeys(); } } else { _width = visibleWidth(); } if (d->layoutTimerId) { killTimer(d->layoutTimerId); } d->layoutTimerId = 0; d->layoutSchedulingEnabled = true; } void KHTMLView::closeChildDialogs() { QList dlgs = findChildren(); foreach (QDialog *dlg, dlgs) { if (dlg->testAttribute(Qt::WA_ShowModal)) { // qCDebug(KHTML_LOG) << "closeChildDialogs: closing dialog " << dlg; // close() ends up calling QButton::animateClick, which isn't immediate // we need something the exits the event loop immediately (#49068) dlg->reject(); } } d->m_dialogsAllowed = false; } bool KHTMLView::dialogsAllowed() { bool allowed = d->m_dialogsAllowed; KHTMLPart *p = m_part->parentPart(); if (p && p->view()) { allowed &= p->view()->dialogsAllowed(); } return allowed; } void KHTMLView::closeEvent(QCloseEvent *ev) { closeChildDialogs(); QScrollArea::closeEvent(ev); } void KHTMLView::setZoomLevel(int percent) { percent = percent < 20 ? 20 : (percent > 800 ? 800 : percent); int oldpercent = d->zoomLevel; d->zoomLevel = percent; if (percent != oldpercent) { if (d->layoutSchedulingEnabled) { layout(); } widget()->update(); } } int KHTMLView::zoomLevel() const { return d->zoomLevel; } void KHTMLView::setSmoothScrollingMode(SmoothScrollingMode m) { d->smoothScrollMode = m; d->smoothScrollModeIsDefault = false; if (d->smoothScrolling && !m) { d->stopScrolling(); } } void KHTMLView::setSmoothScrollingModeDefault(SmoothScrollingMode m) { // check for manual override if (!d->smoothScrollModeIsDefault) { return; } d->smoothScrollMode = m; if (d->smoothScrolling && !m) { d->stopScrolling(); } } KHTMLView::SmoothScrollingMode KHTMLView::smoothScrollingMode() const { return d->smoothScrollMode; } // // Event Handling // ///////////////// void KHTMLView::mousePressEvent(QMouseEvent *_mouse) { if (!m_part->xmlDocImpl()) { return; } if (d->possibleTripleClick && (_mouse->button() & Qt::MouseButtonMask) == Qt::LeftButton) { mouseDoubleClickEvent(_mouse); // it handles triple clicks too return; } int xm = _mouse->x(); int ym = _mouse->y(); revertTransforms(xm, ym); // qCDebug(KHTML_LOG) << "mousePressEvent: viewport=("<<_mouse->x()-contentsX()<<"/"<<_mouse->y()-contentsY()<<"), contents=(" << xm << "/" << ym << ")\n"; d->isDoubleClick = false; DOM::NodeImpl::MouseEvent mev(_mouse->buttons(), DOM::NodeImpl::MousePress); m_part->xmlDocImpl()->prepareMouseEvent(false, xm, ym, &mev); //qCDebug(KHTML_LOG) << "innerNode="<button() == Qt::MidButton) && !m_part->d->m_bOpenMiddleClick && !d->m_mouseScrollTimer && mev.url.isNull() && (mev.innerNode.elementId() != ID_INPUT)) { QPoint point = mapFromGlobal(_mouse->globalPos()); d->m_mouseScroll_byX = 0; d->m_mouseScroll_byY = 0; d->m_mouseScrollTimer = new QTimer(this); connect(d->m_mouseScrollTimer, SIGNAL(timeout()), this, SLOT(slotMouseScrollTimer())); if (!d->m_mouseScrollIndicator) { QPixmap pixmap(48, 48), icon; pixmap.fill(QColor(qRgba(127, 127, 127, 127))); QPainter p(&pixmap); QStyleOption option; option.rect.setRect(16, 0, 16, 16); QApplication::style()->drawPrimitive(QStyle::PE_IndicatorArrowUp, &option, &p); option.rect.setRect(0, 16, 16, 16); QApplication::style()->drawPrimitive(QStyle::PE_IndicatorArrowLeft, &option, &p); option.rect.setRect(16, 32, 16, 16); QApplication::style()->drawPrimitive(QStyle::PE_IndicatorArrowDown, &option, &p); option.rect.setRect(32, 16, 16, 16); QApplication::style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &p); p.drawEllipse(23, 23, 2, 2); d->m_mouseScrollIndicator = new QWidget(this); d->m_mouseScrollIndicator->setFixedSize(48, 48); QPalette palette; palette.setBrush(d->m_mouseScrollIndicator->backgroundRole(), QBrush(pixmap)); d->m_mouseScrollIndicator->setPalette(palette); } d->m_mouseScrollIndicator->move(point.x() - 24, point.y() - 24); bool hasHorBar = visibleWidth() < contentsWidth(); bool hasVerBar = visibleHeight() < contentsHeight(); KConfigGroup cg(KSharedConfig::openConfig(), "HTML Settings"); if (cg.readEntry("ShowMouseScrollIndicator", true)) { d->m_mouseScrollIndicator->show(); d->m_mouseScrollIndicator->unsetCursor(); QBitmap mask = d->m_mouseScrollIndicator->palette().brush(d->m_mouseScrollIndicator->backgroundRole()).texture().createHeuristicMask(true); if (hasHorBar && !hasVerBar) { QBitmap bm(16, 16); bm.clear(); QPainter painter(&mask); painter.drawPixmap(QRectF(16, 0, bm.width(), bm.height()), bm, bm.rect()); painter.drawPixmap(QRectF(16, 32, bm.width(), bm.height()), bm, bm.rect()); d->m_mouseScrollIndicator->setCursor(Qt::SizeHorCursor); } else if (!hasHorBar && hasVerBar) { QBitmap bm(16, 16); bm.clear(); QPainter painter(&mask); painter.drawPixmap(QRectF(0, 16, bm.width(), bm.height()), bm, bm.rect()); painter.drawPixmap(QRectF(32, 16, bm.width(), bm.height()), bm, bm.rect()); d->m_mouseScrollIndicator->setCursor(Qt::SizeVerCursor); } else { d->m_mouseScrollIndicator->setCursor(Qt::SizeAllCursor); } d->m_mouseScrollIndicator->setMask(mask); } else { if (hasHorBar && !hasVerBar) { viewport()->setCursor(Qt::SizeHorCursor); } else if (!hasHorBar && hasVerBar) { viewport()->setCursor(Qt::SizeVerCursor); } else { viewport()->setCursor(Qt::SizeAllCursor); } } return; } else if (d->m_mouseScrollTimer) { delete d->m_mouseScrollTimer; d->m_mouseScrollTimer = nullptr; if (d->m_mouseScrollIndicator) { d->m_mouseScrollIndicator->hide(); } } if (d->clickCount > 0 && QPoint(d->clickX - xm, d->clickY - ym).manhattanLength() <= QApplication::startDragDistance()) { d->clickCount++; } else { d->clickCount = 1; d->clickX = xm; d->clickY = ym; } bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEDOWN_EVENT, mev.innerNode.handle(), mev.innerNonSharedNode.handle(), true, d->clickCount, _mouse, true, DOM::NodeImpl::MousePress); if (!swallowEvent) { emit m_part->nodeActivated(mev.innerNode); khtml::MousePressEvent event(_mouse, xm, ym, mev.url, mev.target, mev.innerNode); QApplication::sendEvent(m_part, &event); // we might be deleted after this } } void KHTMLView::mouseDoubleClickEvent(QMouseEvent *_mouse) { if (!m_part->xmlDocImpl()) { return; } int xm = _mouse->x(); int ym = _mouse->y(); revertTransforms(xm, ym); // qCDebug(KHTML_LOG) << "mouseDblClickEvent: x=" << xm << ", y=" << ym; d->isDoubleClick = true; DOM::NodeImpl::MouseEvent mev(_mouse->buttons(), DOM::NodeImpl::MouseDblClick); m_part->xmlDocImpl()->prepareMouseEvent(false, xm, ym, &mev); // We do the same thing as mousePressEvent() here, since the DOM does not treat // single and double-click events as separate (only the detail, i.e. number of clicks differs) if (d->clickCount > 0 && QPoint(d->clickX - xm, d->clickY - ym).manhattanLength() <= QApplication::startDragDistance()) { d->clickCount++; } else { // shouldn't happen, if Qt has the same criterias for double clicks. d->clickCount = 1; d->clickX = xm; d->clickY = ym; } bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEDOWN_EVENT, mev.innerNode.handle(), mev.innerNonSharedNode.handle(), true, d->clickCount, _mouse, true, DOM::NodeImpl::MouseDblClick); if (!swallowEvent) { khtml::MouseDoubleClickEvent event(_mouse, xm, ym, mev.url, mev.target, mev.innerNode, d->clickCount); QApplication::sendEvent(m_part, &event); } d->possibleTripleClick = true; QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout())); } void KHTMLView::tripleClickTimeout() { d->possibleTripleClick = false; d->clickCount = 0; } static bool targetOpensNewWindow(KHTMLPart *part, QString target) { if (!target.isEmpty() && (target.toLower() != "_top") && (target.toLower() != "_self") && (target.toLower() != "_parent")) { if (target.toLower() == "_blank") { return true; } else { while (part->parentPart()) { part = part->parentPart(); } if (!part->frameExists(target)) { return true; } } } return false; } void KHTMLView::mouseMoveEvent(QMouseEvent *_mouse) { if (d->m_mouseScrollTimer) { QPoint point = mapFromGlobal(_mouse->globalPos()); int deltaX = point.x() - d->m_mouseScrollIndicator->x() - 24; int deltaY = point.y() - d->m_mouseScrollIndicator->y() - 24; (deltaX > 0) ? d->m_mouseScroll_byX = 1 : d->m_mouseScroll_byX = -1; (deltaY > 0) ? d->m_mouseScroll_byY = 1 : d->m_mouseScroll_byY = -1; double adX = qAbs(deltaX) / 30.0; double adY = qAbs(deltaY) / 30.0; d->m_mouseScroll_byX = qMax(qMin(d->m_mouseScroll_byX * int(adX * adX), SHRT_MAX), SHRT_MIN); d->m_mouseScroll_byY = qMax(qMin(d->m_mouseScroll_byY * int(adY * adY), SHRT_MAX), SHRT_MIN); if (d->m_mouseScroll_byX == 0 && d->m_mouseScroll_byY == 0) { d->m_mouseScrollTimer->stop(); } else if (!d->m_mouseScrollTimer->isActive()) { d->m_mouseScrollTimer->start(20); } } if (!m_part->xmlDocImpl()) { return; } int xm = _mouse->x(); int ym = _mouse->y(); revertTransforms(xm, ym); DOM::NodeImpl::MouseEvent mev(_mouse->buttons(), DOM::NodeImpl::MouseMove); // Do not modify :hover/:active state while mouse is pressed. m_part->xmlDocImpl()->prepareMouseEvent(_mouse->buttons() /*readonly ?*/, xm, ym, &mev); // qCDebug(KHTML_LOG) << "mouse move: " << _mouse->pos() // << " button " << _mouse->button() // << " state " << _mouse->state(); DOM::NodeImpl *target = mev.innerNode.handle(); DOM::NodeImpl *fn = m_part->xmlDocImpl()->focusNode(); // a widget may be the real target of this event (e.g. if a scrollbar's slider is being moved) if (d->m_mouseEventsTarget && fn && fn->renderer() && fn->renderer()->isWidget()) { target = fn; } bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEMOVE_EVENT, target, mev.innerNonSharedNode.handle(), false, 0, _mouse, true, DOM::NodeImpl::MouseMove); if (d->clickCount > 0 && QPoint(d->clickX - xm, d->clickY - ym).manhattanLength() > QApplication::startDragDistance()) { d->clickCount = 0; // moving the mouse outside the threshold invalidates the click } khtml::RenderObject *r = target ? target->renderer() : nullptr; bool setCursor = true; bool forceDefault = false; if (r && r->isWidget()) { RenderWidget *rw = static_cast(r); KHTMLWidget *kw = qobject_cast(rw->widget()) ? dynamic_cast(rw->widget()) : nullptr; if (kw && kw->m_kwp->isRedirected()) { setCursor = false; } else if (QLineEdit *le = qobject_cast(rw->widget())) { QList wl = le->findChildren("KLineEditButton"); // force arrow cursor above lineedit clear button foreach (QWidget *w, wl) { if (w->underMouse()) { forceDefault = true; break; } } } else if (QTextEdit *te = qobject_cast(rw->widget())) { if (te->verticalScrollBar()->underMouse() || te->horizontalScrollBar()->underMouse()) { forceDefault = true; } } } khtml::RenderStyle *style = (r && r->style()) ? r->style() : nullptr; QCursor c; LinkCursor linkCursor = LINK_NORMAL; switch (!forceDefault ? (style ? style->cursor() : CURSOR_AUTO) : CURSOR_DEFAULT) { case CURSOR_AUTO: if (r && r->isText() && ((m_part->d->m_bMousePressed && m_part->d->editor_context.m_beganSelectingText) || !r->isPointInsideSelection(xm, ym, m_part->caret()))) { c = QCursor(Qt::IBeamCursor); } if (mev.url.length() && m_part->settings()->changeCursor()) { c = m_part->urlCursor(); if (mev.url.string().startsWith("mailto:") && mev.url.string().indexOf('@') > 0) { linkCursor = LINK_MAILTO; } else if (targetOpensNewWindow(m_part, mev.target.string())) { linkCursor = LINK_NEWWINDOW; } } if (r && r->isFrameSet() && !static_cast(r)->noResize()) { c = QCursor(static_cast(r)->cursorShape()); } break; case CURSOR_CROSS: c = QCursor(Qt::CrossCursor); break; case CURSOR_POINTER: c = m_part->urlCursor(); if (mev.url.string().startsWith("mailto:") && mev.url.string().indexOf('@') > 0) { linkCursor = LINK_MAILTO; } else if (targetOpensNewWindow(m_part, mev.target.string())) { linkCursor = LINK_NEWWINDOW; } break; case CURSOR_PROGRESS: c = QCursor(Qt::BusyCursor); // working_cursor break; case CURSOR_MOVE: case CURSOR_ALL_SCROLL: c = QCursor(Qt::SizeAllCursor); break; case CURSOR_E_RESIZE: case CURSOR_W_RESIZE: case CURSOR_EW_RESIZE: c = QCursor(Qt::SizeHorCursor); break; case CURSOR_N_RESIZE: case CURSOR_S_RESIZE: case CURSOR_NS_RESIZE: c = QCursor(Qt::SizeVerCursor); break; case CURSOR_NE_RESIZE: case CURSOR_SW_RESIZE: case CURSOR_NESW_RESIZE: c = QCursor(Qt::SizeBDiagCursor); break; case CURSOR_NW_RESIZE: case CURSOR_SE_RESIZE: case CURSOR_NWSE_RESIZE: c = QCursor(Qt::SizeFDiagCursor); break; case CURSOR_TEXT: c = QCursor(Qt::IBeamCursor); break; case CURSOR_WAIT: c = QCursor(Qt::WaitCursor); break; case CURSOR_HELP: c = QCursor(Qt::WhatsThisCursor); break; case CURSOR_DEFAULT: break; case CURSOR_NONE: case CURSOR_NOT_ALLOWED: c = QCursor(Qt::ForbiddenCursor); break; case CURSOR_ROW_RESIZE: c = QCursor(Qt::SplitVCursor); break; case CURSOR_COL_RESIZE: c = QCursor(Qt::SplitHCursor); break; case CURSOR_VERTICAL_TEXT: case CURSOR_CONTEXT_MENU: case CURSOR_NO_DROP: case CURSOR_CELL: case CURSOR_COPY: case CURSOR_ALIAS: c = QCursor(Qt::ArrowCursor); break; } if (!setCursor && style && style->cursor() != CURSOR_AUTO) { setCursor = true; } QWidget *vp = viewport(); for (KHTMLPart *p = m_part; p; p = p->parentPart()) if (!p->parentPart()) { vp = p->view()->viewport(); } if (setCursor && (vp->cursor().shape() != c.shape() || c.shape() == Qt::BitmapCursor)) { if (c.shape() == Qt::ArrowCursor) { for (KHTMLPart *p = m_part; p; p = p->parentPart()) { p->view()->viewport()->unsetCursor(); } } else { vp->setCursor(c); } } if (linkCursor != LINK_NORMAL && isVisible() && hasFocus()) { #if HAVE_X11 if (!d->cursorIconWidget) { #if HAVE_X11 d->cursorIconWidget = new QLabel(nullptr, Qt::X11BypassWindowManagerHint); XSetWindowAttributes attr; attr.save_under = True; XChangeWindowAttributes(QX11Info::display(), d->cursorIconWidget->winId(), CWSaveUnder, &attr); #else d->cursorIconWidget = new QLabel(NULL, NULL); //TODO #endif } // Update the pixmap if need be. if (linkCursor != d->cursorIconType) { d->cursorIconType = linkCursor; QString cursorIcon; switch (linkCursor) { case LINK_MAILTO: cursorIcon = "mail-message-new"; break; case LINK_NEWWINDOW: cursorIcon = "window-new"; break; default: cursorIcon = "dialog-error"; break; } QPixmap icon_pixmap = KHTMLGlobal::iconLoader()->loadIcon(cursorIcon, KIconLoader::Small, 0, KIconLoader::DefaultState, QStringList(), nullptr, true); d->cursorIconWidget->resize(icon_pixmap.width(), icon_pixmap.height()); d->cursorIconWidget->setMask(icon_pixmap.createMaskFromColor(Qt::transparent)); d->cursorIconWidget->setPixmap(icon_pixmap); d->cursorIconWidget->update(); } QPoint c_pos = QCursor::pos(); d->cursorIconWidget->move(c_pos.x() + 15, c_pos.y() + 15); #if HAVE_X11 XRaiseWindow(QX11Info::display(), d->cursorIconWidget->winId()); QApplication::flush(); #elif defined(Q_OS_WIN) SetWindowPos(d->cursorIconWidget->winId(), HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); #else //TODO? #endif d->cursorIconWidget->show(); #endif } else if (d->cursorIconWidget) { d->cursorIconWidget->hide(); } if (r && r->isWidget()) { _mouse->ignore(); } if (!swallowEvent) { khtml::MouseMoveEvent event(_mouse, xm, ym, mev.url, mev.target, mev.innerNode); QApplication::sendEvent(m_part, &event); } } void KHTMLView::mouseReleaseEvent(QMouseEvent *_mouse) { bool swallowEvent = false; int xm = _mouse->x(); int ym = _mouse->y(); revertTransforms(xm, ym); DOM::NodeImpl::MouseEvent mev(_mouse->buttons(), DOM::NodeImpl::MouseRelease); if (m_part->xmlDocImpl()) { m_part->xmlDocImpl()->prepareMouseEvent(false, xm, ym, &mev); DOM::NodeImpl *target = mev.innerNode.handle(); DOM::NodeImpl *fn = m_part->xmlDocImpl()->focusNode(); // a widget may be the real target of this event (e.g. if a scrollbar's slider is being moved) if (d->m_mouseEventsTarget && fn && fn->renderer() && fn->renderer()->isWidget()) { target = fn; } swallowEvent = dispatchMouseEvent(EventImpl::MOUSEUP_EVENT, target, mev.innerNonSharedNode.handle(), true, d->clickCount, _mouse, false, DOM::NodeImpl::MouseRelease); // clear our sticky event target on any mouseRelease event if (d->m_mouseEventsTarget) { d->m_mouseEventsTarget = nullptr; } if (d->clickCount > 0 && QPoint(d->clickX - xm, d->clickY - ym).manhattanLength() <= QApplication::startDragDistance()) { QMouseEvent me(d->isDoubleClick ? QEvent::MouseButtonDblClick : QEvent::MouseButtonRelease, _mouse->pos(), _mouse->button(), _mouse->buttons(), _mouse->modifiers()); dispatchMouseEvent(EventImpl::CLICK_EVENT, mev.innerNode.handle(), mev.innerNonSharedNode.handle(), true, d->clickCount, &me, true, DOM::NodeImpl::MouseRelease); } khtml::RenderObject *r = target ? target->renderer() : nullptr; if (r && r->isWidget()) { _mouse->ignore(); } } if (!swallowEvent) { khtml::MouseReleaseEvent event(_mouse, xm, ym, mev.url, mev.target, mev.innerNode); QApplication::sendEvent(m_part, &event); } } // returns true if event should be swallowed bool KHTMLView::dispatchKeyEvent(QKeyEvent *_ke) { if (!m_part->xmlDocImpl()) { return false; } // Pressing and releasing a key should generate keydown, keypress and keyup events // Holding it down should generated keydown, keypress (repeatedly) and keyup events // The problem here is that Qt generates two autorepeat events (keyrelease+keypress) // for autorepeating, while DOM wants only one autorepeat event (keypress), so one // of the Qt events shouldn't be passed to DOM, but it should be still filtered // out if DOM would filter the autorepeat event. Additional problem is that Qt keyrelease // events don't have text() set (Qt bug?), so DOM often would ignore the keypress event // if it was created using Qt keyrelease, but Qt autorepeat keyrelease comes // before Qt autorepeat keypress (i.e. problem whether to filter it out or not). // The solution is to filter out and postpone the Qt autorepeat keyrelease until // the following Qt keypress event comes. If DOM accepts the DOM keypress event, // the postponed event will be simply discarded. If not, it will be passed to keyPressEvent() // again, and here it will be ignored. // // Qt: Press | Release(autorepeat) Press(autorepeat) etc. | Release // DOM: Down + Press | (nothing) Press | Up // It's also possible to get only Releases. E.g. the release of alt-tab, // or when the keypresses get captured by an accel. if (_ke == d->postponed_autorepeat) { // replayed event return false; } if (_ke->type() == QEvent::KeyPress) { if (!_ke->isAutoRepeat()) { bool ret = dispatchKeyEventHelper(_ke, false); // keydown // don't send keypress even if keydown was blocked, like IE (and unlike Mozilla) if (!ret && dispatchKeyEventHelper(_ke, true)) { // keypress ret = true; } return ret; } else { // autorepeat bool ret = dispatchKeyEventHelper(_ke, true); // keypress if (!ret && d->postponed_autorepeat) { keyPressEvent(d->postponed_autorepeat); } delete d->postponed_autorepeat; d->postponed_autorepeat = nullptr; return ret; } } else { // QEvent::KeyRelease // Discard postponed "autorepeat key-release" events that didn't see // a keypress after them (e.g. due to QAccel) delete d->postponed_autorepeat; d->postponed_autorepeat = nullptr; if (!_ke->isAutoRepeat()) { return dispatchKeyEventHelper(_ke, false); // keyup } else { d->postponed_autorepeat = new QKeyEvent(_ke->type(), _ke->key(), _ke->modifiers(), _ke->text(), _ke->isAutoRepeat(), _ke->count()); if (_ke->isAccepted()) { d->postponed_autorepeat->accept(); } else { d->postponed_autorepeat->ignore(); } return true; } } } // returns true if event should be swallowed bool KHTMLView::dispatchKeyEventHelper(QKeyEvent *_ke, bool keypress) { DOM::NodeImpl *keyNode = m_part->xmlDocImpl()->focusNode(); if (keyNode) { return keyNode->dispatchKeyEvent(_ke, keypress); } else { // no focused node, send to document return m_part->xmlDocImpl()->dispatchKeyEvent(_ke, keypress); } } void KHTMLView::keyPressEvent(QKeyEvent *_ke) { // If CTRL was hit, be prepared for access keys if (d->accessKeysEnabled && _ke->key() == Qt::Key_Control && !(_ke->modifiers() & ~Qt::ControlModifier) && !d->accessKeysActivated) { d->accessKeysPreActivate = true; _ke->accept(); return; } if (_ke->key() == Qt::Key_Shift && !(_ke->modifiers() & ~Qt::ShiftModifier)) { d->scrollSuspendPreActivate = true; } // accesskey handling needs to be done before dispatching, otherwise e.g. lineedits // may eat the event if (d->accessKeysEnabled && d->accessKeysActivated) { int state = (_ke->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier)); if (state == 0 || state == Qt::ShiftModifier) { if (_ke->key() != Qt::Key_Shift) { accessKeysTimeout(); } handleAccessKey(_ke); _ke->accept(); return; } accessKeysTimeout(); _ke->accept(); return; } if (dispatchKeyEvent(_ke)) { // If either keydown or keypress was accepted by a widget, or canceled by JS, stop here. _ke->accept(); return; } int offs = (viewport()->height() < 30) ? viewport()->height() : 30; // ### ?? if (_ke->modifiers() & Qt::ShiftModifier) switch (_ke->key()) { case Qt::Key_Space: verticalScrollBar()->setValue(verticalScrollBar()->value() - viewport()->height() + offs); if (d->scrollSuspended) { d->newScrollTimer(this, 0); } break; case Qt::Key_Down: case Qt::Key_J: d->adjustScroller(this, KHTMLViewPrivate::ScrollDown, KHTMLViewPrivate::ScrollUp); break; case Qt::Key_Up: case Qt::Key_K: d->adjustScroller(this, KHTMLViewPrivate::ScrollUp, KHTMLViewPrivate::ScrollDown); break; case Qt::Key_Left: case Qt::Key_H: d->adjustScroller(this, KHTMLViewPrivate::ScrollLeft, KHTMLViewPrivate::ScrollRight); break; case Qt::Key_Right: case Qt::Key_L: d->adjustScroller(this, KHTMLViewPrivate::ScrollRight, KHTMLViewPrivate::ScrollLeft); break; } else switch (_ke->key()) { case Qt::Key_Down: case Qt::Key_J: if (!d->scrollTimerId || d->scrollSuspended) { verticalScrollBar()->setValue(verticalScrollBar()->value() + 10); } if (d->scrollTimerId) { d->newScrollTimer(this, 0); } break; case Qt::Key_Space: case Qt::Key_PageDown: d->shouldSmoothScroll = true; verticalScrollBar()->setValue(verticalScrollBar()->value() + viewport()->height() - offs); if (d->scrollSuspended) { d->newScrollTimer(this, 0); } break; case Qt::Key_Up: case Qt::Key_K: if (!d->scrollTimerId || d->scrollSuspended) { verticalScrollBar()->setValue(verticalScrollBar()->value() - 10); } if (d->scrollTimerId) { d->newScrollTimer(this, 0); } break; case Qt::Key_PageUp: d->shouldSmoothScroll = true; verticalScrollBar()->setValue(verticalScrollBar()->value() - viewport()->height() + offs); if (d->scrollSuspended) { d->newScrollTimer(this, 0); } break; case Qt::Key_Right: case Qt::Key_L: if (!d->scrollTimerId || d->scrollSuspended) { horizontalScrollBar()->setValue(horizontalScrollBar()->value() + 10); } if (d->scrollTimerId) { d->newScrollTimer(this, 0); } break; case Qt::Key_Left: case Qt::Key_H: if (!d->scrollTimerId || d->scrollSuspended) { horizontalScrollBar()->setValue(horizontalScrollBar()->value() - 10); } if (d->scrollTimerId) { d->newScrollTimer(this, 0); } break; case Qt::Key_Enter: case Qt::Key_Return: // ### FIXME: // or even better to HTMLAnchorElementImpl::event() if (m_part->xmlDocImpl()) { NodeImpl *n = m_part->xmlDocImpl()->focusNode(); if (n) { n->setActive(); } } break; case Qt::Key_Home: verticalScrollBar()->setValue(0); horizontalScrollBar()->setValue(0); if (d->scrollSuspended) { d->newScrollTimer(this, 0); } break; case Qt::Key_End: verticalScrollBar()->setValue(contentsHeight() - visibleHeight()); if (d->scrollSuspended) { d->newScrollTimer(this, 0); } break; case Qt::Key_Shift: // what are you doing here? _ke->ignore(); return; default: if (d->scrollTimerId) { d->newScrollTimer(this, 0); } _ke->ignore(); return; } _ke->accept(); } void KHTMLView::keyReleaseEvent(QKeyEvent *_ke) { if (d->scrollSuspendPreActivate && _ke->key() != Qt::Key_Shift) { d->scrollSuspendPreActivate = false; } if (_ke->key() == Qt::Key_Shift && d->scrollSuspendPreActivate && !(_ke->modifiers() & Qt::ShiftModifier)) if (d->scrollTimerId) { d->scrollSuspended = !d->scrollSuspended; if (d->scrollSuspended) { d->stopScrolling(); } } if (d->accessKeysEnabled) { if (d->accessKeysPreActivate && _ke->key() != Qt::Key_Control) { d->accessKeysPreActivate = false; } if (d->accessKeysPreActivate && !(_ke->modifiers() & Qt::ControlModifier)) { displayAccessKeys(); m_part->setStatusBarText(i18n("Access Keys activated"), KHTMLPart::BarOverrideText); d->accessKeysActivated = true; d->accessKeysPreActivate = false; _ke->accept(); return; } else if (d->accessKeysActivated) { accessKeysTimeout(); _ke->accept(); return; } } // Send keyup event if (dispatchKeyEvent(_ke)) { _ke->accept(); return; } QScrollArea::keyReleaseEvent(_ke); } bool KHTMLView::focusNextPrevChild(bool next) { // Now try to find the next child if (m_part->xmlDocImpl() && focusNextPrevNode(next)) { //if (m_part->xmlDocImpl()->focusNode()) // qCDebug(KHTML_LOG) << "focusNode.name: " // << m_part->xmlDocImpl()->focusNode()->nodeName().string(); return true; // focus node found } // If we get here, pass tabbing control up to the next/previous child in our parent d->pseudoFocusNode = KHTMLViewPrivate::PFNone; if (m_part->parentPart() && m_part->parentPart()->view()) { return m_part->parentPart()->view()->focusNextPrevChild(next); } return QWidget::focusNextPrevChild(next); } void KHTMLView::doAutoScroll() { QPoint pos = QCursor::pos(); QPoint off; KHTMLView *v = m_kwp->isRedirected() ? m_kwp->rootViewPos(off) : this; pos = v->viewport()->mapFromGlobal(pos); pos -= off; int xm, ym; viewportToContents(pos.x(), pos.y(), xm, ym); // ### pos = QPoint(pos.x() - viewport()->x(), pos.y() - viewport()->y()); if ((pos.y() < 0) || (pos.y() > visibleHeight()) || (pos.x() < 0) || (pos.x() > visibleWidth())) { ensureVisible(xm, ym, 0, 5); #ifndef KHTML_NO_SELECTION // extend the selection while scrolling DOM::Node innerNode; if (m_part->isExtendingSelection()) { RenderObject::NodeInfo renderInfo(true/*readonly*/, false/*active*/); m_part->xmlDocImpl()->renderer()->layer() ->nodeAtPoint(renderInfo, xm, ym); innerNode = renderInfo.innerNode(); }/*end if*/ if (innerNode.handle() && innerNode.handle()->renderer() && innerNode.handle()->renderer()->shouldSelect()) { m_part->extendSelectionTo(xm, ym, innerNode); }/*end if*/ #endif // KHTML_NO_SELECTION } } // KHTML defines its own stacking order for any object and thus takes // control of widget painting whenever it can. This is called "redirection". // // Redirected widgets are placed off screen. When they are declared as a child of our view (ChildPolished event), // an event filter is installed, so as to catch any paint event and translate them as update() of the view's main widget. // // Painting also happens spontaneously within widgets. In this case, the widget would update() parts of itself. // While this ordinarily results in a paintEvent being schedduled, it is not the case with off screen widgets. // Thus update() is monitored by using the mechanism that deffers any update call happening during a paint event, // transforming it into a posted UpdateLater event. Hence the need to set Qt::WA_WState_InPaintEvent on redirected widgets. // // Once the UpdateLater event has been received, Qt::WA_WState_InPaintEvent is removed and the process continues // with the update of the corresponding rect on the view. That in turn will make our painting subsystem render() // the widget at the correct stacking position. // // For non-redirected (e.g. external) widgets, z-order is honoured through masking. cf.RenderLayer::updateWidgetMasks static void handleWidget(QWidget *w, KHTMLView *view, bool recurse = true) { if (w->isWindow()) { return; } if (!qobject_cast(w)) { w->setAttribute(Qt::WA_NoSystemBackground); } w->setAttribute(Qt::WA_WState_InPaintEvent); if (!(w->objectName() == "KLineEditButton")) { w->setAttribute(Qt::WA_OpaquePaintEvent); } w->installEventFilter(view); if (!recurse) { return; } if (qobject_cast(w)) { handleWidget(static_cast(w)->widget(), view, false); handleWidget(static_cast(w)->horizontalScrollBar(), view, false); handleWidget(static_cast(w)->verticalScrollBar(), view, false); return; } QObjectList children = w->children(); foreach (QObject *object, children) { QWidget *widget = qobject_cast(object); if (widget) { handleWidget(widget, view); } } } class KHTMLBackingStoreHackWidget : public QWidget { public: void publicEvent(QEvent *e) { QWidget::event(e); } }; bool KHTMLView::viewportEvent(QEvent *e) { switch (e->type()) { // those must not be dispatched to the specialized handlers // as widgetEvent() already took care of that case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::MouseMove: #ifndef QT_NO_WHEELEVENT case QEvent::Wheel: #endif case QEvent::ContextMenu: case QEvent::DragEnter: case QEvent::DragMove: case QEvent::DragLeave: case QEvent::Drop: return false; default: break; } return QScrollArea::viewportEvent(e); } static void setInPaintEventFlag(QWidget *w, bool b = true, bool recurse = true) { w->setAttribute(Qt::WA_WState_InPaintEvent, b); if (!recurse) { return; } if (qobject_cast(w)) { setInPaintEventFlag(static_cast(w)->widget(), b, false); setInPaintEventFlag(static_cast(w)->horizontalScrollBar(), b, false); setInPaintEventFlag(static_cast(w)->verticalScrollBar(), b, false); return; } foreach (QObject *cw, w->children()) { if (cw->isWidgetType() && ! static_cast(cw)->isWindow() && !(static_cast(cw)->windowModality() & Qt::ApplicationModal)) { setInPaintEventFlag(static_cast(cw), b); } } } bool KHTMLView::eventFilter(QObject *o, QEvent *e) { if (e->type() == QEvent::ShortcutOverride) { QKeyEvent *ke = (QKeyEvent *) e; if (m_part->isEditable() || m_part->isCaretMode() || (m_part->xmlDocImpl() && m_part->xmlDocImpl()->focusNode() && m_part->xmlDocImpl()->focusNode()->isContentEditable())) { if ((ke->modifiers() & Qt::ControlModifier) || (ke->modifiers() & Qt::ShiftModifier)) { switch (ke->key()) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Home: case Qt::Key_End: ke->accept(); return true; default: break; } } } } if (e->type() == QEvent::Leave) { if (d->cursorIconWidget) { d->cursorIconWidget->hide(); } m_part->resetHoverText(); } QWidget *view = widget(); if (o == view) { if (widgetEvent(e)) { return true; } else if (e->type() == QEvent::Resize) { updateScrollBars(); return false; } } else if (o->isWidgetType()) { QWidget *v = static_cast(o); QWidget *c = v; while (v && v != view) { c = v; v = v->parentWidget(); } KHTMLWidget *k = dynamic_cast(c); if (v && k && k->m_kwp->isRedirected()) { bool block = false; #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) bool isUpdate = false; #endif QWidget *w = static_cast(o); switch (e->type()) { case QEvent::UpdateRequest: { // implicitly call qt_syncBackingStore(w) static_cast(w)->publicEvent(e); block = true; break; } case QEvent::UpdateLater: #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) isUpdate = true; #endif // no break; case QEvent::Paint: if (!allowWidgetPaintEvents) { // eat the event. Like this we can control exactly when the widget // gets repainted. block = true; int x = 0, y = 0; QWidget *v = w; while (v && v->parentWidget() != view) { x += v->x(); y += v->y(); v = v->parentWidget(); } QPoint ap = k->m_kwp->absolutePos(); x += ap.x(); y += ap.y(); #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) QRect pr = isUpdate ? static_cast(e)->region().boundingRect() : static_cast(e)->rect(); bool asap = !d->contentsMoving && qobject_cast(c); if (isUpdate) { setInPaintEventFlag(w, false); if (asap) { w->repaint(static_cast(e)->region()); } else { w->update(static_cast(e)->region()); } setInPaintEventFlag(w); } // QScrollView needs fast repaints if (asap && !isUpdate && !d->painting && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer() && !static_cast(m_part->xmlDocImpl()->renderer())->needsLayout()) { repaintContents(x + pr.x(), y + pr.y(), pr.width(), pr.height() + 1); // ### investigate that +1 (shows up when // updating e.g a textarea's blinking cursor) } else if (!d->painting) { scheduleRepaint(x + pr.x(), y + pr.y(), pr.width(), pr.height() + 1, asap); } #endif } break; case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: { if (0 && w->parentWidget() == view && !qobject_cast(w) && !::qobject_cast(w)) { QMouseEvent *me = static_cast(e); QPoint pt = w->mapTo(view, me->pos()); QMouseEvent me2(me->type(), pt, me->button(), me->buttons(), me->modifiers()); if (e->type() == QEvent::MouseMove) { mouseMoveEvent(&me2); } else if (e->type() == QEvent::MouseButtonPress) { mousePressEvent(&me2); } else if (e->type() == QEvent::MouseButtonRelease) { mouseReleaseEvent(&me2); } else { mouseDoubleClickEvent(&me2); } block = true; } break; } case QEvent::KeyPress: case QEvent::KeyRelease: if (w->parentWidget() == view && !qobject_cast(w)) { QKeyEvent *ke = static_cast(e); if (e->type() == QEvent::KeyPress) { keyPressEvent(ke); ke->accept(); } else { keyReleaseEvent(ke); ke->accept(); } block = true; } if (qobject_cast(w->parentWidget()) && e->type() == QEvent::KeyPress) { // Since keypress events on the upload widget will // be forwarded to the lineedit anyway, // block the original copy at this level to prevent // double-emissions of events it doesn't accept e->ignore(); block = true; } break; case QEvent::FocusIn: case QEvent::FocusOut: { QPoint dummy; KHTMLView *root = m_kwp->rootViewPos(dummy); if (!root) { root = this; } block = static_cast(e)->reason() != Qt::MouseFocusReason || root->underMouse(); break; } default: break; } if (block) { //qDebug("eating event"); return true; } } } // qCDebug(KHTML_LOG) <<"passing event on to sv event filter object=" << o->className() << " event=" << e->type(); return QScrollArea::eventFilter(o, e); } bool KHTMLView::widgetEvent(QEvent *e) { switch (e->type()) { case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseButtonDblClick: case QEvent::MouseMove: case QEvent::Paint: #ifndef QT_NO_WHEELEVENT case QEvent::Wheel: #endif case QEvent::ContextMenu: case QEvent::DragEnter: case QEvent::DragMove: case QEvent::DragLeave: case QEvent::Drop: return QFrame::event(e); case QEvent::ChildPolished: { // we need to install an event filter on all children of the widget() to // be able to get correct stacking of children within the document. QObject *c = static_cast(e)->child(); if (c->isWidgetType()) { QWidget *w = static_cast(c); // don't install the event filter on toplevels if (!(w->windowFlags() & Qt::Window) && !(w->windowModality() & Qt::ApplicationModal)) { KHTMLWidget *k = dynamic_cast(w); if (k && k->m_kwp->isRedirected()) { w->unsetCursor(); handleWidget(w, this); } } } break; } case QEvent::Move: { if (static_cast(e)->pos() != QPoint(0, 0)) { widget()->move(0, 0); updateScrollBars(); return true; } break; } default: break; } return false; } bool KHTMLView::hasLayoutPending() { return d->layoutTimerId && !d->firstLayoutPending; } DOM::NodeImpl *KHTMLView::nodeUnderMouse() const { return d->underMouse; } DOM::NodeImpl *KHTMLView::nonSharedNodeUnderMouse() const { return d->underMouseNonShared; } bool KHTMLView::scrollTo(const QRect &bounds) { d->scrollingSelf = true; // so scroll events get ignored int x, y, xe, ye; x = bounds.left(); y = bounds.top(); xe = bounds.right(); ye = bounds.bottom(); //qCDebug(KHTML_LOG)<<"scrolling coords: x="<pseudoFocusNode == KHTMLViewPrivate::PFTop) { newFocusNode = doc->nextFocusNode(oldFocusNode); } } else { if (oldFocusNode || d->pseudoFocusNode == KHTMLViewPrivate::PFBottom) { newFocusNode = doc->previousFocusNode(oldFocusNode); } } bool targetVisible = false; if (!newFocusNode) { if (next) { targetVisible = scrollTo(QRect(contentsX() + visibleWidth() / 2, contentsHeight() - d->borderY, 0, 0)); } else { targetVisible = scrollTo(QRect(contentsX() + visibleWidth() / 2, d->borderY, 0, 0)); } } else { // if it's an editable element, activate the caret if (!m_part->isCaretMode() && newFocusNode->isContentEditable()) { // qCDebug(KHTML_LOG) << "show caret! fn: " << newFocusNode->nodeName().string(); m_part->clearCaretRectIfNeeded(); m_part->d->editor_context.m_selection.moveTo(Position(newFocusNode, 0L)); m_part->setCaretVisible(true); } else { m_part->setCaretVisible(false); // qCDebug(KHTML_LOG) << "hide caret! fn: " << newFocusNode->nodeName().string(); } m_part->notifySelectionChanged(); targetVisible = scrollTo(newFocusNode->getRect()); } if (targetVisible) { //qCDebug(KHTML_LOG) << " target reached.\n"; d->tabMovePending = false; m_part->xmlDocImpl()->setFocusNode(newFocusNode); if (newFocusNode) { Node guard(newFocusNode); if (!newFocusNode->hasOneRef()) { emit m_part->nodeActivated(Node(newFocusNode)); } return true; } else { d->pseudoFocusNode = next ? KHTMLViewPrivate::PFBottom : KHTMLViewPrivate::PFTop; return false; } } else { if (!d->tabMovePending) { d->lastTabbingDirection = next; } d->tabMovePending = true; return true; } } void KHTMLView::displayAccessKeys() { QVector< QChar > taken; displayAccessKeys(nullptr, this, taken, false); displayAccessKeys(nullptr, this, taken, true); } void KHTMLView::displayAccessKeys(KHTMLView *caller, KHTMLView *origview, QVector< QChar > &taken, bool use_fallbacks) { QMap< ElementImpl *, QChar > fallbacks; if (use_fallbacks) { fallbacks = buildFallbackAccessKeys(); } for (NodeImpl *n = m_part->xmlDocImpl(); n != nullptr; n = n->traverseNextNode()) { if (n->isElementNode()) { ElementImpl *en = static_cast< ElementImpl * >(n); DOMString s = en->getAttribute(ATTR_ACCESSKEY); QString accesskey; if (s.length() == 1) { QChar a = s.string()[ 0 ].toUpper(); if (qFind(taken.begin(), taken.end(), a) == taken.end()) { // !contains accesskey = a; } } if (accesskey.isNull() && fallbacks.contains(en)) { QChar a = fallbacks[ en ].toUpper(); if (qFind(taken.begin(), taken.end(), a) == taken.end()) { // !contains accesskey = QString("") + a + ""; } } if (!accesskey.isNull()) { QRect rec = en->getRect(); QLabel *lab = new QLabel(accesskey, widget()); lab->setAttribute(Qt::WA_DeleteOnClose); lab->setObjectName("KHTMLAccessKey"); connect(origview, SIGNAL(hideAccessKeys()), lab, SLOT(close())); connect(this, SIGNAL(repaintAccessKeys()), lab, SLOT(repaint())); lab->setPalette(QToolTip::palette()); lab->setLineWidth(2); lab->setFrameStyle(QFrame::Box | QFrame::Plain); lab->setContentsMargins(3, 3, 3, 3); lab->adjustSize(); lab->setParent(widget()); lab->setAutoFillBackground(true); lab->move( qMin(rec.left() + rec.width() / 2 - contentsX(), contentsWidth() - lab->width()), qMin(rec.top() + rec.height() / 2 - contentsY(), contentsHeight() - lab->height())); lab->show(); taken.append(accesskey[ 0 ]); } } } if (use_fallbacks) { return; } QList frames = m_part->frames(); foreach (KParts::ReadOnlyPart *cur, frames) { if (!qobject_cast(cur)) { continue; } KHTMLPart *part = static_cast< KHTMLPart * >(cur); if (part->view() && part->view() != caller) { part->view()->displayAccessKeys(this, origview, taken, use_fallbacks); } } // pass up to the parent if (m_part->parentPart() && m_part->parentPart()->view() && m_part->parentPart()->view() != caller) { m_part->parentPart()->view()->displayAccessKeys(this, origview, taken, use_fallbacks); } } bool KHTMLView::isScrollingFromMouseWheel() const { return d->scrollingFromWheel != QPoint(-1, -1); } void KHTMLView::accessKeysTimeout() { d->accessKeysActivated = false; d->accessKeysPreActivate = false; m_part->setStatusBarText(QString(), KHTMLPart::BarOverrideText); emit hideAccessKeys(); } // Handling of the HTML accesskey attribute. bool KHTMLView::handleAccessKey(const QKeyEvent *ev) { // Qt interprets the keyevent also with the modifiers, and ev->text() matches that, // but this code must act as if the modifiers weren't pressed QChar c; if (ev->key() >= Qt::Key_A && ev->key() <= Qt::Key_Z) { c = 'A' + ev->key() - Qt::Key_A; } else if (ev->key() >= Qt::Key_0 && ev->key() <= Qt::Key_9) { c = '0' + ev->key() - Qt::Key_0; } else { // TODO fake XKeyEvent and XLookupString ? // This below seems to work e.g. for eacute though. if (ev->text().length() == 1) { c = ev->text()[ 0 ]; } } if (c.isNull()) { return false; } return focusNodeWithAccessKey(c); } bool KHTMLView::focusNodeWithAccessKey(QChar c, KHTMLView *caller) { DocumentImpl *doc = m_part->xmlDocImpl(); if (!doc) { return false; } ElementImpl *node = doc->findAccessKeyElement(c); if (!node) { QList frames = m_part->frames(); foreach (KParts::ReadOnlyPart *cur, frames) { if (!qobject_cast(cur)) { continue; } KHTMLPart *part = static_cast< KHTMLPart * >(cur); if (part->view() && part->view() != caller && part->view()->focusNodeWithAccessKey(c, this)) { return true; } } // pass up to the parent if (m_part->parentPart() && m_part->parentPart()->view() && m_part->parentPart()->view() != caller && m_part->parentPart()->view()->focusNodeWithAccessKey(c, this)) { return true; } if (caller == nullptr) { // the active frame (where the accesskey was pressed) const QMap< ElementImpl *, QChar > fallbacks = buildFallbackAccessKeys(); for (QMap< ElementImpl *, QChar >::ConstIterator it = fallbacks.begin(); it != fallbacks.end(); ++it) if (*it == c) { node = it.key(); break; } } if (node == nullptr) { return false; } } // Scroll the view as necessary to ensure that the new focus node is visible QRect r = node->getRect(); ensureVisible(r.right(), r.bottom()); ensureVisible(r.left(), r.top()); Node guard(node); if (node->isFocusable()) { if (node->id() == ID_LABEL) { // if Accesskey is a label, give focus to the label's referrer. node = static_cast(static_cast< HTMLLabelElementImpl * >(node)->getFormElement()); if (!node) { return true; } guard = node; } // Set focus node on the document m_part->xmlDocImpl()->setFocusNode(node); if (node != nullptr && node->hasOneRef()) { // deleted, only held by guard return true; } emit m_part->nodeActivated(Node(node)); if (node != nullptr && node->hasOneRef()) { return true; } } switch (node->id()) { case ID_A: static_cast< HTMLAnchorElementImpl * >(node)->click(); break; case ID_INPUT: static_cast< HTMLInputElementImpl * >(node)->click(); break; case ID_BUTTON: static_cast< HTMLButtonElementImpl * >(node)->click(); break; case ID_AREA: static_cast< HTMLAreaElementImpl * >(node)->click(); break; case ID_TEXTAREA: break; // just focusing it is enough case ID_LEGEND: // TODO break; } return true; } static QString getElementText(NodeImpl *start, bool after) { QString ret; // nextSibling(), to go after e.g. for (NodeImpl *n = after ? start->nextSibling() : start->traversePreviousNode(); n != nullptr; n = after ? n->traverseNextNode() : n->traversePreviousNode()) { if (n->isTextNode()) { if (after) { ret += static_cast< TextImpl * >(n)->toString().string(); } else { ret.prepend(static_cast< TextImpl * >(n)->toString().string()); } } else { switch (n->id()) { case ID_A: case ID_FONT: case ID_TT: case ID_U: case ID_B: case ID_I: case ID_S: case ID_STRIKE: case ID_BIG: case ID_SMALL: case ID_EM: case ID_STRONG: case ID_DFN: case ID_CODE: case ID_SAMP: case ID_KBD: case ID_VAR: case ID_CITE: case ID_ABBR: case ID_ACRONYM: case ID_SUB: case ID_SUP: case ID_SPAN: case ID_NOBR: case ID_WBR: break; case ID_TD: if (ret.trimmed().isEmpty()) { break; } // fall through default: return ret.simplified(); } } } return ret.simplified(); } static QMap< NodeImpl *, QString > buildLabels(NodeImpl *start) { QMap< NodeImpl *, QString > ret; for (NodeImpl *n = start; n != nullptr; n = n->traverseNextNode()) { if (n->id() == ID_LABEL) { HTMLLabelElementImpl *label = static_cast< HTMLLabelElementImpl * >(n); NodeImpl *labelfor = label->getFormElement(); if (labelfor) { ret[ labelfor ] = label->innerText().string().simplified(); } } } return ret; } namespace khtml { struct AccessKeyData { ElementImpl *element; QString text; QString url; int priority; // 10(highest) - 0(lowest) }; } QMap< ElementImpl *, QChar > KHTMLView::buildFallbackAccessKeys() const { // build a list of all possible candidate elements that could use an accesskey QLinkedList< AccessKeyData > data; // Note: this has to be a list type that keep iterators valid // when other entries are removed QMap< NodeImpl *, QString > labels = buildLabels(m_part->xmlDocImpl()); QMap< QString, QChar > hrefs; for (NodeImpl *n = m_part->xmlDocImpl(); n != nullptr; n = n->traverseNextNode()) { if (n->isElementNode()) { ElementImpl *element = static_cast< ElementImpl * >(n); if (element->renderer() == nullptr) { continue; // not visible } QString text; QString url; int priority = 0; bool ignore = false; bool text_after = false; bool text_before = false; switch (element->id()) { case ID_A: url = element->getAttribute(ATTR_HREF).trimSpaces().string(); if (url.isEmpty()) { // doesn't have href, it's only an anchor continue; } text = static_cast< HTMLElementImpl * >(element)->innerText().string().simplified(); priority = 2; break; case ID_INPUT: { HTMLInputElementImpl *in = static_cast< HTMLInputElementImpl * >(element); switch (in->inputType()) { case HTMLInputElementImpl::SUBMIT: text = in->value().string(); if (text.isEmpty()) { text = i18n("Submit"); } priority = 7; break; case HTMLInputElementImpl::IMAGE: text = in->altText().string(); priority = 7; break; case HTMLInputElementImpl::BUTTON: text = in->value().string(); priority = 5; break; case HTMLInputElementImpl::RESET: text = in->value().string(); if (text.isEmpty()) { text = i18n("Reset"); } priority = 5; break; case HTMLInputElementImpl::HIDDEN: ignore = true; break; case HTMLInputElementImpl::CHECKBOX: case HTMLInputElementImpl::RADIO: text_after = true; priority = 5; break; case HTMLInputElementImpl::TEXT: case HTMLInputElementImpl::PASSWORD: case HTMLInputElementImpl::FILE: text_before = true; priority = 5; break; default: priority = 5; break; } break; } case ID_BUTTON: text = static_cast< HTMLElementImpl * >(element)->innerText().string().simplified(); switch (static_cast< HTMLButtonElementImpl * >(element)->buttonType()) { case HTMLButtonElementImpl::SUBMIT: if (text.isEmpty()) { text = i18n("Submit"); } priority = 7; break; case HTMLButtonElementImpl::RESET: if (text.isEmpty()) { text = i18n("Reset"); } priority = 5; break; default: priority = 5; break; } break; case ID_SELECT: // these don't have accesskey attribute, but quick access may be handy text_before = true; text_after = true; priority = 5; break; case ID_FRAME: ignore = true; break; default: ignore = !element->isFocusable(); priority = 2; break; } if (ignore) { continue; } // build map of manually assigned accesskeys and their targets DOMString akey = element->getAttribute(ATTR_ACCESSKEY); if (akey.length() == 1) { hrefs[url] = akey.string()[ 0 ].toUpper(); continue; // has accesskey set, ignore } if (text.isNull() && labels.contains(element)) { text = labels[ element ]; } if (text.isNull() && text_before) { text = getElementText(element, false); } if (text.isNull() && text_after) { text = getElementText(element, true); } text = text.trimmed(); // increase priority of items which have explicitly specified accesskeys in the config const QList< QPair< QString, QChar > > priorities = m_part->settings()->fallbackAccessKeysAssignments(); for (QList< QPair< QString, QChar > >::ConstIterator it = priorities.begin(); it != priorities.end(); ++it) { if (text == (*it).first) { priority = 10; } } AccessKeyData tmp = { element, text, url, priority }; data.append(tmp); } } QList< QChar > keys; for (char c = 'A'; c <= 'Z'; ++c) { keys << c; } for (char c = '0'; c <= '9'; ++c) { keys << c; } for (NodeImpl *n = m_part->xmlDocImpl(); n != nullptr; n = n->traverseNextNode()) { if (n->isElementNode()) { ElementImpl *en = static_cast< ElementImpl * >(n); DOMString s = en->getAttribute(ATTR_ACCESSKEY); if (s.length() == 1) { QChar c = s.string()[ 0 ].toUpper(); keys.removeAll(c); // remove manually assigned accesskeys } } } QMap< ElementImpl *, QChar > ret; for (int priority = 10; priority >= 0; --priority) { for (QLinkedList< AccessKeyData >::Iterator it = data.begin(); it != data.end(); ) { if ((*it).priority != priority) { ++it; continue; } if (keys.isEmpty()) { break; } QString text = (*it).text; QChar key; const QString url = (*it).url; // an identical link already has an accesskey assigned if (hrefs.contains(url)) { it = data.erase(it); continue; } if (!text.isEmpty()) { const QList< QPair< QString, QChar > > priorities = m_part->settings()->fallbackAccessKeysAssignments(); for (QList< QPair< QString, QChar > >::ConstIterator it = priorities.begin(); it != priorities.end(); ++it) if (text == (*it).first && keys.contains((*it).second)) { key = (*it).second; break; } } // try first to select the first character as the accesskey, // then first character of the following words, // and then simply the first free character if (key.isNull() && !text.isEmpty()) { const QStringList words = text.split(' '); for (QStringList::ConstIterator it = words.begin(); it != words.end(); ++it) { if (keys.contains((*it)[ 0 ].toUpper())) { key = (*it)[ 0 ].toUpper(); break; } } } if (key.isNull() && !text.isEmpty()) { for (int i = 0; i < text.length(); ++i) { if (keys.contains(text[ i ].toUpper())) { key = text[ i ].toUpper(); break; } } } if (key.isNull()) { key = keys.front(); } ret[(*it).element ] = key; keys.removeAll(key); it = data.erase(it); // assign the same accesskey also to other elements pointing to the same url if (!url.isEmpty() && !url.startsWith("javascript:", Qt::CaseInsensitive)) { for (QLinkedList< AccessKeyData >::Iterator it2 = data.begin(); it2 != data.end(); ) { if ((*it2).url == url) { ret[(*it2).element ] = key; if (it == it2) { ++it; } it2 = data.erase(it2); } else { ++it2; } } } } } return ret; } void KHTMLView::setMediaType(const QString &medium) { m_medium = medium; } QString KHTMLView::mediaType() const { return m_medium; } bool KHTMLView::pagedMode() const { return d->paged; } void KHTMLView::setWidgetVisible(RenderWidget *w, bool vis) { if (vis) { d->visibleWidgets.insert(w, w->widget()); } else { d->visibleWidgets.remove(w); } } bool KHTMLView::needsFullRepaint() const { return d->needsFullRepaint; } namespace { class QPointerDeleter { public: explicit QPointerDeleter(QObject *o) : obj(o) {} ~QPointerDeleter() { delete obj; } private: const QPointer obj; }; } void KHTMLView::print(bool quick) +{ + QPrinter printer; + print(&printer, quick); +} + +void KHTMLView::print(QPrinter *_printer, bool quick) { if (!m_part->xmlDocImpl()) { return; } khtml::RenderCanvas *root = static_cast(m_part->xmlDocImpl()->renderer()); if (!root) { return; } - QPrinter printer; + QPrinter &printer = *_printer; QPointer dialog(new QPrintDialog(&printer, this)); QPointer printSettings(new KHTMLPrintSettings(dialog)); //XXX: doesn't save settings between prints like this dialog->setOptionTabs(QList() << printSettings.data()); const QPointerDeleter dialogDeleter(dialog); QString docname = m_part->xmlDocImpl()->URL().toDisplayString(); if (!docname.isEmpty()) { docname = KStringHandler::csqueeze(docname, 80); } if (quick || (dialog->exec() && dialog)) { /*'this' and thus dialog might have been deleted while exec()!*/ viewport()->setCursor(Qt::WaitCursor); // only viewport(), no QApplication::, otherwise we get the busy cursor in kdeprint's dialogs // set up KPrinter printer.setFullPage(false); printer.setCreator(QString("KDE %1.%2.%3 HTML Library").arg(KHTML_VERSION_MAJOR).arg(KHTML_VERSION_MINOR).arg(KHTML_VERSION_PATCH)); printer.setDocName(docname); QPainter *p = new QPainter; p->begin(&printer); khtml::setPrintPainter(p); m_part->xmlDocImpl()->setPaintDevice(&printer); QString oldMediaType = mediaType(); setMediaType("print"); // We ignore margin settings for html and body when printing // and use the default margins from the print-system // (In Qt 3.0.x the default margins are hardcoded in Qt) m_part->xmlDocImpl()->setPrintStyleSheet(printSettings->printFriendly() ? "* { background-image: none !important;" " background-color: white !important;" " color: black !important; }" "body { margin: 0px !important; }" "html { margin: 0px !important; }" : "body { margin: 0px !important; }" "html { margin: 0px !important; }" ); // qCDebug(KHTML_LOG) << "printing: physical page width = " << printer.width() // << " height = " << printer.height(); root->setStaticMode(true); root->setPagedMode(true); root->setWidth(printer.width()); // root->setHeight(printer.height()); root->setPageTop(0); root->setPageBottom(0); d->paged = true; m_part->xmlDocImpl()->styleSelector()->computeFontSizes(printer.logicalDpiY(), 100); m_part->xmlDocImpl()->updateStyleSelector(); root->setPrintImages(printSettings->printImages()); root->makePageBreakAvoidBlocks(); root->setNeedsLayoutAndMinMaxRecalc(); root->layout(); // check sizes ask for action.. (scale or clip) bool printHeader = printSettings->printHeader(); int headerHeight = 0; QFont headerFont("Sans Serif", 8); QString headerLeft = QDate::currentDate().toString(Qt::DefaultLocaleShortDate); QString headerMid = docname; QString headerRight; if (printHeader) { p->setFont(headerFont); headerHeight = (p->fontMetrics().lineSpacing() * 3) / 2; } // ok. now print the pages. // qCDebug(KHTML_LOG) << "printing: html page width = " << root->docWidth() // << " height = " << root->docHeight(); // qCDebug(KHTML_LOG) << "printing: margins left = " << printer.pageRect().left() - printer.paperRect().left() // << " top = " << printer.pageRect().top() - printer.paperRect().top(); // qCDebug(KHTML_LOG) << "printing: paper width = " << printer.width() // << " height = " << printer.height(); // if the width is too large to fit on the paper we just scale // the whole thing. int pageWidth = printer.width(); int pageHeight = printer.height(); p->setClipRect(0, 0, pageWidth, pageHeight); pageHeight -= headerHeight; #ifndef QT_NO_TRANSFORMATIONS bool scalePage = false; double scale = 0.0; if (root->docWidth() > printer.width()) { scalePage = true; scale = ((double) printer.width()) / ((double) root->docWidth()); pageHeight = (int)(pageHeight / scale); pageWidth = (int)(pageWidth / scale); headerHeight = (int)(headerHeight / scale); } #endif // qCDebug(KHTML_LOG) << "printing: scaled html width = " << pageWidth // << " height = " << pageHeight; root->setHeight(pageHeight); root->setPageBottom(pageHeight); root->setNeedsLayout(true); root->layoutIfNeeded(); // m_part->slotDebugRenderTree(); // Squeeze header to make it it on the page. if (printHeader) { int available_width = printer.width() - 10 - 2 * qMax(p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerLeft).width(), p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerRight).width()); if (available_width < 150) { available_width = 150; } int mid_width; int squeeze = 120; do { headerMid = KStringHandler::csqueeze(docname, squeeze); mid_width = p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerMid).width(); squeeze -= 10; } while (mid_width > available_width); } int top = 0; int bottom = 0; int page = 1; while (top < root->docHeight()) { if (top > 0) { printer.newPage(); } #ifndef QT_NO_TRANSFORMATIONS if (scalePage) { p->scale(scale, scale); } #endif p->save(); p->setClipRect(0, 0, pageWidth, headerHeight); if (printHeader) { int dy = p->fontMetrics().lineSpacing(); p->setPen(Qt::black); p->setFont(headerFont); headerRight = QString("#%1").arg(page); p->drawText(0, 0, printer.width(), dy, Qt::AlignLeft, headerLeft); p->drawText(0, 0, printer.width(), dy, Qt::AlignHCenter, headerMid); p->drawText(0, 0, printer.width(), dy, Qt::AlignRight, headerRight); } p->restore(); p->translate(0, headerHeight - top); bottom = top + pageHeight; root->setPageTop(top); root->setPageBottom(bottom); root->setPageNumber(page); root->layer()->paint(p, QRect(0, top, pageWidth, pageHeight)); // qCDebug(KHTML_LOG) << "printed: page " << page <<" bottom At = " << bottom; top = bottom; p->resetTransform(); page++; } p->end(); delete p; // and now reset the layout to the usual one... root->setPagedMode(false); root->setStaticMode(false); d->paged = false; khtml::setPrintPainter(nullptr); setMediaType(oldMediaType); m_part->xmlDocImpl()->setPaintDevice(this); m_part->xmlDocImpl()->styleSelector()->computeFontSizes(m_part->xmlDocImpl()->logicalDpiY(), m_part->fontScaleFactor()); m_part->xmlDocImpl()->updateStyleSelector(); viewport()->unsetCursor(); } } void KHTMLView::slotPaletteChanged() { if (!m_part->xmlDocImpl()) { return; } DOM::DocumentImpl *document = m_part->xmlDocImpl(); if (!document->isHTMLDocument()) { return; } khtml::RenderCanvas *root = static_cast(document->renderer()); if (!root) { return; } root->style()->resetPalette(); NodeImpl *body = static_cast(document)->body(); if (!body) { return; } body->setChanged(true); body->recalcStyle(NodeImpl::Force); } void KHTMLView::paint(QPainter *p, const QRect &rc, int yOff, bool *more) { if (!m_part->xmlDocImpl()) { return; } khtml::RenderCanvas *root = static_cast(m_part->xmlDocImpl()->renderer()); if (!root) { return; } #ifdef SPEED_DEBUG d->firstRepaintPending = false; #endif QPaintDevice *opd = m_part->xmlDocImpl()->paintDevice(); m_part->xmlDocImpl()->setPaintDevice(p->device()); root->setPagedMode(true); root->setStaticMode(true); root->setWidth(rc.width()); // save() QRegion creg = p->clipRegion(); QTransform t = p->worldTransform(); QRect w = p->window(); QRect v = p->viewport(); bool vte = p->viewTransformEnabled(); bool wme = p->worldMatrixEnabled(); p->setClipRect(rc); p->translate(rc.left(), rc.top()); double scale = ((double) rc.width() / (double) root->docWidth()); int height = (int)((double) rc.height() / scale); #ifndef QT_NO_TRANSFORMATIONS p->scale(scale, scale); #endif root->setPageTop(yOff); root->setPageBottom(yOff + height); root->layer()->paint(p, QRect(0, yOff, root->docWidth(), height)); if (more) { *more = yOff + height < root->docHeight(); } // restore() p->setWorldTransform(t); p->setWindow(w); p->setViewport(v); p->setViewTransformEnabled(vte); p->setWorldMatrixEnabled(wme); if (!creg.isEmpty()) { p->setClipRegion(creg); } else { p->setClipRegion(QRegion(), Qt::NoClip); } root->setPagedMode(false); root->setStaticMode(false); m_part->xmlDocImpl()->setPaintDevice(opd); } void KHTMLView::render(QPainter *p, const QRect &r, const QPoint &off) { #ifdef SPEED_DEBUG d->firstRepaintPending = false; #endif QRect clip(off.x() + r.x(), off.y() + r.y(), r.width(), r.height()); if (!m_part || !m_part->xmlDocImpl() || !m_part->xmlDocImpl()->renderer()) { p->fillRect(clip, palette().brush(QPalette::Active, QPalette::Base)); return; } QPaintDevice *opd = m_part->xmlDocImpl()->paintDevice(); m_part->xmlDocImpl()->setPaintDevice(p->device()); // save() QRegion creg = p->clipRegion(); QTransform t = p->worldTransform(); QRect w = p->window(); QRect v = p->viewport(); bool vte = p->viewTransformEnabled(); bool wme = p->worldMatrixEnabled(); p->setClipRect(clip); QRect rect = r.translated(contentsX(), contentsY()); p->translate(off.x() - contentsX(), off.y() - contentsY()); m_part->xmlDocImpl()->renderer()->layer()->paint(p, rect); // restore() p->setWorldTransform(t); p->setWindow(w); p->setViewport(v); p->setViewTransformEnabled(vte); p->setWorldMatrixEnabled(wme); if (!creg.isEmpty()) { p->setClipRegion(creg); } else { p->setClipRegion(QRegion(), Qt::NoClip); } m_part->xmlDocImpl()->setPaintDevice(opd); } void KHTMLView::setHasStaticBackground(bool partial) { // full static iframe is irreversible for now if (d->staticWidget == KHTMLViewPrivate::SBFull && m_kwp->isRedirected()) { return; } d->staticWidget = partial ? KHTMLViewPrivate::SBPartial : KHTMLViewPrivate::SBFull; } void KHTMLView::setHasNormalBackground() { // full static iframe is irreversible for now if (d->staticWidget == KHTMLViewPrivate::SBFull && m_kwp->isRedirected()) { return; } d->staticWidget = KHTMLViewPrivate::SBNone; } void KHTMLView::addStaticObject(bool fixed) { if (fixed) { d->fixedObjectsCount++; } else { d->staticObjectsCount++; } setHasStaticBackground(true /*partial*/); } void KHTMLView::removeStaticObject(bool fixed) { if (fixed) { d->fixedObjectsCount--; } else { d->staticObjectsCount--; } assert(d->fixedObjectsCount >= 0 && d->staticObjectsCount >= 0); if (!d->staticObjectsCount && !d->fixedObjectsCount) { setHasNormalBackground(); } else { setHasStaticBackground(true /*partial*/); } } void KHTMLView::setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy) { #ifndef KHTML_NO_SCROLLBARS d->vpolicy = policy; QScrollArea::setVerticalScrollBarPolicy(policy); #else Q_UNUSED(policy); #endif } void KHTMLView::setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy) { #ifndef KHTML_NO_SCROLLBARS d->hpolicy = policy; QScrollArea::setHorizontalScrollBarPolicy(policy); #else Q_UNUSED(policy); #endif } void KHTMLView::restoreScrollBar() { int ow = visibleWidth(); QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); if (visibleWidth() != ow) { layout(); } d->prevScrollbarVisible = verticalScrollBar()->isVisible(); } QStringList KHTMLView::formCompletionItems(const QString &name) const { if (!m_part->settings()->isFormCompletionEnabled()) { return QStringList(); } if (!d->formCompletions) { d->formCompletions = new KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "khtml/formcompletions"); } return d->formCompletions->group("").readEntry(name, QStringList()); } void KHTMLView::clearCompletionHistory(const QString &name) { if (!d->formCompletions) { d->formCompletions = new KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "khtml/formcompletions"); } d->formCompletions->group("").writeEntry(name, ""); d->formCompletions->sync(); } void KHTMLView::addFormCompletionItem(const QString &name, const QString &value) { if (!m_part->settings()->isFormCompletionEnabled()) { return; } // don't store values that are all numbers or just numbers with // dashes or spaces as those are likely credit card numbers or // something similar bool cc_number(true); for (int i = 0; i < value.length(); ++i) { QChar c(value[i]); if (!c.isNumber() && c != '-' && !c.isSpace()) { cc_number = false; break; } } if (cc_number) { return; } QStringList items = formCompletionItems(name); if (!items.contains(value)) { items.prepend(value); } while ((int)items.count() > m_part->settings()->maxFormCompletionItems()) { items.erase(items.isEmpty() ? items.end() : --items.end()); } d->formCompletions->group("").writeEntry(name, items); } void KHTMLView::addNonPasswordStorableSite(const QString &host) { if (!d->formCompletions) { d->formCompletions = new KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "khtml/formcompletions"); } KConfigGroup cg(d->formCompletions, "NonPasswordStorableSites"); QStringList sites = cg.readEntry("Sites", QStringList()); sites.append(host); cg.writeEntry("Sites", sites); cg.sync(); } void KHTMLView::delNonPasswordStorableSite(const QString &host) { if (!d->formCompletions) { d->formCompletions = new KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "khtml/formcompletions"); } KConfigGroup cg(d->formCompletions, "NonPasswordStorableSites"); QStringList sites = cg.readEntry("Sites", QStringList()); sites.removeOne(host); cg.writeEntry("Sites", sites); cg.sync(); } bool KHTMLView::nonPasswordStorableSite(const QString &host) const { if (!d->formCompletions) { d->formCompletions = new KConfig(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "khtml/formcompletions"); } QStringList sites = d->formCompletions->group("NonPasswordStorableSites").readEntry("Sites", QStringList()); return (sites.indexOf(host) != -1); } // returns true if event should be swallowed bool KHTMLView::dispatchMouseEvent(int eventId, DOM::NodeImpl *targetNode, DOM::NodeImpl *targetNodeNonShared, bool cancelable, int detail, QMouseEvent *_mouse, bool setUnder, int mouseEventType, int orient) { // if the target node is a text node, dispatch on the parent node - rdar://4196646 (and #76948) if (targetNode && targetNode->isTextNode()) { targetNode = targetNode->parentNode(); } if (d->underMouse) { d->underMouse->deref(); } d->underMouse = targetNode; if (d->underMouse) { d->underMouse->ref(); } if (d->underMouseNonShared) { d->underMouseNonShared->deref(); } d->underMouseNonShared = targetNodeNonShared; if (d->underMouseNonShared) { d->underMouseNonShared->ref(); } bool isWheelEvent = (mouseEventType == DOM::NodeImpl::MouseWheel); int exceptioncode = 0; int pageX = _mouse->x(); int pageY = _mouse->y(); revertTransforms(pageX, pageY); int clientX = pageX - contentsX(); int clientY = pageY - contentsY(); int screenX = _mouse->globalX(); int screenY = _mouse->globalY(); int button = -1; switch (_mouse->button()) { case Qt::LeftButton: button = 0; break; case Qt::MidButton: button = 1; break; case Qt::RightButton: button = 2; break; default: break; } if (d->accessKeysEnabled && d->accessKeysPreActivate && button != -1) { d->accessKeysPreActivate = false; } bool ctrlKey = (_mouse->modifiers() & Qt::ControlModifier); bool altKey = (_mouse->modifiers() & Qt::AltModifier); bool shiftKey = (_mouse->modifiers() & Qt::ShiftModifier); bool metaKey = (_mouse->modifiers() & Qt::MetaModifier); // mouseout/mouseover if (setUnder && d->oldUnderMouse != targetNode) { if (d->oldUnderMouse && d->oldUnderMouse->document() != m_part->xmlDocImpl()) { d->oldUnderMouse->deref(); d->oldUnderMouse = nullptr; } // send mouseout event to the old node if (d->oldUnderMouse) { // send mouseout event to the old node MouseEventImpl *me = new MouseEventImpl(EventImpl::MOUSEOUT_EVENT, true, true, m_part->xmlDocImpl()->defaultView(), 0, screenX, screenY, clientX, clientY, pageX, pageY, ctrlKey, altKey, shiftKey, metaKey, button, targetNode); me->ref(); d->oldUnderMouse->dispatchEvent(me, exceptioncode, true); me->deref(); } // send mouseover event to the new node if (targetNode) { MouseEventImpl *me = new MouseEventImpl(EventImpl::MOUSEOVER_EVENT, true, true, m_part->xmlDocImpl()->defaultView(), 0, screenX, screenY, clientX, clientY, pageX, pageY, ctrlKey, altKey, shiftKey, metaKey, button, d->oldUnderMouse); me->ref(); targetNode->dispatchEvent(me, exceptioncode, true); me->deref(); } if (d->oldUnderMouse) { d->oldUnderMouse->deref(); } d->oldUnderMouse = targetNode; if (d->oldUnderMouse) { d->oldUnderMouse->ref(); } } bool swallowEvent = false; if (targetNode) { // if the target node is a disabled widget, we don't want any full-blown mouse events if (targetNode->isGenericFormElement() && static_cast(targetNode)->disabled()) { return true; } // send the actual event bool dblclick = (eventId == EventImpl::CLICK_EVENT && _mouse->type() == QEvent::MouseButtonDblClick); MouseEventImpl *me = new MouseEventImpl(static_cast(eventId), true, cancelable, m_part->xmlDocImpl()->defaultView(), detail, screenX, screenY, clientX, clientY, pageX, pageY, ctrlKey, altKey, shiftKey, metaKey, button, nullptr, isWheelEvent ? nullptr : _mouse, dblclick, isWheelEvent ? static_cast(orient) : MouseEventImpl::ONone); me->ref(); if (!d->m_mouseEventsTarget && RenderLayer::gScrollBar && eventId == EventImpl::MOUSEDOWN_EVENT) // button is pressed inside a layer scrollbar, so make it the target for future mousemove events until released { d->m_mouseEventsTarget = RenderLayer::gScrollBar; } if (d->m_mouseEventsTarget && qobject_cast(d->m_mouseEventsTarget) && dynamic_cast(static_cast(d->m_mouseEventsTarget))) { // we have a sticky mouse event target and it is a layer's scrollbar. Forward events manually. // ### should use the dom KHTMLWidget *w = dynamic_cast(static_cast(d->m_mouseEventsTarget)); QPoint p = w->m_kwp->absolutePos(); QMouseEvent fw(_mouse->type(), QPoint(pageX, pageY) - p, _mouse->button(), _mouse->buttons(), _mouse->modifiers()); static_cast(static_cast(d->m_mouseEventsTarget))->sendEvent(&fw); if (_mouse->type() == QMouseEvent::MouseButtonPress && _mouse->button() == Qt::RightButton) { QContextMenuEvent cme(QContextMenuEvent::Mouse, p); static_cast(static_cast(d->m_mouseEventsTarget))->sendEvent(&cme); d->m_mouseEventsTarget = nullptr; } swallowEvent = true; } else { targetNode->dispatchEvent(me, exceptioncode, true); bool defaultHandled = me->defaultHandled(); if (defaultHandled || me->defaultPrevented()) { swallowEvent = true; } } if (eventId == EventImpl::MOUSEDOWN_EVENT && !me->defaultPrevented()) { // Focus should be shifted on mouse down, not on a click. -dwh // Blur current focus node when a link/button is clicked; this // is expected by some sites that rely on onChange handlers running // from form fields before the button click is processed. DOM::NodeImpl *nodeImpl = targetNode; for (; nodeImpl && !nodeImpl->isFocusable(); nodeImpl = nodeImpl->parentNode()) { } if (nodeImpl && nodeImpl->isMouseFocusable()) { m_part->xmlDocImpl()->setFocusNode(nodeImpl); } else if (!nodeImpl || !nodeImpl->focused()) { m_part->xmlDocImpl()->setFocusNode(nullptr); } } me->deref(); } return swallowEvent; } void KHTMLView::setIgnoreWheelEvents(bool e) { d->ignoreWheelEvents = e; } #ifndef QT_NO_WHEELEVENT void KHTMLView::wheelEvent(QWheelEvent *e) { // check if we should reset the state of the indicator describing if // we are currently scrolling the view as a result of wheel events if (d->scrollingFromWheel != QPoint(-1, -1) && d->scrollingFromWheel != QCursor::pos()) { d->scrollingFromWheel = d->scrollingFromWheelTimerId ? QCursor::pos() : QPoint(-1, -1); } if (d->accessKeysEnabled && d->accessKeysPreActivate) { d->accessKeysPreActivate = false; } if ((e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier) { emit zoomView(- e->delta()); e->accept(); } else if (d->firstLayoutPending) { e->accept(); } else if (!m_kwp->isRedirected() && ((e->orientation() == Qt::Vertical && ((d->ignoreWheelEvents && !verticalScrollBar()->isVisible()) || (e->delta() > 0 && contentsY() <= 0) || (e->delta() < 0 && contentsY() >= contentsHeight() - visibleHeight()))) || (e->orientation() == Qt::Horizontal && ((d->ignoreWheelEvents && !horizontalScrollBar()->isVisible()) || (e->delta() > 0 && contentsX() <= 0) || (e->delta() < 0 && contentsX() >= contentsWidth() - visibleWidth())))) && m_part->parentPart()) { if (m_part->parentPart()->view()) { m_part->parentPart()->view()->wheelEvent(e); } e->ignore(); } else { int xm = e->x(); int ym = e->y(); revertTransforms(xm, ym); DOM::NodeImpl::MouseEvent mev(e->buttons(), DOM::NodeImpl::MouseWheel); m_part->xmlDocImpl()->prepareMouseEvent(false, xm, ym, &mev); MouseEventImpl::Orientation o = MouseEventImpl::OVertical; if (e->orientation() == Qt::Horizontal) { o = MouseEventImpl::OHorizontal; } QMouseEvent _mouse(QEvent::MouseMove, e->pos(), Qt::NoButton, e->buttons(), e->modifiers()); bool swallow = dispatchMouseEvent(EventImpl::KHTML_MOUSEWHEEL_EVENT, mev.innerNode.handle(), mev.innerNonSharedNode.handle(), true, -e->delta() / 40, &_mouse, true, DOM::NodeImpl::MouseWheel, o); if (swallow) { return; } d->scrollBarMoved = true; d->scrollingFromWheel = QCursor::pos(); if (d->smoothScrollMode != SSMDisabled) { d->shouldSmoothScroll = true; } if (d->scrollingFromWheelTimerId) { killTimer(d->scrollingFromWheelTimerId); } d->scrollingFromWheelTimerId = startTimer(400); if (m_part->parentPart()) { // don't propagate if we are a sub-frame and our scrollbars are already at end of range bool h = (static_cast(e)->orientation() == Qt::Horizontal); bool d = (static_cast(e)->delta() < 0); QScrollBar *hsb = horizontalScrollBar(); QScrollBar *vsb = verticalScrollBar(); if ((h && ((d && hsb->value() == hsb->maximum()) || (!d && hsb->value() == hsb->minimum()))) || (!h && ((d && vsb->value() == vsb->maximum()) || (!d && vsb->value() == vsb->minimum())))) { e->accept(); return; } } QScrollArea::wheelEvent(e); } } #endif void KHTMLView::dragEnterEvent(QDragEnterEvent *ev) { // Still overridden for BC reasons only... QScrollArea::dragEnterEvent(ev); } void KHTMLView::dropEvent(QDropEvent *ev) { // Still overridden for BC reasons only... QScrollArea::dropEvent(ev); } void KHTMLView::focusInEvent(QFocusEvent *e) { DOM::NodeImpl *fn = m_part->xmlDocImpl() ? m_part->xmlDocImpl()->focusNode() : nullptr; if (fn && fn->renderer() && fn->renderer()->isWidget() && (e->reason() != Qt::MouseFocusReason) && static_cast(fn->renderer())->widget()) { static_cast(fn->renderer())->widget()->setFocus(); } m_part->setSelectionVisible(); QScrollArea::focusInEvent(e); } void KHTMLView::focusOutEvent(QFocusEvent *e) { if (m_part) { m_part->stopAutoScroll(); m_part->setSelectionVisible(false); } if (d->cursorIconWidget) { d->cursorIconWidget->hide(); } QScrollArea::focusOutEvent(e); } void KHTMLView::scrollContentsBy(int dx, int dy) { if (!dx && !dy) { return; } if (!d->firstLayoutPending && !d->complete && m_part->xmlDocImpl() && d->layoutSchedulingEnabled) { // contents scroll while we are not complete: we need to check our layout *now* khtml::RenderCanvas *root = static_cast(m_part->xmlDocImpl()->renderer()); if (root && root->needsLayout()) { unscheduleRelayout(); layout(); } } if (d->shouldSmoothScroll && d->smoothScrollMode != SSMDisabled && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer() && (d->smoothScrollMode != SSMWhenEfficient || d->smoothScrollMissedDeadlines != sWayTooMany)) { bool doSmoothScroll = (!d->staticWidget || d->smoothScrollMode == SSMEnabled); int numStaticPixels = 0; QRegion r = static_cast(m_part->xmlDocImpl()->renderer())->staticRegion(); // only do smooth scrolling if static region is relatively small if (!doSmoothScroll && d->staticWidget == KHTMLViewPrivate::SBPartial && r.rects().size() <= 10) { foreach (const QRect &rr, r.rects()) { numStaticPixels += rr.width() * rr.height(); } if ((numStaticPixels < sSmoothScrollMinStaticPixels) || (numStaticPixels * 8 < visibleWidth()*visibleHeight())) { doSmoothScroll = true; } } if (doSmoothScroll) { setupSmoothScrolling(dx, dy); return; } } if (underMouse() && QToolTip::isVisible()) { QToolTip::hideText(); } if (!d->scrollingSelf) { d->scrollBarMoved = true; d->contentsMoving = true; // ensure quick reset of contentsMoving flag scheduleRepaint(0, 0, 0, 0); } if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->documentElement()) { m_part->xmlDocImpl()->documentElement()->dispatchHTMLEvent(EventImpl::SCROLL_EVENT, true, false); } if (QApplication::isRightToLeft()) { dx = -dx; } if (!d->smoothScrolling) { d->updateContentsXY(); } else { d->contentsX -= dx; d->contentsY -= dy; } if (widget()->pos() != QPoint(0, 0)) { // qCDebug(KHTML_LOG) << "Static widget wasn't positioned at (0,0). This should NOT happen. Please report this event to developers."; widget()->move(0, 0); } QWidget *w = widget(); QPoint off; if (m_kwp->isRedirected()) { // This is a redirected sub frame. Translate to root view context KHTMLView *v = m_kwp->rootViewPos(off); if (v) { w = v->widget(); } off = viewport()->mapTo(this, off); } if (d->staticWidget) { // now remove from view the external widgets that must have completely // disappeared after dx/dy scroll delta is effective if (!d->visibleWidgets.isEmpty()) { checkExternalWidgetsPosition(); } if (d->staticWidget == KHTMLViewPrivate::SBPartial && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer()) { // static objects might be selectively repainted, like stones in flowing water QRegion r = static_cast(m_part->xmlDocImpl()->renderer())->staticRegion(); r.translate(-contentsX(), -contentsY()); QVector ar = r.rects(); for (int i = 0; i < ar.size(); ++i) { widget()->update(ar[i]); } r = QRegion(QRect(0, 0, visibleWidth(), visibleHeight())) - r; ar = r.rects(); for (int i = 0; i < ar.size(); ++i) { w->scroll(dx, dy, ar[i].translated(off)); } d->scrollExternalWidgets(dx, dy); } else { // we can't avoid a full update widget()->update(); } if (d->accessKeysActivated) { d->scrollAccessKeys(dx, dy); } return; } if (m_kwp->isRedirected()) { const QRect rect(off.x(), off.y(), visibleWidth() * d->zoomLevel / 100, visibleHeight() * d->zoomLevel / 100); w->scroll(dx, dy, rect); if (d->zoomLevel != 100) { w->update(rect); // without this update we are getting bad rendering when an iframe is zoomed in } } else { widget()->scroll(dx, dy, widget()->rect() & viewport()->rect()); } d->scrollExternalWidgets(dx, dy); if (d->accessKeysActivated) { d->scrollAccessKeys(dx, dy); } } void KHTMLView::setupSmoothScrolling(int dx, int dy) { // old or minimum speed int ddx = qMax(d->steps ? abs(d->dx) / d->steps : 0, 3); int ddy = qMax(d->steps ? abs(d->dy) / d->steps : 0, 3); // full scroll is remaining scroll plus new scroll d->dx = d->dx + dx; d->dy = d->dy + dy; if (d->dx == 0 && d->dy == 0) { d->stopScrolling(); return; } d->steps = (sSmoothScrollTime - 1) / sSmoothScrollTick + 1; if (qMax(abs(d->dx), abs(d->dy)) / d->steps < qMax(ddx, ddy)) { // Don't move slower than average 4px/step in minimum one direction // This means fewer than normal steps d->steps = qMax((abs(d->dx) + ddx - 1) / ddx, (abs(d->dy) + ddy - 1) / ddy); if (d->steps < 1) { d->steps = 1; } } d->smoothScrollStopwatch.start(); if (!d->smoothScrolling) { d->startScrolling(); scrollTick(); } } void KHTMLView::scrollTick() { if (d->dx == 0 && d->dy == 0) { d->stopScrolling(); return; } if (d->steps < 1) { d->steps = 1; } int takesteps = d->smoothScrollStopwatch.restart() / sSmoothScrollTick; int scroll_x = 0; int scroll_y = 0; if (takesteps < 1) { takesteps = 1; } if (takesteps > d->steps) { takesteps = d->steps; } for (int i = 0; i < takesteps; i++) { int ddx = (d->dx / (d->steps + 1)) * 2; int ddy = (d->dy / (d->steps + 1)) * 2; // limit step to requested scrolling distance if (abs(ddx) > abs(d->dx)) { ddx = d->dx; } if (abs(ddy) > abs(d->dy)) { ddy = d->dy; } // update remaining scroll d->dx -= ddx; d->dy -= ddy; scroll_x += ddx; scroll_y += ddy; d->steps--; } d->shouldSmoothScroll = false; scrollContentsBy(scroll_x, scroll_y); if (takesteps < 2) { d->smoothScrollMissedDeadlines = 0; } else { if (d->smoothScrollMissedDeadlines != sWayTooMany && (!m_part->xmlDocImpl() || !m_part->xmlDocImpl()->parsing())) { d->smoothScrollMissedDeadlines++; if (d->smoothScrollMissedDeadlines >= sMaxMissedDeadlines) { // we missed many deadlines in a row! // time to signal we had enough.. d->smoothScrollMissedDeadlines = sWayTooMany; } } } } void KHTMLView::addChild(QWidget *child, int x, int y) { if (!child) { return; } if (child->parent() != widget()) { child->setParent(widget()); } // ### handle pseudo-zooming of non-redirected widgets (e.g. just resize'em) child->move(x - contentsX(), y - contentsY()); } void KHTMLView::timerEvent(QTimerEvent *e) { // qCDebug(KHTML_LOG) << "timer event " << e->timerId(); if (e->timerId() == d->scrollTimerId) { if (d->scrollSuspended) { return; } switch (d->scrollDirection) { case KHTMLViewPrivate::ScrollDown: if (contentsY() + visibleHeight() >= contentsHeight()) { d->newScrollTimer(this, 0); } else { verticalScrollBar()->setValue(verticalScrollBar()->value() + d->scrollBy); } break; case KHTMLViewPrivate::ScrollUp: if (contentsY() <= 0) { d->newScrollTimer(this, 0); } else { verticalScrollBar()->setValue(verticalScrollBar()->value() - d->scrollBy); } break; case KHTMLViewPrivate::ScrollRight: if (contentsX() + visibleWidth() >= contentsWidth()) { d->newScrollTimer(this, 0); } else { horizontalScrollBar()->setValue(horizontalScrollBar()->value() + d->scrollBy); } break; case KHTMLViewPrivate::ScrollLeft: if (contentsX() <= 0) { d->newScrollTimer(this, 0); } else { horizontalScrollBar()->setValue(horizontalScrollBar()->value() - d->scrollBy); } break; } return; } else if (e->timerId() == d->scrollingFromWheelTimerId) { killTimer(d->scrollingFromWheelTimerId); d->scrollingFromWheelTimerId = 0; } else if (e->timerId() == d->layoutTimerId) { if (d->firstLayoutPending && d->layoutAttemptCounter < 4 && (!m_part->xmlDocImpl() || !m_part->xmlDocImpl()->readyForLayout())) { d->layoutAttemptCounter++; killTimer(d->layoutTimerId); d->layoutTimerId = 0; scheduleRelayout(); return; } layout(); d->scheduledLayoutCounter++; if (d->firstLayoutPending) { d->firstLayoutPending = false; verticalScrollBar()->setEnabled(true); horizontalScrollBar()->setEnabled(true); } } d->contentsMoving = false; if (m_part->xmlDocImpl()) { DOM::DocumentImpl *document = m_part->xmlDocImpl(); khtml::RenderCanvas *root = static_cast(document->renderer()); if (root && root->needsLayout()) { if (d->repaintTimerId) { killTimer(d->repaintTimerId); } d->repaintTimerId = 0; scheduleRelayout(); return; } } if (d->repaintTimerId) { killTimer(d->repaintTimerId); } d->repaintTimerId = 0; QRect updateRegion; const QVector rects = d->updateRegion.rects(); d->updateRegion = QRegion(); if (rects.size()) { updateRegion = rects[0]; } for (int i = 1; i < rects.size(); ++i) { QRect newRegion = updateRegion.united(rects[i]); if (2 * newRegion.height() > 3 * updateRegion.height()) { repaintContents(updateRegion); updateRegion = rects[i]; } else { updateRegion = newRegion; } } if (!updateRegion.isNull()) { repaintContents(updateRegion); } // As widgets can only be accurately positioned during painting, every layout might // dissociate a widget from its RenderWidget. E.g: if a RenderWidget was visible before layout, but the layout // pushed it out of the viewport, it will not be repainted, and consequently it's associated widget won't be repositioned. // Thus we need to check each supposedly 'visible' widget at the end of layout, and remove it in case it's no more in sight. if (d->dirtyLayout && !d->visibleWidgets.isEmpty()) { checkExternalWidgetsPosition(); } d->dirtyLayout = false; emit repaintAccessKeys(); if (d->emitCompletedAfterRepaint) { bool full = d->emitCompletedAfterRepaint == KHTMLViewPrivate::CSFull; d->emitCompletedAfterRepaint = KHTMLViewPrivate::CSNone; if (full) { emit m_part->completed(); } else { emit m_part->completed(true); } } } void KHTMLView::checkExternalWidgetsPosition() { QWidget *w; QRect visibleRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()); QList toRemove; QHashIterator it(d->visibleWidgets); while (it.hasNext()) { int xp = 0, yp = 0; it.next(); RenderWidget *rw = static_cast(it.key()); if (!rw->absolutePosition(xp, yp) || !visibleRect.intersects(QRect(xp, yp, it.value()->width(), it.value()->height()))) { toRemove.append(rw); } } foreach (RenderWidget *r, toRemove) if ((w = d->visibleWidgets.take(r))) { w->move(0, -500000); } } void KHTMLView::scheduleRelayout(khtml::RenderObject * /*clippedObj*/) { if (!d->layoutSchedulingEnabled || d->layoutTimerId) { return; } int time = 0; if (d->firstLayoutPending) { // Any repaint happening while we have no content blanks the viewport ("white flash"). // Hence the need to delay the first layout as much as we can. // Only if the document gets stuck for too long in incomplete state will we allow the blanking. time = d->layoutAttemptCounter ? sLayoutAttemptDelay + sLayoutAttemptIncrement * d->layoutAttemptCounter : sFirstLayoutDelay; } else if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->parsing()) { // Delay between successive layouts in parsing mode. // Increment reflects the decaying importance of visual feedback over time. time = qMin(2000, sParsingLayoutsInterval + d->scheduledLayoutCounter * sParsingLayoutsIncrement); } d->layoutTimerId = startTimer(time); } void KHTMLView::unscheduleRelayout() { if (!d->layoutTimerId) { return; } killTimer(d->layoutTimerId); d->layoutTimerId = 0; } void KHTMLView::unscheduleRepaint() { if (!d->repaintTimerId) { return; } killTimer(d->repaintTimerId); d->repaintTimerId = 0; } void KHTMLView::scheduleRepaint(int x, int y, int w, int h, bool asap) { bool parsing = !m_part->xmlDocImpl() || m_part->xmlDocImpl()->parsing(); // qCDebug(KHTML_LOG) << "parsing " << parsing; // qCDebug(KHTML_LOG) << "complete " << d->complete; int time = parsing && !d->firstLayoutPending ? 150 : (!asap ? (!d->complete ? 80 : 20) : 0); #ifdef DEBUG_FLICKER QPainter p; p.begin(viewport()); int vx, vy; contentsToViewport(x, y, vx, vy); p.fillRect(vx, vy, w, h, Qt::red); p.end(); #endif d->updateRegion = d->updateRegion.united(QRect(x, y, w, h)); if (asap && !parsing) { unscheduleRepaint(); } if (!d->repaintTimerId) { d->repaintTimerId = startTimer(time); } // qCDebug(KHTML_LOG) << "starting timer " << time; } void KHTMLView::complete(bool pendingAction) { // qCDebug(KHTML_LOG) << "KHTMLView::complete()"; d->complete = true; // is there a relayout pending? if (d->layoutTimerId) { // qCDebug(KHTML_LOG) << "requesting relayout now"; // do it now killTimer(d->layoutTimerId); d->layoutTimerId = startTimer(0); d->emitCompletedAfterRepaint = pendingAction ? KHTMLViewPrivate::CSActionPending : KHTMLViewPrivate::CSFull; } // is there a repaint pending? if (d->repaintTimerId) { // qCDebug(KHTML_LOG) << "requesting repaint now"; // do it now killTimer(d->repaintTimerId); d->repaintTimerId = startTimer(0); d->emitCompletedAfterRepaint = pendingAction ? KHTMLViewPrivate::CSActionPending : KHTMLViewPrivate::CSFull; } if (!d->emitCompletedAfterRepaint) { if (!pendingAction) { emit m_part->completed(); } else { emit m_part->completed(true); } } } void KHTMLView::updateScrollBars() { const QWidget *view = widget(); if (!view) { return; } QSize p = viewport()->size(); QSize m = maximumViewportSize(); if (m.expandedTo(view->size()) == m) { p = m; // no scroll bars needed } QSize v = view->size(); horizontalScrollBar()->setRange(0, v.width() - p.width()); horizontalScrollBar()->setPageStep(p.width()); verticalScrollBar()->setRange(0, v.height() - p.height()); verticalScrollBar()->setPageStep(p.height()); if (!d->smoothScrolling) { d->updateContentsXY(); } } void KHTMLView::slotMouseScrollTimer() { horizontalScrollBar()->setValue(horizontalScrollBar()->value() + d->m_mouseScroll_byX); verticalScrollBar()->setValue(verticalScrollBar()->value() + d->m_mouseScroll_byY); } static DOM::Position positionOfLineBoundary(const DOM::Position &pos, bool toEnd) { Selection sel = pos; sel.expandUsingGranularity(Selection::LINE); return toEnd ? sel.end() : sel.start(); } inline static DOM::Position positionOfLineBegin(const DOM::Position &pos) { return positionOfLineBoundary(pos, false); } inline static DOM::Position positionOfLineEnd(const DOM::Position &pos) { return positionOfLineBoundary(pos, true); } bool KHTMLView::caretKeyPressEvent(QKeyEvent *_ke) { EditorContext *ec = &m_part->d->editor_context; Selection &caret = ec->m_selection; Position old_pos = caret.caretPos(); Position pos = old_pos; bool recalcXPos = true; bool handled = true; bool ctrl = _ke->modifiers() & Qt::ControlModifier; bool shift = _ke->modifiers() & Qt::ShiftModifier; switch (_ke->key()) { // -- Navigational keys case Qt::Key_Down: pos = old_pos.nextLinePosition(caret.xPosForVerticalArrowNavigation(Selection::EXTENT)); recalcXPos = false; break; case Qt::Key_Up: pos = old_pos.previousLinePosition(caret.xPosForVerticalArrowNavigation(Selection::EXTENT)); recalcXPos = false; break; case Qt::Key_Left: pos = ctrl ? old_pos.previousWordPosition() : old_pos.previousCharacterPosition(); break; case Qt::Key_Right: pos = ctrl ? old_pos.nextWordPosition() : old_pos.nextCharacterPosition(); break; case Qt::Key_PageDown: // moveCaretNextPage(); ### break; case Qt::Key_PageUp: // moveCaretPrevPage(); ### break; case Qt::Key_Home: if (ctrl) /*moveCaretToDocumentBoundary(false)*/; // ### else { pos = positionOfLineBegin(old_pos); } break; case Qt::Key_End: if (ctrl) /*moveCaretToDocumentBoundary(true)*/; // ### else { pos = positionOfLineEnd(old_pos); } break; default: handled = false; }/*end switch*/ if (pos != old_pos) { m_part->clearCaretRectIfNeeded(); caret.moveTo(shift ? caret.nonCaretPos() : pos, pos); int old_x = caret.xPosForVerticalArrowNavigation(Selection::CARETPOS); m_part->selectionLayoutChanged(); // restore old x-position to prevent recalculation if (!recalcXPos) { m_part->d->editor_context.m_xPosForVerticalArrowNavigation = old_x; } m_part->emitCaretPositionChanged(pos); // ### check when to emit it m_part->notifySelectionChanged(); } if (handled) { _ke->accept(); } return handled; } #undef DEBUG_CARETMODE diff --git a/src/khtmlview.h b/src/khtmlview.h index 77debe6..76fda45 100644 --- a/src/khtmlview.h +++ b/src/khtmlview.h @@ -1,549 +1,559 @@ /* This file is part of the KDE project Copyright (C) 1997 Martin Jones (mjones@kde.org) (C) 1998 Waldo Bastian (bastian@kde.org) (C) 1998, 1999 Torben Weis (weis@kde.org) (C) 1999 Lars Knoll (knoll@kde.org) (C) 1999 Antti Koivisto (koivisto@kde.org) (C) 2006 Germain Garand (germain@ebooksfrance.org) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KHTMLVIEW_H #define KHTMLVIEW_H #include // qt includes and classes #include class QPainter; +class QPrinter; class QRect; template< typename T > class QVector; template class QStack; namespace DOM { class HTMLDocumentImpl; class DocumentImpl; class ElementImpl; class HTMLTitleElementImpl; class HTMLGenericFormElementImpl; class HTMLFormElementImpl; class HTMLAnchorElementImpl; class HTMLInputElementImpl; class NodeImpl; class CSSProperty; } namespace KJS { class WindowFunc; class ExternalFunc; } namespace khtml { class RenderObject; class RenderCanvas; class RenderLineEdit; class RenderPartObject; class RenderWidget; class RenderLayer; class RenderBox; class CSSStyleSelector; class LineEditWidget; class CaretBox; class HTMLTokenizer; class KHTMLWidgetPrivate; class KHTMLWidget { public: KHTMLWidget(); ~KHTMLWidget(); KHTMLWidgetPrivate *m_kwp; }; void applyRule(DOM::CSSProperty *prop); } class KHTMLPart; class KHTMLViewPrivate; namespace khtml { } /** * Renders and displays HTML in a QScrollArea. * * Suitable for use as an application's main view. **/ class KHTML_EXPORT KHTMLView : public QScrollArea, public khtml::KHTMLWidget { Q_OBJECT friend class DOM::HTMLDocumentImpl; friend class DOM::HTMLTitleElementImpl; friend class DOM::HTMLGenericFormElementImpl; friend class DOM::HTMLFormElementImpl; friend class DOM::HTMLAnchorElementImpl; friend class DOM::HTMLInputElementImpl; friend class DOM::NodeImpl; friend class DOM::ElementImpl; friend class DOM::DocumentImpl; friend class KHTMLPart; friend class KHTMLFind; friend class StorePass; friend class khtml::RenderCanvas; friend class khtml::RenderObject; friend class khtml::RenderLineEdit; friend class khtml::RenderPartObject; friend class khtml::RenderWidget; friend class khtml::KHTMLWidgetPrivate; friend class khtml::RenderLayer; friend class khtml::RenderBox; friend class khtml::CSSStyleSelector; friend class khtml::LineEditWidget; friend class khtml::HTMLTokenizer; friend class KJS::WindowFunc; friend class KJS::ExternalFunc; friend void khtml::applyRule(DOM::CSSProperty *prop); public: /** * Constructs a KHTMLView. */ KHTMLView(KHTMLPart *part, QWidget *parent); virtual ~KHTMLView(); /** * Returns a pointer to the KHTMLPart that is * rendering the page. **/ KHTMLPart *part() const { return m_part; } int frameWidth() const { return _width; } /** * Sets a margin in x direction. */ void setMarginWidth(int x); /** * Returns the margin width. * * A return value of -1 means the default value will be used. */ int marginWidth() const { return _marginWidth; } /* * Sets a margin in y direction. */ void setMarginHeight(int y); /** * Returns the margin height. * * A return value of -1 means the default value will be used. */ int marginHeight() { return _marginHeight; } /** * Sets vertical scrollbar mode. * * WARNING: do not call this method on a base class pointer unless you * specifically want QAbstractScrollArea's variant (not recommended). * QAbstractScrollArea::setVerticalScrollBarPolicy is *not* virtual. */ virtual void setVerticalScrollBarPolicy(Qt::ScrollBarPolicy policy); /** * Sets horizontal scrollbar mode. * * WARNING: do not call this method on a base class pointer unless you * specifically want QAbstractScrollArea's variant (not recommended). * QAbstractScrollArea::setHorizontalScrollBarPolicy is *not* virtual. */ virtual void setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy policy); /** * Prints the HTML document. * @param quick if true, fully automated printing, without print dialog */ void print(bool quick = false); + /** + * Prints the HTML document. + * @param printer shared printer instance + * @param quick if true, fully automated printing, without print dialog + * + * @since 5.64 + */ + void print(QPrinter *printer, bool quick = false); + /** * Display all accesskeys in small tooltips */ void displayAccessKeys(); /** * Returns the contents area's width */ int contentsWidth() const; /** * Returns the contents area's height */ int contentsHeight() const; /** * Returns the x coordinate of the contents area point * that is currently located at the top left in the viewport */ int contentsX() const; /** * Returns the y coordinate of the contents area point * that is currently located at the top left in the viewport */ int contentsY() const; /** * Returns the width of the viewport */ int visibleWidth() const; /** * Returns the height of the viewport */ int visibleHeight() const; /** * Place the contents area point x/y * at the top left of the viewport */ void setContentsPos(int x, int y); /** * Returns a point translated to viewport coordinates * @param p the contents area point to translate * */ QPoint contentsToViewport(const QPoint &p) const; /** * Returns a point translated to contents area coordinates * @param p the viewport point to translate * */ QPoint viewportToContents(const QPoint &p) const; /** * Returns a point translated to contents area coordinates * @param x x coordinate of viewport point to translate * @param y y coordinate of viewport point to translate * @param cx resulting x coordinate * @param cy resulting y coordinate * */ void viewportToContents(int x, int y, int &cx, int &cy) const; /** * Returns a point translated to viewport coordinates * @param x x coordinate of contents area point to translate * @param y y coordinate of contents area point to translate * @param cx resulting x coordinate * @param cy resulting y coordinate * */ void contentsToViewport(int x, int y, int &cx, int &cy) const; /** * Scrolls the content area by a given amount * @param x x offset * @param y y offset */ void scrollBy(int x, int y); /** * Requests an update of the content area * @param r the content area rectangle to update */ void updateContents(const QRect &r); void updateContents(int x, int y, int w, int h); void addChild(QWidget *child, int dx, int dy); /** * Requests an immediate repaint of the content area * @param r the content area rectangle to repaint */ void repaintContents(const QRect &r); void repaintContents(int x, int y, int w, int h); /** * Apply a zoom level to the content area * @param percent a zoom level expressed as a percentage */ void setZoomLevel(int percent); /** * Retrieve the current zoom level * */ int zoomLevel() const; /** * Smooth Scrolling Mode enumeration * @li SSMDisabled smooth scrolling is disabled * @li SSMWhenEfficient only use smooth scrolling on pages that do not require a full repaint of the content area when scrolling * @li SSMAlways smooth scrolling is performed unconditionally */ enum SmoothScrollingMode { SSMDisabled = 0, SSMWhenEfficient, SSMEnabled }; /** * Set the smooth scrolling mode. * * Smooth scrolling mode is normally controlled by the configuration file's SmoothScrolling key. * Using this setter will override the configuration file's settings. * * @since 4.1 */ void setSmoothScrollingMode(SmoothScrollingMode m); /** * Retrieve the current smooth scrolling mode * * @since 4.1 */ SmoothScrollingMode smoothScrollingMode() const; public Q_SLOTS: /** * Resize the contents area * @param w the new width * @param h the new height */ virtual void resizeContents(int w, int h); /** * ensure the display is up to date */ void layout(); Q_SIGNALS: /** * This signal is used for internal layouting. Don't use it to check if rendering finished. * Use @ref KHTMLPart completed() signal instead. */ void finishedLayout(); void cleared(); void zoomView(int); void hideAccessKeys(); void repaintAccessKeys(); void findAheadActive(bool); protected: void clear(); bool event(QEvent *event) override; void paintEvent(QPaintEvent *) override; void resizeEvent(QResizeEvent *event) override; void showEvent(QShowEvent *) override; void hideEvent(QHideEvent *) override; bool focusNextPrevChild(bool next) override; void mousePressEvent(QMouseEvent *) override; void focusInEvent(QFocusEvent *) override; void focusOutEvent(QFocusEvent *) override; void mouseDoubleClickEvent(QMouseEvent *) override; void mouseMoveEvent(QMouseEvent *) override; void mouseReleaseEvent(QMouseEvent *) override; #ifndef QT_NO_WHEELEVENT void wheelEvent(QWheelEvent *) override; #endif void dragEnterEvent(QDragEnterEvent *) override; void dropEvent(QDropEvent *) override; void closeEvent(QCloseEvent *) override; virtual bool widgetEvent(QEvent *); bool viewportEvent(QEvent *e) override; bool eventFilter(QObject *, QEvent *) override; void scrollContentsBy(int dx, int dy) override; void keyPressEvent(QKeyEvent *_ke) override; void keyReleaseEvent(QKeyEvent *_ke) override; void doAutoScroll(); void timerEvent(QTimerEvent *) override; void setSmoothScrollingModeDefault(SmoothScrollingMode m); protected Q_SLOTS: void slotPaletteChanged(); private Q_SLOTS: void tripleClickTimeout(); void accessKeysTimeout(); void scrollTick(); /** * @internal * used for autoscrolling with MMB */ void slotMouseScrollTimer(); private: void resizeContentsToViewport(); void scheduleRelayout(khtml::RenderObject *clippedObj = nullptr); void unscheduleRelayout(); bool hasLayoutPending(); void scheduleRepaint(int x, int y, int w, int h, bool asap = false); void unscheduleRepaint(); bool needsFullRepaint() const; void closeChildDialogs(); bool dialogsAllowed(); void setMouseEventsTarget(QWidget *w); QWidget *mouseEventsTarget() const; QStack *clipHolder() const; void setClipHolder(QStack *ch); void setPart(KHTMLPart *part); /** * Paints the HTML document to a QPainter. * The document will be scaled to match the width of * rc and clipped to fit in the height. * yOff determines the vertical offset in the document to start with. * more, if nonzero will be set to true if the documents extends * beyond the rc or false if everything below yOff was painted. **/ void paint(QPainter *p, const QRect &rc, int yOff = 0, bool *more = nullptr); void render(QPainter *p, const QRect &r, const QPoint &off); /** * Get/set the CSS Media Type. * * Media type is set to "screen" for on-screen rendering and "print" * during printing. Other media types lack the proper support in the * renderer and are not activated. The DOM tree and the parser itself, * however, properly handle other media types. To make them actually work * you only need to enable the media type in the view and if necessary * add the media type dependent changes to the renderer. */ void setMediaType(const QString &medium); QString mediaType() const; bool pagedMode() const; bool scrollTo(const QRect &); bool focusNextPrevNode(bool next); bool handleAccessKey(const QKeyEvent *ev); bool focusNodeWithAccessKey(QChar c, KHTMLView *caller = nullptr); QMap< DOM::ElementImpl *, QChar > buildFallbackAccessKeys() const; void displayAccessKeys(KHTMLView *caller, KHTMLView *origview, QVector< QChar > &taken, bool use_fallbacks); bool isScrollingFromMouseWheel() const; void setHasStaticBackground(bool partial = false); void setHasNormalBackground(); void addStaticObject(bool fixed); void removeStaticObject(bool fixed); void applyTransforms(int &x, int &y, int &w, int &h) const; void revertTransforms(int &x, int &y, int &w, int &h) const; void revertTransforms(int &x, int &y) const; void checkExternalWidgetsPosition(); void setIgnoreWheelEvents(bool e); void initWidget(); DOM::NodeImpl *nodeUnderMouse() const; DOM::NodeImpl *nonSharedNodeUnderMouse() const; void restoreScrollBar(); QStringList formCompletionItems(const QString &name) const; void clearCompletionHistory(const QString &name); void addFormCompletionItem(const QString &name, const QString &value); void addNonPasswordStorableSite(const QString &host); void delNonPasswordStorableSite(const QString &host); bool nonPasswordStorableSite(const QString &host) const; bool dispatchMouseEvent(int eventId, DOM::NodeImpl *targetNode, DOM::NodeImpl *targetNodeNonShared, bool cancelable, int detail, QMouseEvent *_mouse, bool setUnder, int mouseEventType, int orientation = 0); bool dispatchKeyEvent(QKeyEvent *_ke); bool dispatchKeyEventHelper(QKeyEvent *_ke, bool generate_keypress); void complete(bool pendingAction); void updateScrollBars(); void setupSmoothScrolling(int dx, int dy); /** * Returns the current caret policy when the view is not focused. * @return a KHTMLPart::CaretDisplay value */ int caretDisplayPolicyNonFocused() const; /** * Sets the caret display policy when the view is not focused. * @param policy new display policy as * defined by KHTMLPart::CaretDisplayPolicy */ void setCaretDisplayPolicyNonFocused(int policy); // -- caret event handler /** * Evaluates key presses for caret navigation on editable nodes. * @return true if event has been handled */ bool caretKeyPressEvent(QKeyEvent *); // ------------------------------------- member variables ------------------------------------ private: friend class KHTMLViewPrivate; enum LinkCursor { LINK_NORMAL, LINK_MAILTO, LINK_NEWWINDOW }; void setWidgetVisible(::khtml::RenderWidget *, bool visible); int _width; int _height; int _marginWidth; int _marginHeight; KHTMLPart *m_part; KHTMLViewPrivate *const d; QString m_medium; // media type }; #endif