From ce4a32fe52ef09d8f5ff1dd22c001110902b60a2 Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdelibs@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- khtml/khtmlview.cpp | 4465 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4465 insertions(+) create mode 100644 khtml/khtmlview.cpp (limited to 'khtml/khtmlview.cpp') diff --git a/khtml/khtmlview.cpp b/khtml/khtmlview.cpp new file mode 100644 index 000000000..376b65cc3 --- /dev/null +++ b/khtml/khtmlview.cpp @@ -0,0 +1,4465 @@ +/* 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-2004 Apple Computer, Inc. + * + * 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.moc" + +#include "khtmlview.h" + +#include "khtml_part.h" +#include "khtml_events.h" + +#include "html/html_documentimpl.h" +#include "html/html_inlineimpl.h" +#include "html/html_formimpl.h" +#include "rendering/render_arena.h" +#include "rendering/render_canvas.h" +#include "rendering/render_frames.h" +#include "rendering/render_replaced.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 "css/csshelper.h" +#include "misc/htmlhashes.h" +#include "misc/helper.h" +#include "misc/loader.h" +#include "khtml_settings.h" +#include "khtml_printsettings.h" + +#include "khtmlpart_p.h" + +#ifndef KHTML_NO_CARET +#include "khtml_caret_p.h" +#include "xml/dom2_rangeimpl.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG_NO_PAINT_BUFFER + +//#define DEBUG_FLICKER + +//#define DEBUG_PIXEL + +#ifdef Q_WS_X11 +#include +#include +#endif + +#define PAINT_BUFFER_HEIGHT 128 + +#if 0 +namespace khtml { + void dumpLineBoxes(RenderFlow *flow); +} +#endif + +using namespace DOM; +using namespace khtml; +class KHTMLToolTip; + + +#ifndef QT_NO_TOOLTIP + +class KHTMLToolTip : public QToolTip +{ +public: + KHTMLToolTip(KHTMLView *view, KHTMLViewPrivate* vp) : QToolTip(view->viewport()) + { + m_view = view; + m_viewprivate = vp; + }; + +protected: + virtual void maybeTip(const QPoint &); + +private: + KHTMLView *m_view; + KHTMLViewPrivate* m_viewprivate; +}; + +#endif + +class KHTMLViewPrivate { + friend class KHTMLToolTip; +public: + + enum PseudoFocusNodes { + PFNone, + PFTop, + PFBottom + }; + + enum CompletedState { + CSNone = 0, + CSFull, + CSActionPending + }; + + KHTMLViewPrivate() + : underMouse( 0 ), underMouseNonShared( 0 ), visibleWidgets( 107 ) + { +#ifndef KHTML_NO_CARET + m_caretViewContext = 0; + m_editorContext = 0; +#endif // KHTML_NO_CARET + postponed_autorepeat = NULL; + reset(); + vmode = QScrollView::Auto; + hmode = QScrollView::Auto; + tp=0; + paintBuffer=0; + vertPaintBuffer=0; + formCompletions=0; + prevScrollbarVisible = true; + tooltip = 0; + possibleTripleClick = false; + emitCompletedAfterRepaint = CSNone; + cursor_icon_widget = NULL; + m_mouseScrollTimer = 0; + m_mouseScrollIndicator = 0; + } + ~KHTMLViewPrivate() + { + delete formCompletions; + delete tp; tp = 0; + delete paintBuffer; paintBuffer =0; + delete vertPaintBuffer; + delete postponed_autorepeat; + if (underMouse) + underMouse->deref(); + if (underMouseNonShared) + underMouseNonShared->deref(); + delete tooltip; +#ifndef KHTML_NO_CARET + delete m_caretViewContext; + delete m_editorContext; +#endif // KHTML_NO_CARET + delete cursor_icon_widget; + delete m_mouseScrollTimer; + delete m_mouseScrollIndicator; + } + void reset() + { + if (underMouse) + underMouse->deref(); + underMouse = 0; + if (underMouseNonShared) + underMouseNonShared->deref(); + underMouseNonShared = 0; + linkPressed = false; + useSlowRepaints = false; + tabMovePending = false; + lastTabbingDirection = true; + pseudoFocusNode = PFNone; +#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 + vmode = QScrollView::AlwaysOff; + hmode = QScrollView::AlwaysOff; +#endif +#ifdef DEBUG_PIXEL + timer.start(); + pixelbooth = 0; + repaintbooth = 0; +#endif + scrollBarMoved = false; + contentsMoving = false; + ignoreWheelEvents = false; + borderX = 30; + borderY = 30; + paged = false; + clickX = -1; + clickY = -1; + prevMouseX = -1; + prevMouseY = -1; + clickCount = 0; + isDoubleClick = false; + scrollingSelf = false; + delete postponed_autorepeat; + postponed_autorepeat = NULL; + layoutTimerId = 0; + repaintTimerId = 0; + scrollTimerId = 0; + scrollSuspended = false; + scrollSuspendPreActivate = false; + complete = false; + firstRelayout = true; + needsFullRepaint = true; + dirtyLayout = false; + layoutSchedulingEnabled = true; + painting = false; + updateRegion = QRegion(); + m_dialogsAllowed = true; +#ifndef KHTML_NO_CARET + if (m_caretViewContext) { + m_caretViewContext->caretMoved = false; + m_caretViewContext->keyReleasePending = false; + }/*end if*/ +#endif // KHTML_NO_CARET +#ifndef KHTML_NO_TYPE_AHEAD_FIND + typeAheadActivated = false; +#endif // KHTML_NO_TYPE_AHEAD_FIND + accessKeysActivated = false; + accessKeysPreActivate = false; + + // We ref/deref to ensure defaultHTMLSettings is available + KHTMLFactory::ref(); + accessKeysEnabled = KHTMLFactory::defaultHTMLSettings()->accessKeysEnabled(); + KHTMLFactory::deref(); + + emitCompletedAfterRepaint = CSNone; + } + void newScrollTimer(QWidget *view, int tid) + { + //kdDebug(6000) << "newScrollTimer timer " << tid << endl; + 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; + } + +#ifndef KHTML_NO_CARET + /** this function returns an instance of the caret view context. If none + * exists, it will be instantiated. + */ + CaretViewContext *caretViewContext() { + if (!m_caretViewContext) m_caretViewContext = new CaretViewContext(); + return m_caretViewContext; + } + /** this function returns an instance of the editor context. If none + * exists, it will be instantiated. + */ + EditorContext *editorContext() { + if (!m_editorContext) m_editorContext = new EditorContext(); + return m_editorContext; + } +#endif // KHTML_NO_CARET + +#ifdef DEBUG_PIXEL + QTime timer; + unsigned int pixelbooth; + unsigned int repaintbooth; +#endif + + QPainter *tp; + QPixmap *paintBuffer; + QPixmap *vertPaintBuffer; + NodeImpl *underMouse; + NodeImpl *underMouseNonShared; + + bool tabMovePending:1; + bool lastTabbingDirection:1; + PseudoFocusNodes pseudoFocusNode:2; + bool scrollBarMoved:1; + bool contentsMoving:1; + + QScrollView::ScrollBarMode vmode; + QScrollView::ScrollBarMode hmode; + bool prevScrollbarVisible:1; + bool linkPressed:1; + bool useSlowRepaints:1; + bool ignoreWheelEvents:1; + + int borderX, borderY; + KSimpleConfig *formCompletions; + + bool paged; + + int clickX, clickY, clickCount; + bool isDoubleClick; + + int prevMouseX, prevMouseY; + bool scrollingSelf; + int layoutTimerId; + QKeyEvent* postponed_autorepeat; + + int repaintTimerId; + int scrollTimerId; + int scrollTiming; + int scrollBy; + ScrollDirection scrollDirection :2; + bool scrollSuspended :1; + bool scrollSuspendPreActivate :1; + bool complete :1; + bool firstRelayout :1; + bool layoutSchedulingEnabled :1; + bool needsFullRepaint :1; + bool painting :1; + bool possibleTripleClick :1; + bool dirtyLayout :1; + bool m_dialogsAllowed :1; + QRegion updateRegion; + KHTMLToolTip *tooltip; + QPtrDict visibleWidgets; +#ifndef KHTML_NO_CARET + CaretViewContext *m_caretViewContext; + EditorContext *m_editorContext; +#endif // KHTML_NO_CARET +#ifndef KHTML_NO_TYPE_AHEAD_FIND + QString findString; + QTimer timer; + bool findLinksOnly; + bool typeAheadActivated; +#endif // KHTML_NO_TYPE_AHEAD_FIND + bool accessKeysEnabled; + bool accessKeysActivated; + bool accessKeysPreActivate; + CompletedState emitCompletedAfterRepaint; + + QWidget* cursor_icon_widget; + + // scrolling activated by MMB + short m_mouseScroll_byX; + short m_mouseScroll_byY; + QTimer *m_mouseScrollTimer; + QWidget *m_mouseScrollIndicator; +}; + +#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->getDocument()->isHTMLDocument() && + (map = static_cast(img->getDocument())->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.moveBy(ax, ay); + return true; + } + } + } + return false; +} + +void KHTMLToolTip::maybeTip(const QPoint& p) +{ + DOM::NodeImpl *node = m_viewprivate->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), + m_view->viewportToContents(QPoint(0, 0)), p, r, s); + } + if (!found) { + s = e->getAttribute( ATTR_TITLE ).string(); + r = node->getRect(); + } + region |= QRect( m_view->contentsToViewport( r.topLeft() ), r.size() ); + if ( !s.isEmpty() ) { + tip( region, QStyleSheet::convertFromPlainText( s, QStyleSheetItem::WhiteSpaceNormal ) ); + break; + } + } + node = node->parentNode(); + } +} +#endif + +KHTMLView::KHTMLView( KHTMLPart *part, QWidget *parent, const char *name) + : QScrollView( parent, name, WResizeNoErase | WRepaintNoErase ) +{ + m_medium = "screen"; + + m_part = part; + d = new KHTMLViewPrivate; + QScrollView::setVScrollBarMode(d->vmode); + QScrollView::setHScrollBarMode(d->hmode); + connect(kapp, SIGNAL(kdisplayPaletteChanged()), this, SLOT(slotPaletteChanged())); + connect(this, SIGNAL(contentsMoving(int, int)), this, SLOT(slotScrollBarMoved())); + + // initialize QScrollView + enableClipper(true); + // hack to get unclipped painting on the viewport. + static_cast(static_cast(viewport()))->setWFlags(WPaintUnclipped); + + setResizePolicy(Manual); + viewport()->setMouseTracking(true); + viewport()->setBackgroundMode(NoBackground); + + KImageIO::registerFormats(); + +#ifndef QT_NO_TOOLTIP + d->tooltip = new KHTMLToolTip( this, d ); +#endif + +#ifndef KHTML_NO_TYPE_AHEAD_FIND + connect(&d->timer, SIGNAL(timeout()), this, SLOT(findTimeout())); +#endif // KHTML_NO_TYPE_AHEAD_FIND + + init(); + + viewport()->show(); +} + +KHTMLView::~KHTMLView() +{ + closeChildDialogs(); + if (m_part) + { + //WABA: Is this Ok? Do I need to deref it as well? + //Does this need to be done somewhere else? + DOM::DocumentImpl *doc = m_part->xmlDocImpl(); + if (doc) + doc->detach(); + } + delete d; d = 0; +} + +void KHTMLView::init() +{ + if(!d->paintBuffer) d->paintBuffer = new QPixmap(PAINT_BUFFER_HEIGHT, PAINT_BUFFER_HEIGHT); + if(!d->vertPaintBuffer) + d->vertPaintBuffer = new QPixmap(10, PAINT_BUFFER_HEIGHT); + if(!d->tp) d->tp = new QPainter(); + + setFocusPolicy(QWidget::StrongFocus); + viewport()->setFocusProxy(this); + + _marginWidth = -1; // undefined + _marginHeight = -1; + _width = 0; + _height = 0; + + installEventFilter(this); + + setAcceptDrops(true); + QSize s = viewportSize(4095, 4095); + resizeContents(s.width(), s.height()); +} + +void KHTMLView::clear() +{ + // work around QScrollview's unbelievable bugginess + setStaticBackground(true); +#ifndef KHTML_NO_CARET + if (!m_part->isCaretMode() && !m_part->isEditable()) caretOff(); +#endif + +#ifndef KHTML_NO_TYPE_AHEAD_FIND + if( d->typeAheadActivated ) + findTimeout(); +#endif + if (d->accessKeysEnabled && d->accessKeysActivated) + accessKeysTimeout(); + viewport()->unsetCursor(); + if ( d->cursor_icon_widget ) + d->cursor_icon_widget->hide(); + d->reset(); + killTimers(); + emit cleared(); + + QScrollView::setHScrollBarMode(d->hmode); + QScrollView::setVScrollBarMode(d->vmode); + verticalScrollBar()->setEnabled( false ); + horizontalScrollBar()->setEnabled( false ); +} + +void KHTMLView::hideEvent(QHideEvent* e) +{ + QScrollView::hideEvent(e); + if ( m_part && m_part->xmlDocImpl() ) + m_part->xmlDocImpl()->docLoader()->pauseAnimations(); +} + +void KHTMLView::showEvent(QShowEvent* e) +{ + QScrollView::showEvent(e); + if ( m_part && m_part->xmlDocImpl() ) + m_part->xmlDocImpl()->docLoader()->resumeAnimations(); +} + +void KHTMLView::resizeEvent (QResizeEvent* e) +{ + int dw = e->oldSize().width() - e->size().width(); + int dh = e->oldSize().height() - e->size().height(); + + // if we are shrinking the view, don't allow the content to overflow + // before the layout occurs - we don't know if we need scrollbars yet + dw = dw>0 ? kMax(0, contentsWidth()-dw) : contentsWidth(); + dh = dh>0 ? kMax(0, contentsHeight()-dh) : contentsHeight(); + + resizeContents(dw, dh); + + QScrollView::resizeEvent(e); + + if ( m_part && m_part->xmlDocImpl() ) + m_part->xmlDocImpl()->dispatchWindowEvent( EventImpl::RESIZE_EVENT, false, false ); +} + +void KHTMLView::viewportResizeEvent (QResizeEvent* e) +{ + QScrollView::viewportResizeEvent(e); + + //int w = visibleWidth(); + //int h = visibleHeight(); + + if (d->layoutSchedulingEnabled) + layout(); +#ifndef KHTML_NO_CARET + else { + hideCaret(); + recalcAndStoreCaretPos(); + showCaret(); + }/*end if*/ +#endif + + KApplication::sendPostedEvents(viewport(), QEvent::Paint); +} + +// this is to get rid of a compiler virtual overload mismatch warning. do not remove +void KHTMLView::drawContents( QPainter*) +{ +} + +void KHTMLView::drawContents( QPainter *p, int ex, int ey, int ew, int eh ) +{ +#ifdef DEBUG_PIXEL + + if ( d->timer.elapsed() > 5000 ) { + qDebug( "drawed %d pixels in %d repaints the last %d milliseconds", + d->pixelbooth, d->repaintbooth, d->timer.elapsed() ); + d->timer.restart(); + d->pixelbooth = 0; + d->repaintbooth = 0; + } + d->pixelbooth += ew*eh; + d->repaintbooth++; +#endif + + //kdDebug( 6000 ) << "drawContents this="<< this <<" x=" << ex << ",y=" << ey << ",w=" << ew << ",h=" << eh << endl; + if(!m_part || !m_part->xmlDocImpl() || !m_part->xmlDocImpl()->renderer()) { + p->fillRect(ex, ey, ew, eh, palette().active().brush(QColorGroup::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(); + } + + if (d->painting) { + kdDebug( 6000 ) << "WARNING: drawContents reentered! " << endl; + return; + } + d->painting = true; + + QPoint pt = contentsToViewport(QPoint(ex, ey)); + QRegion cr = QRect(pt.x(), pt.y(), ew, eh); + + // kdDebug(6000) << "clip rect: " << QRect(pt.x(), pt.y(), ew, eh) << endl; + for (QPtrDictIterator it(d->visibleWidgets); it.current(); ++it) { + QWidget *w = it.current(); + RenderWidget* rw = static_cast( it.currentKey() ); + if (w && rw && !rw->isKHTMLWidget()) { + int x, y; + rw->absolutePosition(x, y); + contentsToViewport(x, y, x, y); + int pbx = rw->borderLeft()+rw->paddingLeft(); + int pby = rw->borderTop()+rw->paddingTop(); + QRect g = QRect(x+pbx, y+pby, + rw->width()-pbx-rw->borderRight()-rw->paddingRight(), + rw->height()-pby-rw->borderBottom()-rw->paddingBottom()); + if ( !rw->isFrame() && ((g.top() > pt.y()+eh) || (g.bottom() <= pt.y()) || + (g.right() <= pt.x()) || (g.left() > pt.x()+ew) )) + continue; + RenderLayer* rl = rw->needsMask() ? rw->enclosingStackingContext() : 0; + QRegion mask = rl ? rl->getMask() : QRegion(); + if (!mask.isNull()) { + QPoint o(0,0); + o = contentsToViewport(o); + mask.translate(o.x(),o.y()); + mask = mask.intersect( QRect(g.x(),g.y(),g.width(),g.height()) ); + cr -= mask; + } else { + cr -= g; + } + } + } + +#if 0 + // this is commonly the case with framesets. we still do + // want to paint them, otherwise the widgets don't get placed. + if (cr.isEmpty()) { + d->painting = false; + return; + } +#endif + +#ifndef DEBUG_NO_PAINT_BUFFER + p->setClipRegion(cr); + + if (eh > PAINT_BUFFER_HEIGHT && ew <= 10) { + if ( d->vertPaintBuffer->height() < visibleHeight() ) + d->vertPaintBuffer->resize(10, visibleHeight()); + d->tp->begin(d->vertPaintBuffer); + d->tp->translate(-ex, -ey); + d->tp->fillRect(ex, ey, ew, eh, palette().active().brush(QColorGroup::Base)); + m_part->xmlDocImpl()->renderer()->layer()->paint(d->tp, QRect(ex, ey, ew, eh)); + d->tp->end(); + p->drawPixmap(ex, ey, *d->vertPaintBuffer, 0, 0, ew, eh); + } + else { + if ( d->paintBuffer->width() < visibleWidth() ) + d->paintBuffer->resize(visibleWidth(),PAINT_BUFFER_HEIGHT); + + int py=0; + while (py < eh) { + int ph = eh-py < PAINT_BUFFER_HEIGHT ? eh-py : PAINT_BUFFER_HEIGHT; + d->tp->begin(d->paintBuffer); + d->tp->translate(-ex, -ey-py); + d->tp->fillRect(ex, ey+py, ew, ph, palette().active().brush(QColorGroup::Base)); + m_part->xmlDocImpl()->renderer()->layer()->paint(d->tp, QRect(ex, ey+py, ew, ph)); + d->tp->end(); + + p->drawPixmap(ex, ey+py, *d->paintBuffer, 0, 0, ew, ph); + py += PAINT_BUFFER_HEIGHT; + } + } +#else // !DEBUG_NO_PAINT_BUFFER +static int cnt=0; + ex = contentsX(); ey = contentsY(); + ew = visibleWidth(); eh = visibleHeight(); + QRect pr(ex,ey,ew,eh); + kdDebug() << "[" << ++cnt << "]" << " clip region: " << pr << endl; +// p->setClipRegion(QRect(0,0,ew,eh)); +// p->translate(-ex, -ey); + p->fillRect(ex, ey, ew, eh, palette().active().brush(QColorGroup::Base)); + m_part->xmlDocImpl()->renderer()->layer()->paint(p, pr); +#endif // DEBUG_NO_PAINT_BUFFER + +#ifndef KHTML_NO_CARET + if (d->m_caretViewContext && d->m_caretViewContext->visible) { + QRect pos(d->m_caretViewContext->x, d->m_caretViewContext->y, + d->m_caretViewContext->width, d->m_caretViewContext->height); + if (pos.intersects(QRect(ex, ey, ew, eh))) { + p->setRasterOp(XorROP); + p->setPen(white); + if (pos.width() == 1) + p->drawLine(pos.topLeft(), pos.bottomRight()); + else { + p->fillRect(pos, white); + }/*end if*/ + }/*end if*/ + }/*end if*/ +#endif // KHTML_NO_CARET + +// p->setPen(QPen(magenta,0,DashDotDotLine)); +// p->drawRect(dbg_paint_rect); + + khtml::DrawContentsEvent event( p, ex, ey, ew, eh ); + QApplication::sendEvent( m_part, &event ); + + 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; + + // the reference object for the overflow property on canvas + RenderObject * ref = 0; + RenderObject* root = document->documentElement() ? document->documentElement()->renderer() : 0; + + if (document->isHTMLDocument()) { + NodeImpl *body = static_cast(document)->body(); + if(body && body->renderer() && body->id() == ID_FRAMESET) { + QScrollView::setVScrollBarMode(AlwaysOff); + QScrollView::setHScrollBarMode(AlwaysOff); + body->renderer()->setNeedsLayout(true); +// if (d->tooltip) { +// delete d->tooltip; +// d->tooltip = 0; +// } + } + else { + if (!d->tooltip) + d->tooltip = new KHTMLToolTip( this, d ); + // only apply body's overflow to canvas if root as a visible overflow + if (root) + ref = (!body || root->style()->hidesOverflow()) ? root : body->renderer(); + } + } else { + ref = root; + } + if (ref) { + if( ref->style()->overflowX() == OHIDDEN ) { + if (d->hmode == Auto) QScrollView::setHScrollBarMode(AlwaysOff); + } else if (ref->style()->overflowX() == OSCROLL ) { + if (d->hmode == Auto) QScrollView::setHScrollBarMode(AlwaysOn); + } else { + if (QScrollView::hScrollBarMode() == AlwaysOff) QScrollView::setHScrollBarMode(d->hmode); + } if ( ref->style()->overflowY() == OHIDDEN ) { + if (d->vmode == Auto) QScrollView::setVScrollBarMode(AlwaysOff); + } else if (ref->style()->overflowY() == OSCROLL ) { + if (d->vmode == Auto) QScrollView::setVScrollBarMode(AlwaysOn); + } else { + if (QScrollView::vScrollBarMode() == AlwaysOff) QScrollView::setVScrollBarMode(d->vmode); + } + } + d->needsFullRepaint = d->firstRelayout; + if (_height != visibleHeight() || _width != visibleWidth()) {; + d->needsFullRepaint = true; + _height = visibleHeight(); + _width = visibleWidth(); + } + //QTime qt; + //qt.start(); + canvas->layout(); + + emit finishedLayout(); + if (d->firstRelayout) { + // make sure firstRelayout is set to false now in case this layout + // wasn't scheduled + d->firstRelayout = false; + verticalScrollBar()->setEnabled( true ); + horizontalScrollBar()->setEnabled( true ); + } +#if 0 + ElementImpl *listitem = m_part->xmlDocImpl()->getElementById("__test_element__"); + if (listitem) kdDebug(6000) << "after layout, before repaint" << endl; + if (listitem) dumpLineBoxes(static_cast(listitem->renderer())); +#endif +#ifndef KHTML_NO_CARET + hideCaret(); + if ((m_part->isCaretMode() || m_part->isEditable()) + && !d->complete && d->m_caretViewContext + && !d->m_caretViewContext->caretMoved) { + initCaret(); + } else { + recalcAndStoreCaretPos(); + showCaret(); + }/*end if*/ +#endif + if (d->accessKeysEnabled && d->accessKeysActivated) { + emit hideAccessKeys(); + displayAccessKeys(); + } + //kdDebug( 6000 ) << "TIME: layout() dt=" << qt.elapsed() << endl; + } + else + _width = visibleWidth(); + + killTimer(d->layoutTimerId); + d->layoutTimerId = 0; + d->layoutSchedulingEnabled=true; +} + +void KHTMLView::closeChildDialogs() +{ + QObjectList *dlgs = queryList("QDialog"); + for (QObject *dlg = dlgs->first(); dlg; dlg = dlgs->next()) + { + KDialogBase* dlgbase = dynamic_cast( dlg ); + if ( dlgbase ) { + if ( dlgbase->testWFlags( WShowModal ) ) { + kdDebug(6000) << "closeChildDialogs: closing dialog " << dlgbase << endl; + // close() ends up calling QButton::animateClick, which isn't immediate + // we need something the exits the event loop immediately (#49068) + dlgbase->cancel(); + } + } + else + { + kdWarning() << "closeChildDialogs: not a KDialogBase! Don't use QDialogs in KDE! " << static_cast(dlg) << endl; + static_cast(dlg)->hide(); + } + } + delete dlgs; + 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(); + QScrollView::closeEvent( ev ); +} + +// +// Event Handling +// +///////////////// + +void KHTMLView::viewportMousePressEvent( QMouseEvent *_mouse ) +{ + if (!m_part->xmlDocImpl()) return; + if (d->possibleTripleClick && ( _mouse->button() & MouseButtonMask ) == LeftButton) + { + viewportMouseDoubleClickEvent( _mouse ); // it handles triple clicks too + return; + } + + int xm, ym; + viewportToContents(_mouse->x(), _mouse->y(), xm, ym); + //kdDebug( 6000 ) << "mousePressEvent: viewport=("<<_mouse->x()<<"/"<<_mouse->y()<<"), contents=(" << xm << "/" << ym << ")\n"; + + d->isDoubleClick = false; + + DOM::NodeImpl::MouseEvent mev( _mouse->stateAfter(), DOM::NodeImpl::MousePress ); + m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev ); + + //kdDebug(6000) << "innerNode="<button() == 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, icon; + pixmap.resize( 48, 48 ); + pixmap.fill( QColor( qRgba( 127, 127, 127, 127 ) ) ); + + QPainter p( &pixmap ); + icon = KGlobal::iconLoader()->loadIcon( "1uparrow", KIcon::Small ); + p.drawPixmap( 16, 0, icon ); + icon = KGlobal::iconLoader()->loadIcon( "1leftarrow", KIcon::Small ); + p.drawPixmap( 0, 16, icon ); + icon = KGlobal::iconLoader()->loadIcon( "1downarrow", KIcon::Small ); + p.drawPixmap( 16, 32,icon ); + icon = KGlobal::iconLoader()->loadIcon( "1rightarrow", KIcon::Small ); + p.drawPixmap( 32, 16, icon ); + p.drawEllipse( 23, 23, 2, 2 ); + + d->m_mouseScrollIndicator = new QWidget( this, 0 ); + d->m_mouseScrollIndicator->setFixedSize( 48, 48 ); + d->m_mouseScrollIndicator->setPaletteBackgroundPixmap( pixmap ); + } + d->m_mouseScrollIndicator->move( point.x()-24, point.y()-24 ); + + bool hasHorBar = visibleWidth() < contentsWidth(); + bool hasVerBar = visibleHeight() < contentsHeight(); + + KConfig *config = KGlobal::config(); + KConfigGroupSaver saver( config, "HTML Settings" ); + if ( config->readBoolEntry( "ShowMouseScrollIndicator", true ) ) { + d->m_mouseScrollIndicator->show(); + d->m_mouseScrollIndicator->unsetCursor(); + + QBitmap mask = d->m_mouseScrollIndicator->paletteBackgroundPixmap()->createHeuristicMask( true ); + + if ( hasHorBar && !hasVerBar ) { + QBitmap bm( 16, 16, true ); + bitBlt( &mask, 16, 0, &bm, 0, 0, -1, -1 ); + bitBlt( &mask, 16, 32, &bm, 0, 0, -1, -1 ); + d->m_mouseScrollIndicator->setCursor( KCursor::SizeHorCursor ); + } + else if ( !hasHorBar && hasVerBar ) { + QBitmap bm( 16, 16, true ); + bitBlt( &mask, 0, 16, &bm, 0, 0, -1, -1 ); + bitBlt( &mask, 32, 16, &bm, 0, 0, -1, -1 ); + d->m_mouseScrollIndicator->setCursor( KCursor::SizeVerCursor ); + } + else + d->m_mouseScrollIndicator->setCursor( KCursor::SizeAllCursor ); + + d->m_mouseScrollIndicator->setMask( mask ); + } + else { + if ( hasHorBar && !hasVerBar ) + viewport()->setCursor( KCursor::SizeHorCursor ); + else if ( !hasHorBar && hasVerBar ) + viewport()->setCursor( KCursor::SizeVerCursor ); + else + viewport()->setCursor( KCursor::SizeAllCursor ); + } + + return; + } + else if ( d->m_mouseScrollTimer ) { + delete d->m_mouseScrollTimer; + d->m_mouseScrollTimer = 0; + + if ( d->m_mouseScrollIndicator ) + d->m_mouseScrollIndicator->hide(); + } + + 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); + + khtml::RenderObject* r = mev.innerNode.handle() ? mev.innerNode.handle()->renderer() : 0; + if (r && r->isWidget()) + _mouse->ignore(); + + 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::viewportMouseDoubleClickEvent( QMouseEvent *_mouse ) +{ + if(!m_part->xmlDocImpl()) return; + + int xm, ym; + viewportToContents(_mouse->x(), _mouse->y(), xm, ym); + + kdDebug( 6000 ) << "mouseDblClickEvent: x=" << xm << ", y=" << ym << endl; + + d->isDoubleClick = true; + + DOM::NodeImpl::MouseEvent mev( _mouse->stateAfter(), DOM::NodeImpl::MouseDblClick ); + m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev ); + + // We do the same thing as viewportMousePressEvent() 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); + + khtml::RenderObject* r = mev.innerNode.handle() ? mev.innerNode.handle()->renderer() : 0; + if (r && r->isWidget()) + _mouse->ignore(); + + 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 inline void forwardPeripheralEvent(khtml::RenderWidget* r, QMouseEvent* me, int x, int y) +{ + int absx = 0; + int absy = 0; + r->absolutePosition(absx, absy); + QPoint p(x-absx, y-absy); + QMouseEvent fw(me->type(), p, me->button(), me->state()); + QWidget* w = r->widget(); + QScrollView* sc = ::qt_cast(w); + if (sc && !::qt_cast(w)) + static_cast(sc)->sendEvent(&fw); + else if(w) + static_cast(w)->sendEvent(&fw); +} + + +static bool targetOpensNewWindow(KHTMLPart *part, QString target) +{ + if (!target.isEmpty() && (target.lower() != "_top") && + (target.lower() != "_self") && (target.lower() != "_parent")) { + if (target.lower() == "_blank") + return true; + else { + while (part->parentPart()) + part = part->parentPart(); + if (!part->frameExists(target)) + return true; + } + } + return false; +} + +void KHTMLView::viewportMouseMoveEvent( 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 = kMax(kMin(d->m_mouseScroll_byX * int(adX*adX), SHRT_MAX), SHRT_MIN); + d->m_mouseScroll_byY = kMax(kMin(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->changeInterval( 20 ); + } + } + + if(!m_part->xmlDocImpl()) return; + + int xm, ym; + viewportToContents(_mouse->x(), _mouse->y(), xm, ym); + + DOM::NodeImpl::MouseEvent mev( _mouse->stateAfter(), DOM::NodeImpl::MouseMove ); + // Do not modify :hover/:active state while mouse is pressed. + m_part->xmlDocImpl()->prepareMouseEvent( _mouse->state() & Qt::MouseButtonMask /*readonly ?*/, xm, ym, &mev ); + +// kdDebug(6000) << "mouse move: " << _mouse->pos() +// << " button " << _mouse->button() +// << " state " << _mouse->state() << endl; + + bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEMOVE_EVENT,mev.innerNode.handle(),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 + } + + // execute the scheduled script. This is to make sure the mouseover events come after the mouseout events + m_part->executeScheduledScript(); + + DOM::NodeImpl* fn = m_part->xmlDocImpl()->focusNode(); + if (fn && fn != mev.innerNode.handle() && + fn->renderer() && fn->renderer()->isWidget()) { + forwardPeripheralEvent(static_cast(fn->renderer()), _mouse, xm, ym); + } + + khtml::RenderObject* r = mev.innerNode.handle() ? mev.innerNode.handle()->renderer() : 0; + khtml::RenderStyle* style = (r && r->style()) ? r->style() : 0; + QCursor c; + bool mailtoCursor = false; + bool newWindowCursor = false; + switch ( style ? style->cursor() : CURSOR_AUTO) { + case CURSOR_AUTO: + if ( r && r->isText() ) + c = KCursor::ibeamCursor(); + if ( mev.url.length() && m_part->settings()->changeCursor() ) { + c = m_part->urlCursor(); + if (mev.url.string().startsWith("mailto:") && mev.url.string().find('@')>0) + mailtoCursor = true; + else + newWindowCursor = targetOpensNewWindow( m_part, mev.target.string() ); + } + + if (r && r->isFrameSet() && !static_cast(r)->noResize()) + c = QCursor(static_cast(r)->cursorShape()); + + break; + case CURSOR_CROSS: + c = KCursor::crossCursor(); + break; + case CURSOR_POINTER: + c = m_part->urlCursor(); + if (mev.url.string().startsWith("mailto:") && mev.url.string().find('@')>0) + mailtoCursor = true; + else + newWindowCursor = targetOpensNewWindow( m_part, mev.target.string() ); + break; + case CURSOR_PROGRESS: + c = KCursor::workingCursor(); + break; + case CURSOR_MOVE: + c = KCursor::sizeAllCursor(); + break; + case CURSOR_E_RESIZE: + case CURSOR_W_RESIZE: + c = KCursor::sizeHorCursor(); + break; + case CURSOR_N_RESIZE: + case CURSOR_S_RESIZE: + c = KCursor::sizeVerCursor(); + break; + case CURSOR_NE_RESIZE: + case CURSOR_SW_RESIZE: + c = KCursor::sizeBDiagCursor(); + break; + case CURSOR_NW_RESIZE: + case CURSOR_SE_RESIZE: + c = KCursor::sizeFDiagCursor(); + break; + case CURSOR_TEXT: + c = KCursor::ibeamCursor(); + break; + case CURSOR_WAIT: + c = KCursor::waitCursor(); + break; + case CURSOR_HELP: + c = KCursor::whatsThisCursor(); + break; + case CURSOR_DEFAULT: + break; + } + + if ( viewport()->cursor().handle() != c.handle() ) { + if( c.handle() == KCursor::arrowCursor().handle()) { + for (KHTMLPart* p = m_part; p; p = p->parentPart()) + p->view()->viewport()->unsetCursor(); + } + else { + viewport()->setCursor( c ); + } + } + + if ( ( mailtoCursor || newWindowCursor ) && isVisible() && hasFocus() ) { +#ifdef Q_WS_X11 + QPixmap icon_pixmap = KGlobal::iconLoader()->loadIcon( mailtoCursor ? "mail_generic" : "window_new", KIcon::Small, 0, KIcon::DefaultState, 0, true ); + + if (d->cursor_icon_widget) { + const QPixmap *pm = d->cursor_icon_widget->backgroundPixmap(); + if (!pm || pm->serialNumber()!=icon_pixmap.serialNumber()) { + delete d->cursor_icon_widget; + d->cursor_icon_widget = 0; + } + } + + if( !d->cursor_icon_widget ) { + d->cursor_icon_widget = new QWidget( NULL, NULL, WX11BypassWM ); + XSetWindowAttributes attr; + attr.save_under = True; + XChangeWindowAttributes( qt_xdisplay(), d->cursor_icon_widget->winId(), CWSaveUnder, &attr ); + d->cursor_icon_widget->resize( icon_pixmap.width(), icon_pixmap.height()); + if( icon_pixmap.mask() ) + d->cursor_icon_widget->setMask( *icon_pixmap.mask()); + else + d->cursor_icon_widget->clearMask(); + d->cursor_icon_widget->setBackgroundPixmap( icon_pixmap ); + d->cursor_icon_widget->erase(); + } + QPoint c_pos = QCursor::pos(); + d->cursor_icon_widget->move( c_pos.x() + 15, c_pos.y() + 15 ); + XRaiseWindow( qt_xdisplay(), d->cursor_icon_widget->winId()); + QApplication::flushX(); + d->cursor_icon_widget->show(); +#endif + } + else if ( d->cursor_icon_widget ) + d->cursor_icon_widget->hide(); + + if (r && r->isWidget()) { + _mouse->ignore(); + } + + + d->prevMouseX = xm; + d->prevMouseY = ym; + + if (!swallowEvent) { + khtml::MouseMoveEvent event( _mouse, xm, ym, mev.url, mev.target, mev.innerNode ); + QApplication::sendEvent( m_part, &event ); + } +} + +void KHTMLView::viewportMouseReleaseEvent( QMouseEvent * _mouse ) +{ + bool swallowEvent = false; + int xm, ym; + viewportToContents(_mouse->x(), _mouse->y(), xm, ym); + DOM::NodeImpl::MouseEvent mev( _mouse->stateAfter(), DOM::NodeImpl::MouseRelease ); + + if ( m_part->xmlDocImpl() ) + { + m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev ); + + swallowEvent = dispatchMouseEvent(EventImpl::MOUSEUP_EVENT,mev.innerNode.handle(),mev.innerNonSharedNode.handle(),true, + d->clickCount,_mouse,false,DOM::NodeImpl::MouseRelease); + + 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->state()); + dispatchMouseEvent(EventImpl::CLICK_EVENT, mev.innerNode.handle(),mev.innerNonSharedNode.handle(),true, + d->clickCount, &me, true, DOM::NodeImpl::MouseRelease); + } + + DOM::NodeImpl* fn = m_part->xmlDocImpl()->focusNode(); + if (fn && fn != mev.innerNode.handle() && + fn->renderer() && fn->renderer()->isWidget() && + _mouse->button() != MidButton) { + forwardPeripheralEvent(static_cast(fn->renderer()), _mouse, xm, ym); + } + + khtml::RenderObject* r = mev.innerNode.handle() ? mev.innerNode.handle()->renderer() : 0; + 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 = NULL; + return ret; + } + } + else // QEvent::KeyRelease + { + // Discard postponed "autorepeat key-release" events that didn't see + // a keypress after them (e.g. due to QAccel) + if ( d->postponed_autorepeat ) { + delete d->postponed_autorepeat; + d->postponed_autorepeat = 0; + } + + if( !_ke->isAutoRepeat()) { + return dispatchKeyEventHelper( _ke, false ); // keyup + } + else + { + d->postponed_autorepeat = new QKeyEvent( _ke->type(), _ke->key(), _ke->ascii(), _ke->state(), + _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 ) +{ +#ifndef KHTML_NO_TYPE_AHEAD_FIND + if(d->typeAheadActivated) + { + // type-ahead find aka find-as-you-type + if(_ke->key() == Key_BackSpace) + { + d->findString = d->findString.left(d->findString.length() - 1); + + if(!d->findString.isEmpty()) + { + findAhead(false); + } + else + { + findTimeout(); + } + + d->timer.start(3000, true); + _ke->accept(); + return; + } + else if(_ke->key() == Key_Escape) + { + findTimeout(); + + _ke->accept(); + return; + } + else if(_ke->key() == Key_Space || !_ke->text().stripWhiteSpace().isEmpty()) + { + d->findString += _ke->text(); + + findAhead(true); + + d->timer.start(3000, true); + _ke->accept(); + return; + } + } +#endif // KHTML_NO_TYPE_AHEAD_FIND + +#ifndef KHTML_NO_CARET + if (m_part->isEditable() || m_part->isCaretMode() + || (m_part->xmlDocImpl() && m_part->xmlDocImpl()->focusNode() + && m_part->xmlDocImpl()->focusNode()->contentEditable())) { + d->caretViewContext()->keyReleasePending = true; + caretKeyPressEvent(_ke); + return; + } +#endif // KHTML_NO_CARET + + // If CTRL was hit, be prepared for access keys + if (d->accessKeysEnabled && _ke->key() == Key_Control && _ke->state()==0 && !d->accessKeysActivated) + { + d->accessKeysPreActivate=true; + _ke->accept(); + return; + } + + if (_ke->key() == Key_Shift && _ke->state()==0) + 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->state() & ( ShiftButton | ControlButton | AltButton | MetaButton )); + if ( state==0 || state==ShiftButton) { + if (_ke->key() != Key_Shift) accessKeysTimeout(); + handleAccessKey( _ke ); + _ke->accept(); + return; + } + accessKeysTimeout(); + } + + if ( dispatchKeyEvent( _ke )) { + // If either keydown or keypress was accepted by a widget, or canceled by JS, stop here. + _ke->accept(); + return; + } + + int offs = (clipper()->height() < 30) ? clipper()->height() : 30; + if (_ke->state() & Qt::ShiftButton) + switch(_ke->key()) + { + case Key_Space: + scrollBy( 0, -clipper()->height() + offs ); + if(d->scrollSuspended) + d->newScrollTimer(this, 0); + break; + + case Key_Down: + case Key_J: + d->adjustScroller(this, KHTMLViewPrivate::ScrollDown, KHTMLViewPrivate::ScrollUp); + break; + + case Key_Up: + case Key_K: + d->adjustScroller(this, KHTMLViewPrivate::ScrollUp, KHTMLViewPrivate::ScrollDown); + break; + + case Key_Left: + case Key_H: + d->adjustScroller(this, KHTMLViewPrivate::ScrollLeft, KHTMLViewPrivate::ScrollRight); + break; + + case Key_Right: + case Key_L: + d->adjustScroller(this, KHTMLViewPrivate::ScrollRight, KHTMLViewPrivate::ScrollLeft); + break; + } + else + switch ( _ke->key() ) + { + case Key_Down: + case Key_J: + if (!d->scrollTimerId || d->scrollSuspended) + scrollBy( 0, 10 ); + if (d->scrollTimerId) + d->newScrollTimer(this, 0); + break; + + case Key_Space: + case Key_Next: + scrollBy( 0, clipper()->height() - offs ); + if(d->scrollSuspended) + d->newScrollTimer(this, 0); + break; + + case Key_Up: + case Key_K: + if (!d->scrollTimerId || d->scrollSuspended) + scrollBy( 0, -10 ); + if (d->scrollTimerId) + d->newScrollTimer(this, 0); + break; + + case Key_Prior: + scrollBy( 0, -clipper()->height() + offs ); + if(d->scrollSuspended) + d->newScrollTimer(this, 0); + break; + case Key_Right: + case Key_L: + if (!d->scrollTimerId || d->scrollSuspended) + scrollBy( 10, 0 ); + if (d->scrollTimerId) + d->newScrollTimer(this, 0); + break; + case Key_Left: + case Key_H: + if (!d->scrollTimerId || d->scrollSuspended) + scrollBy( -10, 0 ); + if (d->scrollTimerId) + d->newScrollTimer(this, 0); + break; + case Key_Enter: + case 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 Key_Home: + setContentsPos( 0, 0 ); + if(d->scrollSuspended) + d->newScrollTimer(this, 0); + break; + case Key_End: + setContentsPos( 0, contentsHeight() - visibleHeight() ); + if(d->scrollSuspended) + d->newScrollTimer(this, 0); + break; + case 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::findTimeout() +{ +#ifndef KHTML_NO_TYPE_AHEAD_FIND + d->typeAheadActivated = false; + d->findString = ""; + m_part->setStatusBarText(i18n("Find stopped."), KHTMLPart::BarDefaultText); + m_part->enableFindAheadActions( true ); +#endif // KHTML_NO_TYPE_AHEAD_FIND +} + +#ifndef KHTML_NO_TYPE_AHEAD_FIND +void KHTMLView::startFindAhead( bool linksOnly ) +{ + if( linksOnly ) + { + d->findLinksOnly = true; + m_part->setStatusBarText(i18n("Starting -- find links as you type"), + KHTMLPart::BarDefaultText); + } + else + { + d->findLinksOnly = false; + m_part->setStatusBarText(i18n("Starting -- find text as you type"), + KHTMLPart::BarDefaultText); + } + + m_part->findTextBegin(); + d->typeAheadActivated = true; + // disable, so that the shortcut ( / or ' by default ) doesn't interfere + m_part->enableFindAheadActions( false ); + d->timer.start(3000, true); +} + +void KHTMLView::findAhead(bool increase) +{ + QString status; + + if(d->findLinksOnly) + { + m_part->findText(d->findString, KHTMLPart::FindNoPopups | + KHTMLPart::FindLinksOnly, this); + if(m_part->findTextNext()) + { + status = i18n("Link found: \"%1\"."); + } + else + { + if(increase) KNotifyClient::beep(); + status = i18n("Link not found: \"%1\"."); + } + } + else + { + m_part->findText(d->findString, KHTMLPart::FindNoPopups, this); + if(m_part->findTextNext()) + { + status = i18n("Text found: \"%1\"."); + } + else + { + if(increase) KNotifyClient::beep(); + status = i18n("Text not found: \"%1\"."); + } + } + + m_part->setStatusBarText(status.arg(d->findString.lower()), + KHTMLPart::BarDefaultText); +} + +void KHTMLView::updateFindAheadTimeout() +{ + if( d->typeAheadActivated ) + d->timer.start( 3000, true ); +} + +#endif // KHTML_NO_TYPE_AHEAD_FIND + +void KHTMLView::keyReleaseEvent(QKeyEvent *_ke) +{ +#ifndef KHTML_NO_TYPE_AHEAD_FIND + if(d->typeAheadActivated) { + _ke->accept(); + return; + } +#endif + if (d->m_caretViewContext && d->m_caretViewContext->keyReleasePending) { + //caretKeyReleaseEvent(_ke); + d->m_caretViewContext->keyReleasePending = false; + return; + } + + if( d->scrollSuspendPreActivate && _ke->key() != Key_Shift ) + d->scrollSuspendPreActivate = false; + if( _ke->key() == Key_Shift && d->scrollSuspendPreActivate && _ke->state() == Qt::ShiftButton + && !(KApplication::keyboardMouseState() & Qt::ShiftButton)) + if (d->scrollTimerId) + d->scrollSuspended = !d->scrollSuspended; + + if (d->accessKeysEnabled) + { + if (d->accessKeysPreActivate && _ke->key() != Key_Control) + d->accessKeysPreActivate=false; + if (d->accessKeysPreActivate && _ke->state() == Qt::ControlButton && !(KApplication::keyboardMouseState() & Qt::ControlButton)) + { + 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; + } + + QScrollView::keyReleaseEvent(_ke); +} + +void KHTMLView::contentsContextMenuEvent ( QContextMenuEvent * /*ce*/ ) +{ +// ### what kind of c*** is that ? +#if 0 + if (!m_part->xmlDocImpl()) return; + int xm = _ce->x(); + int ym = _ce->y(); + + DOM::NodeImpl::MouseEvent mev( _ce->state(), DOM::NodeImpl::MouseMove ); // ### not a mouse event! + m_part->xmlDocImpl()->prepareMouseEvent( xm, ym, &mev ); + + NodeImpl *targetNode = mev.innerNode.handle(); + if (targetNode && targetNode->renderer() && targetNode->renderer()->isWidget()) { + int absx = 0; + int absy = 0; + targetNode->renderer()->absolutePosition(absx,absy); + QPoint pos(xm-absx,ym-absy); + + QWidget *w = static_cast(targetNode->renderer())->widget(); + QContextMenuEvent cme(_ce->reason(),pos,_ce->globalPos(),_ce->state()); + setIgnoreEvents(true); + QApplication::sendEvent(w,&cme); + setIgnoreEvents(false); + } +#endif +} + +bool KHTMLView::focusNextPrevChild( bool next ) +{ + // Now try to find the next child + if (m_part->xmlDocImpl() && focusNextPrevNode(next)) + { + if (m_part->xmlDocImpl()->focusNode()) + kdDebug() << "focusNode.name: " + << m_part->xmlDocImpl()->focusNode()->nodeName().string() << endl; + 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(); + pos = viewport()->mapFromGlobal( pos ); + + 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()) { + int absX, absY; + innerNode.handle()->renderer()->absolutePosition(absX, absY); + + m_part->extendSelectionTo(xm, ym, absX, absY, innerNode); + }/*end if*/ +#endif // KHTML_NO_SELECTION + } +} + + +class HackWidget : public QWidget +{ + public: + inline void setNoErase() { setWFlags(getWFlags()|WRepaintNoErase); } +}; + +bool KHTMLView::eventFilter(QObject *o, QEvent *e) +{ + if ( e->type() == QEvent::AccelOverride ) { + QKeyEvent* ke = (QKeyEvent*) e; +//kdDebug(6200) << "QEvent::AccelOverride" << endl; + if (m_part->isEditable() || m_part->isCaretMode() + || (m_part->xmlDocImpl() && m_part->xmlDocImpl()->focusNode() + && m_part->xmlDocImpl()->focusNode()->contentEditable())) { +//kdDebug(6200) << "editable/navigable" << endl; + if ( (ke->state() & ControlButton) || (ke->state() & ShiftButton) ) { + switch ( ke->key() ) { + case Key_Left: + case Key_Right: + case Key_Up: + case Key_Down: + case Key_Home: + case Key_End: + ke->accept(); +//kdDebug(6200) << "eaten" << endl; + return true; + default: + break; + } + } + } + } + + if ( e->type() == QEvent::Leave ) { + if ( d->cursor_icon_widget ) + d->cursor_icon_widget->hide(); + m_part->resetHoverText(); + } + + QWidget *view = viewport(); + + if (o == view) { + // we need to install an event filter on all children of the viewport to + // be able to get correct stacking of children within the document. + if(e->type() == QEvent::ChildInserted) { + QObject *c = static_cast(e)->child(); + if (c->isWidgetType()) { + QWidget *w = static_cast(c); + // don't install the event filter on toplevels + if (w->parentWidget(true) == view) { + if (!strcmp(w->name(), "__khtml")) { + w->installEventFilter(this); + w->unsetCursor(); + if (!::qt_cast(w)) + w->setBackgroundMode( QWidget::NoBackground ); + static_cast(w)->setNoErase(); + if (w->children()) { + QObjectListIterator it(*w->children()); + for (; it.current(); ++it) { + QWidget *widget = ::qt_cast(it.current()); + if (widget && !widget->isTopLevel()) { + if (!::qt_cast(w)) + widget->setBackgroundMode( QWidget::NoBackground ); + static_cast(widget)->setNoErase(); + widget->installEventFilter(this); + } + } + } + } + } + } + } + } else if (o->isWidgetType()) { + QWidget *v = static_cast(o); + QWidget *c = v; + while (v && v != view) { + c = v; + v = v->parentWidget(true); + } + + if (v && !strcmp(c->name(), "__khtml")) { + bool block = false; + QWidget *w = static_cast(o); + switch(e->type()) { + case QEvent::Paint: + if (!allowWidgetPaintEvents) { + // eat the event. Like this we can control exactly when the widget + // get's repainted. + block = true; + int x = 0, y = 0; + QWidget *v = w; + while (v && v != view) { + x += v->x(); + y += v->y(); + v = v->parentWidget(); + } + viewportToContents( x, y, x, y ); + QPaintEvent *pe = static_cast(e); + bool asap = !d->contentsMoving && ::qt_cast(c); + + // QScrollView needs fast repaints + if ( asap && !d->painting && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer() && + !static_cast(m_part->xmlDocImpl()->renderer())->needsLayout() ) { + repaintContents(x + pe->rect().x(), y + pe->rect().y(), + pe->rect().width(), pe->rect().height(), true); + } else { + scheduleRepaint(x + pe->rect().x(), y + pe->rect().y(), + pe->rect().width(), pe->rect().height(), asap); + } + } + break; + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: { + if ( (w->parentWidget() == view || ::qt_cast(c)) && !::qt_cast(w)) { + QMouseEvent *me = static_cast(e); + QPoint pt = w->mapTo( view, me->pos()); + QMouseEvent me2(me->type(), pt, me->button(), me->state()); + + if (e->type() == QEvent::MouseMove) + viewportMouseMoveEvent(&me2); + else if(e->type() == QEvent::MouseButtonPress) + viewportMousePressEvent(&me2); + else if(e->type() == QEvent::MouseButtonRelease) + viewportMouseReleaseEvent(&me2); + else + viewportMouseDoubleClickEvent(&me2); + block = true; + } + break; + } + case QEvent::KeyPress: + case QEvent::KeyRelease: + if (w->parentWidget() == view && !::qt_cast(w)) { + QKeyEvent *ke = static_cast(e); + if (e->type() == QEvent::KeyPress) + keyPressEvent(ke); + else + keyReleaseEvent(ke); + block = true; + } + default: + break; + } + if (block) { + //qDebug("eating event"); + return true; + } + } + } + +// kdDebug(6000) <<"passing event on to sv event filter object=" << o->className() << " event=" << e->type() << endl; + return QScrollView::eventFilter(o, e); +} + + +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(); + + //kdDebug(6000)<<"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 + { +#ifndef KHTML_NO_CARET + // if it's an editable element, activate the caret + if (!m_part->isCaretMode() && !m_part->isEditable() + && newFocusNode->contentEditable()) { + d->caretViewContext(); + moveCaretTo(newFocusNode, 0L, true); + } else { + caretOff(); + } +#endif // KHTML_NO_CARET + + targetVisible = scrollTo(newFocusNode->getRect()); + } + + if (targetVisible) + { + //kdDebug ( 6000 ) << " 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() +{ + QValueVector< QChar > taken; + displayAccessKeys( NULL, this, taken, false ); + displayAccessKeys( NULL, this, taken, true ); +} + +void KHTMLView::displayAccessKeys( KHTMLView* caller, KHTMLView* origview, QValueVector< QChar >& taken, bool use_fallbacks ) +{ + QMap< ElementImpl*, QChar > fallbacks; + if( use_fallbacks ) + fallbacks = buildFallbackAccessKeys(); + for( NodeImpl* n = m_part->xmlDocImpl(); n != NULL; 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 ].upper(); + if( qFind( taken.begin(), taken.end(), a ) == taken.end()) // !contains + accesskey = a; + } + if( accesskey.isNull() && fallbacks.contains( en )) { + QChar a = fallbacks[ en ].upper(); + 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,viewport(),0,Qt::WDestructiveClose); + 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->setMargin(3); + lab->adjustSize(); + addChild(lab, + KMIN(rec.left()+rec.width()/2, contentsWidth() - lab->width()), + KMIN(rec.top()+rec.height()/2, contentsHeight() - lab->height())); + showChild(lab); + taken.append( accesskey[ 0 ] ); + } + } + } + if( use_fallbacks ) + return; + QPtrList frames = m_part->frames(); + for( QPtrListIterator it( frames ); + it != NULL; + ++it ) { + if( !(*it)->inherits( "KHTMLPart" )) + continue; + KHTMLPart* part = static_cast< KHTMLPart* >( *it ); + 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 ); +} + + + +void KHTMLView::accessKeysTimeout() +{ +d->accessKeysActivated=false; +d->accessKeysPreActivate = false; +m_part->setStatusBarText(QString::null, 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() >= Key_A && ev->key() <= Key_Z ) + c = 'A' + ev->key() - Key_A; + else if( ev->key() >= Key_0 && ev->key() <= Key_9 ) + c = '0' + ev->key() - 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 ) { + QPtrList frames = m_part->frames(); + for( QPtrListIterator it( frames ); + it != NULL; + ++it ) { + if( !(*it)->inherits( "KHTMLPart" )) + continue; + KHTMLPart* part = static_cast< KHTMLPart* >( *it ); + 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 == NULL ) { // the active frame (where the accesskey was pressed) + 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 == NULL ) + return false; + } + + // Scroll the view as necessary to ensure that the new focus node is visible +#ifndef KHTML_NO_CARET + // if it's an editable element, activate the caret + if (!m_part->isCaretMode() && !m_part->isEditable() + && node->contentEditable()) { + d->caretViewContext(); + moveCaretTo(node, 0L, true); + } else { + caretOff(); + } +#endif // KHTML_NO_CARET + + 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 + QFocusEvent::setReason( QFocusEvent::Shortcut ); + m_part->xmlDocImpl()->setFocusNode(node); + QFocusEvent::resetReason(); + if( node != NULL && node->hasOneRef()) // deleted, only held by guard + return true; + emit m_part->nodeActivated(Node(node)); + if( node != NULL && 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 != NULL; + 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.stripWhiteSpace().isEmpty()) + break; + // fall through + default: + return ret.simplifyWhiteSpace(); + } + } + } + return ret.simplifyWhiteSpace(); +} + +static QMap< NodeImpl*, QString > buildLabels( NodeImpl* start ) +{ + QMap< NodeImpl*, QString > ret; + for( NodeImpl* n = start; + n != NULL; + 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().simplifyWhiteSpace(); + } + } + 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 + QValueList< AccessKeyData > data; + QMap< NodeImpl*, QString > labels = buildLabels( m_part->xmlDocImpl()); + for( NodeImpl* n = m_part->xmlDocImpl(); + n != NULL; + n = n->traverseNextNode()) { + if( n->isElementNode()) { + ElementImpl* element = static_cast< ElementImpl* >( n ); + if( element->getAttribute( ATTR_ACCESSKEY ).length() == 1 ) + continue; // has accesskey set, ignore + if( element->renderer() == NULL ) + 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 = khtml::parseURL(element->getAttribute(ATTR_HREF)).string(); + if( url.isEmpty()) // doesn't have href, it's only an anchor + continue; + text = static_cast< HTMLElementImpl* >( element )->innerText().string().simplifyWhiteSpace(); + 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().simplifyWhiteSpace(); + 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; + 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.stripWhiteSpace(); + // increase priority of items which have explicitly specified accesskeys in the config + QValueList< QPair< QString, QChar > > priorities + = m_part->settings()->fallbackAccessKeysAssignments(); + for( QValueList< 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 ); + } + } + + QValueList< 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 != NULL; + 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 ].upper(); + keys.remove( c ); // remove manually assigned accesskeys + } + } + } + + QMap< ElementImpl*, QChar > ret; + for( int priority = 10; + priority >= 0; + --priority ) { + for( QValueList< AccessKeyData >::Iterator it = data.begin(); + it != data.end(); + ) { + if( (*it).priority != priority ) { + ++it; + continue; + } + if( keys.isEmpty()) + break; + QString text = (*it).text; + QChar key; + if( key.isNull() && !text.isEmpty()) { + QValueList< QPair< QString, QChar > > priorities + = m_part->settings()->fallbackAccessKeysAssignments(); + for( QValueList< 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()) { + QStringList words = QStringList::split( ' ', text ); + for( QStringList::ConstIterator it = words.begin(); + it != words.end(); + ++it ) { + if( keys.contains( (*it)[ 0 ].upper())) { + key = (*it)[ 0 ].upper(); + break; + } + } + } + if( key.isNull() && !text.isEmpty()) { + for( unsigned int i = 0; + i < text.length(); + ++i ) { + if( keys.contains( text[ i ].upper())) { + key = text[ i ].upper(); + break; + } + } + } + if( key.isNull()) + key = keys.front(); + ret[ (*it).element ] = key; + keys.remove( key ); + QString url = (*it).url; + it = data.remove( it ); + // assign the same accesskey also to other elements pointing to the same url + if( !url.isEmpty() && !url.startsWith( "javascript:", false )) { + for( QValueList< AccessKeyData >::Iterator it2 = data.begin(); + it2 != data.end(); + ) { + if( (*it2).url == url ) { + ret[ (*it2).element ] = key; + if( it == it2 ) + ++it; + it2 = data.remove( 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.replace(w, w->widget()); + } + else + d->visibleWidgets.remove(w); +} + +bool KHTMLView::needsFullRepaint() const +{ + return d->needsFullRepaint; +} + +void KHTMLView::print() +{ + print( false ); +} + +void KHTMLView::print(bool quick) +{ + if(!m_part->xmlDocImpl()) return; + khtml::RenderCanvas *root = static_cast(m_part->xmlDocImpl()->renderer()); + if(!root) return; + + KPrinter *printer = new KPrinter(true, QPrinter::ScreenResolution); + printer->addDialogPage(new KHTMLPrintSettings()); + QString docname = m_part->xmlDocImpl()->URL().prettyURL(); + if ( !docname.isEmpty() ) + docname = KStringHandler::csqueeze(docname, 80); + if(quick || printer->setup(this, i18n("Print %1").arg(docname))) { + viewport()->setCursor( 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(KDE_VERSION_MAJOR).arg(KDE_VERSION_MINOR).arg(KDE_VERSION_RELEASE)); + 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( printer->option("app-khtml-printfriendly") == "true" ? + "* { 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; }" + ); + + QPaintDeviceMetrics metrics( printer ); + + kdDebug(6000) << "printing: physical page width = " << metrics.width() + << " height = " << metrics.height() << endl; + root->setStaticMode(true); + root->setPagedMode(true); + root->setWidth(metrics.width()); +// root->setHeight(metrics.height()); + root->setPageTop(0); + root->setPageBottom(0); + d->paged = true; + + m_part->xmlDocImpl()->styleSelector()->computeFontSizes(&metrics, 100); + m_part->xmlDocImpl()->updateStyleSelector(); + root->setPrintImages( printer->option("app-khtml-printimages") == "true"); + root->makePageBreakAvoidBlocks(); + + root->setNeedsLayoutAndMinMaxRecalc(); + root->layout(); + khtml::RenderWidget::flushWidgetResizes(); // make sure widgets have their final size + + // check sizes ask for action.. (scale or clip) + + bool printHeader = (printer->option("app-khtml-printheader") == "true"); + + int headerHeight = 0; + QFont headerFont("Sans Serif", 8); + + QString headerLeft = KGlobal::locale()->formatDate(QDate::currentDate(),true); + QString headerMid = docname; + QString headerRight; + + if (printHeader) + { + p->setFont(headerFont); + headerHeight = (p->fontMetrics().lineSpacing() * 3) / 2; + } + + // ok. now print the pages. + kdDebug(6000) << "printing: html page width = " << root->docWidth() + << " height = " << root->docHeight() << endl; + kdDebug(6000) << "printing: margins left = " << printer->margins().width() + << " top = " << printer->margins().height() << endl; + kdDebug(6000) << "printing: paper width = " << metrics.width() + << " height = " << metrics.height() << endl; + // if the width is too large to fit on the paper we just scale + // the whole thing. + int pageWidth = metrics.width(); + int pageHeight = metrics.height(); + p->setClipRect(0,0, pageWidth, pageHeight); + + pageHeight -= headerHeight; + + bool scalePage = false; + double scale = 0.0; +#ifndef QT_NO_TRANSFORMATIONS + if(root->docWidth() > metrics.width()) { + scalePage = true; + scale = ((double) metrics.width())/((double) root->docWidth()); + pageHeight = (int) (pageHeight/scale); + pageWidth = (int) (pageWidth/scale); + headerHeight = (int) (headerHeight/scale); + } +#endif + kdDebug(6000) << "printing: scaled html width = " << pageWidth + << " height = " << pageHeight << endl; + + 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 = metrics.width() - 10 - + 2 * kMax(p->boundingRect(0, 0, metrics.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerLeft).width(), + p->boundingRect(0, 0, metrics.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, metrics.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(); + p->setClipRect(0, 0, pageWidth, headerHeight, QPainter::CoordDevice); + if (printHeader) + { + int dy = p->fontMetrics().lineSpacing(); + p->setPen(Qt::black); + p->setFont(headerFont); + + headerRight = QString("#%1").arg(page); + + p->drawText(0, 0, metrics.width(), dy, Qt::AlignLeft, headerLeft); + p->drawText(0, 0, metrics.width(), dy, Qt::AlignHCenter, headerMid); + p->drawText(0, 0, metrics.width(), dy, Qt::AlignRight, headerRight); + } + + +#ifndef QT_NO_TRANSFORMATIONS + if (scalePage) + p->scale(scale, scale); +#endif + + p->setClipRect(0, headerHeight, pageWidth, pageHeight, QPainter::CoordDevice); + 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)); +// m_part->xmlDocImpl()->renderer()->layer()->paint(p, QRect(0, top, pageWidth, pageHeight)); +// root->repaint(); +// p->flush(); + kdDebug(6000) << "printed: page " << page <<" bottom At = " << bottom << endl; + + top = bottom; + p->resetXForm(); + 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( 0 ); + setMediaType( oldMediaType ); + m_part->xmlDocImpl()->setPaintDevice( this ); + m_part->xmlDocImpl()->styleSelector()->computeFontSizes(m_part->xmlDocImpl()->paintDeviceMetrics(), m_part->zoomFactor()); + m_part->xmlDocImpl()->updateStyleSelector(); + viewport()->unsetCursor(); + } + delete printer; +} + +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; + + m_part->xmlDocImpl()->setPaintDevice(p->device()); + root->setPagedMode(true); + root->setStaticMode(true); + root->setWidth(rc.width()); + + p->save(); + 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(); + p->restore(); + + root->setPagedMode(false); + root->setStaticMode(false); + m_part->xmlDocImpl()->setPaintDevice( this ); +} + + +void KHTMLView::useSlowRepaints() +{ + d->useSlowRepaints = true; + setStaticBackground(true); +} + + +void KHTMLView::setVScrollBarMode ( ScrollBarMode mode ) +{ +#ifndef KHTML_NO_SCROLLBARS + d->vmode = mode; + QScrollView::setVScrollBarMode(mode); +#else + Q_UNUSED( mode ); +#endif +} + +void KHTMLView::setHScrollBarMode ( ScrollBarMode mode ) +{ +#ifndef KHTML_NO_SCROLLBARS + d->hmode = mode; + QScrollView::setHScrollBarMode(mode); +#else + Q_UNUSED( mode ); +#endif +} + +void KHTMLView::restoreScrollBar() +{ + int ow = visibleWidth(); + QScrollView::setVScrollBarMode(d->vmode); + 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 KSimpleConfig(locateLocal("data", "khtml/formcompletions")); + return d->formCompletions->readListEntry(name); +} + +void KHTMLView::clearCompletionHistory(const QString& name) +{ + if (!d->formCompletions) + { + d->formCompletions = new KSimpleConfig(locateLocal("data", "khtml/formcompletions")); + } + d->formCompletions->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 (unsigned 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.remove(items.fromLast()); + d->formCompletions->writeEntry(name, items); +} + +void KHTMLView::addNonPasswordStorableSite(const QString& host) +{ + if (!d->formCompletions) { + d->formCompletions = new KSimpleConfig(locateLocal("data", "khtml/formcompletions")); + } + + d->formCompletions->setGroup("NonPasswordStorableSites"); + QStringList sites = d->formCompletions->readListEntry("Sites"); + sites.append(host); + d->formCompletions->writeEntry("Sites", sites); + d->formCompletions->sync(); + d->formCompletions->setGroup(QString::null);//reset +} + +bool KHTMLView::nonPasswordStorableSite(const QString& host) const +{ + if (!d->formCompletions) { + d->formCompletions = new KSimpleConfig(locateLocal("data", "khtml/formcompletions")); + } + d->formCompletions->setGroup("NonPasswordStorableSites"); + QStringList sites = d->formCompletions->readListEntry("Sites"); + d->formCompletions->setGroup(QString::null);//reset + + return (sites.find(host) != sites.end()); +} + +// 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) +{ + // 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(); + + int exceptioncode = 0; + int pageX = 0; + int pageY = 0; + viewportToContents(_mouse->x(), _mouse->y(), 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 LeftButton: + button = 0; + break; + case MidButton: + button = 1; + break; + case RightButton: + button = 2; + break; + default: + break; + } + if (d->accessKeysEnabled && d->accessKeysPreActivate && button!=-1) + d->accessKeysPreActivate=false; + + bool ctrlKey = (_mouse->state() & ControlButton); + bool altKey = (_mouse->state() & AltButton); + bool shiftKey = (_mouse->state() & ShiftButton); + bool metaKey = (_mouse->state() & MetaButton); + + // mouseout/mouseover + if (setUnder && (d->prevMouseX != pageX || d->prevMouseY != pageY)) { + + // ### this code sucks. we should save the oldUnder instead of calculating + // it again. calculating is expensive! (Dirk) + NodeImpl *oldUnder = 0; + if (d->prevMouseX >= 0 && d->prevMouseY >= 0) { + NodeImpl::MouseEvent mev( _mouse->stateAfter(), static_cast(mouseEventType)); + m_part->xmlDocImpl()->prepareMouseEvent( true, d->prevMouseX, d->prevMouseY, &mev ); + oldUnder = mev.innerNode.handle(); + + if (oldUnder && oldUnder->isTextNode()) + oldUnder = oldUnder->parentNode(); + } +// qDebug("oldunder=%p (%s), target=%p (%s) x/y=%d/%d", oldUnder, oldUnder ? oldUnder->renderer()->renderName() : 0, targetNode, targetNode ? targetNode->renderer()->renderName() : 0, _mouse->x(), _mouse->y()); + if (oldUnder != targetNode) { + // send mouseout event to the old node + if (oldUnder){ + oldUnder->ref(); + 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(); + oldUnder->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,oldUnder); + + me->ref(); + targetNode->dispatchEvent(me,exceptioncode,true); + me->deref(); + } + + if (oldUnder) + oldUnder->deref(); + } + } + + bool swallowEvent = false; + + if (targetNode) { + // 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,0, _mouse, dblclick ); + me->ref(); + targetNode->dispatchEvent(me,exceptioncode,true); + bool defaultHandled = me->defaultHandled(); + if (defaultHandled || me->defaultPrevented()) + swallowEvent = true; + me->deref(); + + if (eventId == EventImpl::MOUSEDOWN_EVENT) { + // 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(0); + } + } + + return swallowEvent; +} + +void KHTMLView::setIgnoreWheelEvents( bool e ) +{ + d->ignoreWheelEvents = e; +} + +#ifndef QT_NO_WHEELEVENT + +void KHTMLView::viewportWheelEvent(QWheelEvent* e) +{ + if (d->accessKeysEnabled && d->accessKeysPreActivate) d->accessKeysPreActivate=false; + + if ( ( e->state() & ControlButton) == ControlButton ) + { + emit zoomView( - e->delta() ); + e->accept(); + } + else if (d->firstRelayout) + { + e->accept(); + } + else if( ( (e->orientation() == Vertical && + ((d->ignoreWheelEvents && !verticalScrollBar()->isVisible()) + || e->delta() > 0 && contentsY() <= 0 + || e->delta() < 0 && contentsY() >= contentsHeight() - visibleHeight())) + || + (e->orientation() == 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 + { + d->scrollBarMoved = true; + QScrollView::viewportWheelEvent( e ); + + QMouseEvent *tempEvent = new QMouseEvent( QEvent::MouseMove, QPoint(-1,-1), QPoint(-1,-1), Qt::NoButton, e->state() ); + emit viewportMouseMoveEvent ( tempEvent ); + delete tempEvent; + } + +} +#endif + +void KHTMLView::dragEnterEvent( QDragEnterEvent* ev ) +{ + // Handle drops onto frames (#16820) + // Drops on the main html part is handled by Konqueror (and shouldn't do anything + // in e.g. kmail, so not handled here). + if ( m_part->parentPart() ) + { + QApplication::sendEvent(m_part->parentPart()->widget(), ev); + return; + } + QScrollView::dragEnterEvent( ev ); +} + +void KHTMLView::dropEvent( QDropEvent *ev ) +{ + // Handle drops onto frames (#16820) + // Drops on the main html part is handled by Konqueror (and shouldn't do anything + // in e.g. kmail, so not handled here). + if ( m_part->parentPart() ) + { + QApplication::sendEvent(m_part->parentPart()->widget(), ev); + return; + } + QScrollView::dropEvent( ev ); +} + +void KHTMLView::focusInEvent( QFocusEvent *e ) +{ +#ifndef KHTML_NO_TYPE_AHEAD_FIND + m_part->enableFindAheadActions( true ); +#endif + DOM::NodeImpl* fn = m_part->xmlDocImpl() ? m_part->xmlDocImpl()->focusNode() : 0; + if (fn && fn->renderer() && fn->renderer()->isWidget() && + (e->reason() != QFocusEvent::Mouse) && + static_cast(fn->renderer())->widget()) + static_cast(fn->renderer())->widget()->setFocus(); +#ifndef KHTML_NO_CARET + // Restart blink frequency timer if it has been killed, but only on + // editable nodes + if (d->m_caretViewContext && + d->m_caretViewContext->freqTimerId == -1 && + fn) { + if (m_part->isCaretMode() + || m_part->isEditable() + || (fn && fn->renderer() + && fn->renderer()->style()->userInput() + == UI_ENABLED)) { + d->m_caretViewContext->freqTimerId = startTimer(500); + d->m_caretViewContext->visible = true; + }/*end if*/ + }/*end if*/ + showCaret(); +#endif // KHTML_NO_CARET + QScrollView::focusInEvent( e ); +} + +void KHTMLView::focusOutEvent( QFocusEvent *e ) +{ + if(m_part) m_part->stopAutoScroll(); + +#ifndef KHTML_NO_TYPE_AHEAD_FIND + if(d->typeAheadActivated) + { + findTimeout(); + } + m_part->enableFindAheadActions( false ); +#endif // KHTML_NO_TYPE_AHEAD_FIND + +#ifndef KHTML_NO_CARET + if (d->m_caretViewContext) { + switch (d->m_caretViewContext->displayNonFocused) { + case KHTMLPart::CaretInvisible: + hideCaret(); + break; + case KHTMLPart::CaretVisible: { + killTimer(d->m_caretViewContext->freqTimerId); + d->m_caretViewContext->freqTimerId = -1; + NodeImpl *caretNode = m_part->xmlDocImpl()->focusNode(); + if (!d->m_caretViewContext->visible && (m_part->isCaretMode() + || m_part->isEditable() + || (caretNode && caretNode->renderer() + && caretNode->renderer()->style()->userInput() + == UI_ENABLED))) { + d->m_caretViewContext->visible = true; + showCaret(true); + }/*end if*/ + break; + } + case KHTMLPart::CaretBlink: + // simply leave as is + break; + }/*end switch*/ + }/*end if*/ +#endif // KHTML_NO_CARET + + if ( d->cursor_icon_widget ) + d->cursor_icon_widget->hide(); + + QScrollView::focusOutEvent( e ); +} + +void KHTMLView::slotScrollBarMoved() +{ + if ( !d->firstRelayout && !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->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); +} + +void KHTMLView::timerEvent ( QTimerEvent *e ) +{ +// kdDebug() << "timer event " << e->timerId() << endl; + if ( e->timerId() == d->scrollTimerId ) { + if( d->scrollSuspended ) + return; + switch (d->scrollDirection) { + case KHTMLViewPrivate::ScrollDown: + if (contentsY() + visibleHeight () >= contentsHeight()) + d->newScrollTimer(this, 0); + else + scrollBy( 0, d->scrollBy ); + break; + case KHTMLViewPrivate::ScrollUp: + if (contentsY() <= 0) + d->newScrollTimer(this, 0); + else + scrollBy( 0, -d->scrollBy ); + break; + case KHTMLViewPrivate::ScrollRight: + if (contentsX() + visibleWidth () >= contentsWidth()) + d->newScrollTimer(this, 0); + else + scrollBy( d->scrollBy, 0 ); + break; + case KHTMLViewPrivate::ScrollLeft: + if (contentsX() <= 0) + d->newScrollTimer(this, 0); + else + scrollBy( -d->scrollBy, 0 ); + break; + } + return; + } + else if ( e->timerId() == d->layoutTimerId ) { + d->dirtyLayout = true; + layout(); + if (d->firstRelayout) { + d->firstRelayout = false; + verticalScrollBar()->setEnabled( true ); + horizontalScrollBar()->setEnabled( true ); + } + } +#ifndef KHTML_NO_CARET + else if (d->m_caretViewContext + && e->timerId() == d->m_caretViewContext->freqTimerId) { + d->m_caretViewContext->visible = !d->m_caretViewContext->visible; + if (d->m_caretViewContext->displayed) { + updateContents(d->m_caretViewContext->x, d->m_caretViewContext->y, + d->m_caretViewContext->width, + d->m_caretViewContext->height); + }/*end if*/ +// if (d->m_caretViewContext->visible) cout << "|" << flush; +// else cout << "" << flush; + return; + } +#endif + + d->contentsMoving = false; + if( m_part->xmlDocImpl() ) { + DOM::DocumentImpl *document = m_part->xmlDocImpl(); + khtml::RenderCanvas* root = static_cast(document->renderer()); + + if ( root && root->needsLayout() ) { + killTimer(d->repaintTimerId); + d->repaintTimerId = 0; + scheduleRelayout(); + return; + } + } + + setStaticBackground(d->useSlowRepaints); + +// kdDebug() << "scheduled repaint "<< d->repaintTimerId << endl; + killTimer(d->repaintTimerId); + d->repaintTimerId = 0; + + QRect updateRegion; + QMemArray rects = d->updateRegion.rects(); + + d->updateRegion = QRegion(); + + if ( rects.size() ) + updateRegion = rects[0]; + + for ( unsigned i = 1; i < rects.size(); ++i ) { + QRect newRegion = updateRegion.unite(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 assocoated widget won't be repositioned! + // Thus we need to check each supposedly 'visible' widget at the end of each layout, and remove it in case it's no more in sight. + + if (d->dirtyLayout && !d->visibleWidgets.isEmpty()) { + QWidget* w; + d->dirtyLayout = false; + + QRect visibleRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()); + QPtrList toRemove; + for (QPtrDictIterator it(d->visibleWidgets); it.current(); ++it) { + int xp = 0, yp = 0; + w = it.current(); + RenderWidget* rw = static_cast( it.currentKey() ); + if (!rw->absolutePosition(xp, yp) || + !visibleRect.intersects(QRect(xp, yp, w->width(), w->height()))) + toRemove.append(rw); + } + for (RenderWidget* r = toRemove.first(); r; r = toRemove.next()) + if ( (w = d->visibleWidgets.take(r) ) ) + addChild(w, 0, -500000); + } + + 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::scheduleRelayout(khtml::RenderObject * /*clippedObj*/) +{ + if (!d->layoutSchedulingEnabled || d->layoutTimerId) + return; + + d->layoutTimerId = startTimer( m_part->xmlDocImpl() && m_part->xmlDocImpl()->parsing() + ? 1000 : 0 ); +} + +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(); + +// kdDebug() << "parsing " << parsing << endl; +// kdDebug() << "complete " << d->complete << endl; + + int time = parsing ? 300 : (!asap ? ( !d->complete ? 100 : 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.unite(QRect(x,y,w,h)); + + if (asap && !parsing) + unscheduleRepaint(); + + if ( !d->repaintTimerId ) + d->repaintTimerId = startTimer( time ); + +// kdDebug() << "starting timer " << time << endl; +} + +void KHTMLView::complete( bool pendingAction ) +{ +// kdDebug() << "KHTMLView::complete()" << endl; + + d->complete = true; + + // is there a relayout pending? + if (d->layoutTimerId) + { +// kdDebug() << "requesting relayout now" << endl; + // 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) + { +// kdDebug() << "requesting repaint now" << endl; + // do it now + killTimer(d->repaintTimerId); + d->repaintTimerId = startTimer( 20 ); + d->emitCompletedAfterRepaint = pendingAction ? + KHTMLViewPrivate::CSActionPending : KHTMLViewPrivate::CSFull; + } + + if (!d->emitCompletedAfterRepaint) + { + if (!pendingAction) + emit m_part->completed(); + else + emit m_part->completed(true); + } + +} + +void KHTMLView::slotMouseScrollTimer() +{ + scrollBy( d->m_mouseScroll_byX, d->m_mouseScroll_byY ); +} + +#ifndef KHTML_NO_CARET + +// ### the dependencies on static functions are a nightmare. just be +// hacky and include the implementation here. Clean me up, please. + +#include "khtml_caret.cpp" + +void KHTMLView::initCaret(bool keepSelection) +{ +#if DEBUG_CARETMODE > 0 + kdDebug(6200) << "begin initCaret" << endl; +#endif + // save caretMoved state as moveCaretTo changes it + if (m_part->xmlDocImpl()) { +#if 0 + ElementImpl *listitem = m_part->xmlDocImpl()->getElementById("__test_element__"); + if (listitem) dumpLineBoxes(static_cast(listitem->renderer())); +#endif + d->caretViewContext(); + bool cmoved = d->m_caretViewContext->caretMoved; + if (m_part->d->caretNode().isNull()) { + // set to document, position will be sanitized anyway + m_part->d->caretNode() = m_part->document(); + m_part->d->caretOffset() = 0L; + // This sanity check is necessary for the not so unlikely case that + // setEditable or setCaretMode is called before any render objects have + // been created. + if (!m_part->d->caretNode().handle()->renderer()) return; + }/*end if*/ +// kdDebug(6200) << "d->m_selectionStart " << m_part->d->m_selectionStart.handle() +// << " d->m_selectionEnd " << m_part->d->m_selectionEnd.handle() << endl; + // ### does not repaint the selection on keepSelection!=false + moveCaretTo(m_part->d->caretNode().handle(), m_part->d->caretOffset(), !keepSelection); +// kdDebug(6200) << "d->m_selectionStart " << m_part->d->m_selectionStart.handle() +// << " d->m_selectionEnd " << m_part->d->m_selectionEnd.handle() << endl; + d->m_caretViewContext->caretMoved = cmoved; + }/*end if*/ +#if DEBUG_CARETMODE > 0 + kdDebug(6200) << "end initCaret" << endl; +#endif +} + +bool KHTMLView::caretOverrides() const +{ + bool cm = m_part->isCaretMode(); + bool dm = m_part->isEditable(); + return cm && !dm ? false + : (dm || m_part->d->caretNode().handle()->contentEditable()) + && d->editorContext()->override; +} + +void KHTMLView::ensureNodeHasFocus(NodeImpl *node) +{ + if (m_part->isCaretMode() || m_part->isEditable()) return; + if (node->focused()) return; + + // Find first ancestor whose "user-input" is "enabled" + NodeImpl *firstAncestor = 0; + while (node) { + if (node->renderer() + && node->renderer()->style()->userInput() != UI_ENABLED) + break; + firstAncestor = node; + node = node->parentNode(); + }/*wend*/ + + if (!node) firstAncestor = 0; + + DocumentImpl *doc = m_part->xmlDocImpl(); + // ensure that embedded widgets don't lose their focus + if (!firstAncestor && doc->focusNode() && doc->focusNode()->renderer() + && doc->focusNode()->renderer()->isWidget()) + return; + + // Set focus node on the document +#if DEBUG_CARETMODE > 1 + kdDebug(6200) << k_funcinfo << "firstAncestor " << firstAncestor << ": " + << (firstAncestor ? firstAncestor->nodeName().string() : QString::null) << endl; +#endif + doc->setFocusNode(firstAncestor); + emit m_part->nodeActivated(Node(firstAncestor)); +} + +void KHTMLView::recalcAndStoreCaretPos(CaretBox *hintBox) +{ + if (!m_part || m_part->d->caretNode().isNull()) return; + d->caretViewContext(); + NodeImpl *caretNode = m_part->d->caretNode().handle(); +#if DEBUG_CARETMODE > 0 + kdDebug(6200) << "recalcAndStoreCaretPos: caretNode=" << caretNode << (caretNode ? " "+caretNode->nodeName().string() : QString::null) << " r@" << caretNode->renderer() << (caretNode->renderer() && caretNode->renderer()->isText() ? " \"" + QConstString(static_cast(caretNode->renderer())->str->s, kMin(static_cast(caretNode->renderer())->str->l, 15u)).string() + "\"" : QString::null) << endl; +#endif + caretNode->getCaret(m_part->d->caretOffset(), caretOverrides(), + d->m_caretViewContext->x, d->m_caretViewContext->y, + d->m_caretViewContext->width, + d->m_caretViewContext->height); + + if (hintBox && d->m_caretViewContext->x == -1) { +#if DEBUG_CARETMODE > 1 + kdDebug(6200) << "using hint inline box coordinates" << endl; +#endif + RenderObject *r = caretNode->renderer(); + const QFontMetrics &fm = r->style()->fontMetrics(); + int absx, absy; + r->containingBlock()->absolutePosition(absx, absy, + false); // ### what about fixed? + d->m_caretViewContext->x = absx + hintBox->xPos(); + d->m_caretViewContext->y = absy + hintBox->yPos(); +// + hintBox->baseline() - fm.ascent(); + d->m_caretViewContext->width = 1; + // ### firstline not regarded. But I think it can be safely neglected + // as hint boxes are only used for empty lines. + d->m_caretViewContext->height = fm.height(); + }/*end if*/ + +#if DEBUG_CARETMODE > 4 +// kdDebug(6200) << "freqTimerId: "<m_caretViewContext->freqTimerId< 0 + kdDebug(6200) << "caret: ofs="<d->caretOffset()<<" " + <<" x="<m_caretViewContext->x<<" y="<m_caretViewContext->y + <<" h="<m_caretViewContext->height<m_caretViewContext) { + killTimer(d->m_caretViewContext->freqTimerId); + + if (hasFocus() || d->m_caretViewContext->displayNonFocused + == KHTMLPart::CaretBlink) { + d->m_caretViewContext->freqTimerId = startTimer(500); + } else { + d->m_caretViewContext->freqTimerId = -1; + }/*end if*/ + + d->m_caretViewContext->visible = true; + if ((d->m_caretViewContext->displayed = (hasFocus() + || d->m_caretViewContext->displayNonFocused + != KHTMLPart::CaretInvisible))) { + updateContents(d->m_caretViewContext->x, d->m_caretViewContext->y, + d->m_caretViewContext->width, + d->m_caretViewContext->height); + }/*end if*/ +// kdDebug(6200) << "caret on" << endl; + }/*end if*/ +} + +void KHTMLView::caretOff() +{ + if (d->m_caretViewContext) { + killTimer(d->m_caretViewContext->freqTimerId); + d->m_caretViewContext->freqTimerId = -1; + d->m_caretViewContext->displayed = false; + if (d->m_caretViewContext->visible) { + d->m_caretViewContext->visible = false; + updateContents(d->m_caretViewContext->x, d->m_caretViewContext->y, + d->m_caretViewContext->width, + d->m_caretViewContext->height); + }/*end if*/ +// kdDebug(6200) << "caret off" << endl; + }/*end if*/ +} + +void KHTMLView::showCaret(bool forceRepaint) +{ + if (d->m_caretViewContext) { + d->m_caretViewContext->displayed = true; + if (d->m_caretViewContext->visible) { + if (!forceRepaint) { + updateContents(d->m_caretViewContext->x, d->m_caretViewContext->y, + d->m_caretViewContext->width, + d->m_caretViewContext->height); + } else { + repaintContents(d->m_caretViewContext->x, d->m_caretViewContext->y, + d->m_caretViewContext->width, + d->m_caretViewContext->height); + }/*end if*/ + }/*end if*/ +// kdDebug(6200) << "caret shown" << endl; + }/*end if*/ +} + +bool KHTMLView::foldSelectionToCaret(NodeImpl *startNode, long startOffset, + NodeImpl *endNode, long endOffset) +{ + m_part->d->m_selectionStart = m_part->d->m_selectionEnd = m_part->d->caretNode(); + m_part->d->m_startOffset = m_part->d->m_endOffset = m_part->d->caretOffset(); + m_part->d->m_extendAtEnd = true; + + bool folded = startNode != endNode || startOffset != endOffset; + + // Only clear the selection if there has been one. + if (folded) { + m_part->xmlDocImpl()->clearSelection(); + }/*end if*/ + + return folded; +} + +void KHTMLView::hideCaret() +{ + if (d->m_caretViewContext) { + if (d->m_caretViewContext->visible) { +// kdDebug(6200) << "redraw caret hidden" << endl; + d->m_caretViewContext->visible = false; + // force repaint, otherwise the event won't be handled + // before the focus leaves the window + repaintContents(d->m_caretViewContext->x, d->m_caretViewContext->y, + d->m_caretViewContext->width, + d->m_caretViewContext->height); + d->m_caretViewContext->visible = true; + }/*end if*/ + d->m_caretViewContext->displayed = false; +// kdDebug(6200) << "caret hidden" << endl; + }/*end if*/ +} + +int KHTMLView::caretDisplayPolicyNonFocused() const +{ + if (d->m_caretViewContext) + return d->m_caretViewContext->displayNonFocused; + else + return KHTMLPart::CaretInvisible; +} + +void KHTMLView::setCaretDisplayPolicyNonFocused(int policy) +{ + d->caretViewContext(); +// int old = d->m_caretViewContext->displayNonFocused; + d->m_caretViewContext->displayNonFocused = (KHTMLPart::CaretDisplayPolicy)policy; + + // make change immediately take effect if not focused + if (!hasFocus()) { + switch (d->m_caretViewContext->displayNonFocused) { + case KHTMLPart::CaretInvisible: + hideCaret(); + break; + case KHTMLPart::CaretBlink: + if (d->m_caretViewContext->freqTimerId != -1) break; + d->m_caretViewContext->freqTimerId = startTimer(500); + // fall through + case KHTMLPart::CaretVisible: + d->m_caretViewContext->displayed = true; + showCaret(); + break; + }/*end switch*/ + }/*end if*/ +} + +bool KHTMLView::placeCaret(CaretBox *hintBox) +{ + CaretViewContext *cv = d->caretViewContext(); + caretOff(); + NodeImpl *caretNode = m_part->d->caretNode().handle(); + // ### why is it sometimes null? + if (!caretNode || !caretNode->renderer()) return false; + ensureNodeHasFocus(caretNode); + if (m_part->isCaretMode() || m_part->isEditable() + || caretNode->renderer()->style()->userInput() == UI_ENABLED) { + recalcAndStoreCaretPos(hintBox); + + cv->origX = cv->x; + + caretOn(); + return true; + }/*end if*/ + return false; +} + +void KHTMLView::ensureCaretVisible() +{ + CaretViewContext *cv = d->m_caretViewContext; + if (!cv) return; + ensureVisible(cv->x, cv->y, cv->width, cv->height); + d->scrollBarMoved = false; +} + +bool KHTMLView::extendSelection(NodeImpl *oldStartSel, long oldStartOfs, + NodeImpl *oldEndSel, long oldEndOfs) +{ + bool changed = false; + if (m_part->d->m_selectionStart == m_part->d->m_selectionEnd + && m_part->d->m_startOffset == m_part->d->m_endOffset) { + changed = foldSelectionToCaret(oldStartSel, oldStartOfs, oldEndSel, oldEndOfs); + m_part->d->m_extendAtEnd = true; + } else do { + changed = m_part->d->m_selectionStart.handle() != oldStartSel + || m_part->d->m_startOffset != oldStartOfs + || m_part->d->m_selectionEnd.handle() != oldEndSel + || m_part->d->m_endOffset != oldEndOfs; + if (!changed) break; + + // determine start position -- caret position is always at end. + NodeImpl *startNode; + long startOffset; + if (m_part->d->m_extendAtEnd) { + startNode = m_part->d->m_selectionStart.handle(); + startOffset = m_part->d->m_startOffset; + } else { + startNode = m_part->d->m_selectionEnd.handle(); + startOffset = m_part->d->m_endOffset; + m_part->d->m_selectionEnd = m_part->d->m_selectionStart; + m_part->d->m_endOffset = m_part->d->m_startOffset; + m_part->d->m_extendAtEnd = true; + }/*end if*/ + + bool swapNeeded = false; + if (!m_part->d->m_selectionEnd.isNull() && startNode) { + swapNeeded = RangeImpl::compareBoundaryPoints(startNode, startOffset, + m_part->d->m_selectionEnd.handle(), + m_part->d->m_endOffset) >= 0; + }/*end if*/ + + m_part->d->m_selectionStart = startNode; + m_part->d->m_startOffset = startOffset; + + if (swapNeeded) { + m_part->xmlDocImpl()->setSelection(m_part->d->m_selectionEnd.handle(), + m_part->d->m_endOffset, m_part->d->m_selectionStart.handle(), + m_part->d->m_startOffset); + } else { + m_part->xmlDocImpl()->setSelection(m_part->d->m_selectionStart.handle(), + m_part->d->m_startOffset, m_part->d->m_selectionEnd.handle(), + m_part->d->m_endOffset); + }/*end if*/ + } while(false);/*end if*/ + return changed; +} + +void KHTMLView::updateSelection(NodeImpl *oldStartSel, long oldStartOfs, + NodeImpl *oldEndSel, long oldEndOfs) +{ + if (m_part->d->m_selectionStart == m_part->d->m_selectionEnd + && m_part->d->m_startOffset == m_part->d->m_endOffset) { + if (foldSelectionToCaret(oldStartSel, oldStartOfs, oldEndSel, oldEndOfs)) { + m_part->emitSelectionChanged(); + }/*end if*/ + m_part->d->m_extendAtEnd = true; + } else { + // check if the extending end has passed the immobile end + if (!m_part->d->m_selectionEnd.isNull() && !m_part->d->m_selectionEnd.isNull()) { + bool swapNeeded = RangeImpl::compareBoundaryPoints( + m_part->d->m_selectionStart.handle(), m_part->d->m_startOffset, + m_part->d->m_selectionEnd.handle(), m_part->d->m_endOffset) >= 0; + if (swapNeeded) { + DOM::Node tmpNode = m_part->d->m_selectionStart; + long tmpOffset = m_part->d->m_startOffset; + m_part->d->m_selectionStart = m_part->d->m_selectionEnd; + m_part->d->m_startOffset = m_part->d->m_endOffset; + m_part->d->m_selectionEnd = tmpNode; + m_part->d->m_endOffset = tmpOffset; + m_part->d->m_startBeforeEnd = true; + m_part->d->m_extendAtEnd = !m_part->d->m_extendAtEnd; + }/*end if*/ + }/*end if*/ + + m_part->xmlDocImpl()->setSelection(m_part->d->m_selectionStart.handle(), + m_part->d->m_startOffset, m_part->d->m_selectionEnd.handle(), + m_part->d->m_endOffset); + m_part->emitSelectionChanged(); + }/*end if*/ +} + +void KHTMLView::caretKeyPressEvent(QKeyEvent *_ke) +{ + NodeImpl *oldStartSel = m_part->d->m_selectionStart.handle(); + long oldStartOfs = m_part->d->m_startOffset; + NodeImpl *oldEndSel = m_part->d->m_selectionEnd.handle(); + long oldEndOfs = m_part->d->m_endOffset; + + NodeImpl *oldCaretNode = m_part->d->caretNode().handle(); + long oldOffset = m_part->d->caretOffset(); + + bool ctrl = _ke->state() & ControlButton; + +// FIXME: this is that widely indented because I will write ifs around it. + switch(_ke->key()) { + case Key_Space: + break; + + case Key_Down: + moveCaretNextLine(1); + break; + + case Key_Up: + moveCaretPrevLine(1); + break; + + case Key_Left: + moveCaretBy(false, ctrl ? CaretByWord : CaretByCharacter, 1); + break; + + case Key_Right: + moveCaretBy(true, ctrl ? CaretByWord : CaretByCharacter, 1); + break; + + case Key_Next: + moveCaretNextPage(); + break; + + case Key_Prior: + moveCaretPrevPage(); + break; + + case Key_Home: + if (ctrl) + moveCaretToDocumentBoundary(false); + else + moveCaretToLineBegin(); + break; + + case Key_End: + if (ctrl) + moveCaretToDocumentBoundary(true); + else + moveCaretToLineEnd(); + break; + + }/*end switch*/ + + if ((m_part->d->caretNode().handle() != oldCaretNode + || m_part->d->caretOffset() != oldOffset) + // node should never be null, but faulty conditions may cause it to be + && !m_part->d->caretNode().isNull()) { + + d->m_caretViewContext->caretMoved = true; + + if (_ke->state() & ShiftButton) { // extend selection + updateSelection(oldStartSel, oldStartOfs, oldEndSel, oldEndOfs); + } else { // clear any selection + if (foldSelectionToCaret(oldStartSel, oldStartOfs, oldEndSel, oldEndOfs)) + m_part->emitSelectionChanged(); + }/*end if*/ + + m_part->emitCaretPositionChanged(m_part->d->caretNode(), m_part->d->caretOffset()); + }/*end if*/ + + _ke->accept(); +} + +bool KHTMLView::moveCaretTo(NodeImpl *node, long offset, bool clearSel) +{ + if (!node) return false; + ElementImpl *baseElem = determineBaseElement(node); + RenderFlow *base = static_cast(baseElem ? baseElem->renderer() : 0); + if (!node) return false; + + // need to find out the node's inline box. If there is none, this function + // will snap to the next node that has one. This is necessary to make the + // caret visible in any case. + CaretBoxLineDeleter cblDeleter; +// RenderBlock *cb; + long r_ofs; + CaretBoxIterator cbit; + CaretBoxLine *cbl = findCaretBoxLine(node, offset, &cblDeleter, base, r_ofs, cbit); + if(!cbl) { + kdWarning() << "KHTMLView::moveCaretTo - findCaretBoxLine() returns NULL" << endl; + return false; + } + +#if DEBUG_CARETMODE > 3 + if (cbl) kdDebug(6200) << cbl->information() << endl; +#endif + CaretBox *box = *cbit; + if (cbit != cbl->end() && box->object() != node->renderer()) { + if (box->object()->element()) { + mapRenderPosToDOMPos(box->object(), r_ofs, box->isOutside(), + box->isOutsideEnd(), node, offset); + //if (!outside) offset = node->minOffset(); +#if DEBUG_CARETMODE > 1 + kdDebug(6200) << "set new node " << node->nodeName().string() << "@" << node << endl; +#endif + } else { // box has no associated element -> do not use + // this case should actually never happen. + box = 0; + kdError(6200) << "Box contains no node! Crash imminent" << endl; + }/*end if*/ + } + + NodeImpl *oldStartSel = m_part->d->m_selectionStart.handle(); + long oldStartOfs = m_part->d->m_startOffset; + NodeImpl *oldEndSel = m_part->d->m_selectionEnd.handle(); + long oldEndOfs = m_part->d->m_endOffset; + + // test for position change + bool posChanged = m_part->d->caretNode().handle() != node + || m_part->d->caretOffset() != offset; + bool selChanged = false; + + m_part->d->caretNode() = node; + m_part->d->caretOffset() = offset; + if (clearSel || !oldStartSel || !oldEndSel) { + selChanged = foldSelectionToCaret(oldStartSel, oldStartOfs, oldEndSel, oldEndOfs); + } else { + //kdDebug(6200) << "moveToCaret: extendSelection: m_extendAtEnd " << m_part->d->m_extendAtEnd << endl; + //kdDebug(6200) << "selection: start(" << m_part->d->m_selectionStart.handle() << "," << m_part->d->m_startOffset << "), end(" << m_part->d->m_selectionEnd.handle() << "," << m_part->d->m_endOffset << "), caret(" << m_part->d->caretNode().handle() << "," << m_part->d->caretOffset() << ")" << endl; + selChanged = extendSelection(oldStartSel, oldStartOfs, oldEndSel, oldEndOfs); + //kdDebug(6200) << "after extendSelection: m_extendAtEnd " << m_part->d->m_extendAtEnd << endl; + //kdDebug(6200) << "selection: start(" << m_part->d->m_selectionStart.handle() << "," << m_part->d->m_startOffset << "), end(" << m_part->d->m_selectionEnd.handle() << "," << m_part->d->m_endOffset << "), caret(" << m_part->d->caretNode().handle() << "," << m_part->d->caretOffset() << ")" << endl; + }/*end if*/ + + d->caretViewContext()->caretMoved = true; + + bool visible_caret = placeCaret(box); + + // FIXME: if the old position was !visible_caret, and the new position is + // also, then two caretPositionChanged signals with a null Node are + // emitted in series. + if (posChanged) { + m_part->emitCaretPositionChanged(visible_caret ? node : 0, offset); + }/*end if*/ + + return selChanged; +} + +void KHTMLView::moveCaretByLine(bool next, int count) +{ + Node &caretNodeRef = m_part->d->caretNode(); + if (caretNodeRef.isNull()) return; + + NodeImpl *caretNode = caretNodeRef.handle(); +// kdDebug(6200) << ": caretNode=" << caretNode << endl; + long offset = m_part->d->caretOffset(); + + CaretViewContext *cv = d->caretViewContext(); + + ElementImpl *baseElem = determineBaseElement(caretNode); + LinearDocument ld(m_part, caretNode, offset, LeafsOnly, baseElem); + + ErgonomicEditableLineIterator it(ld.current(), cv->origX); + + // move count lines vertically + while (count > 0 && it != ld.end() && it != ld.preBegin()) { + count--; + if (next) ++it; else --it; + }/*wend*/ + + // Nothing? Then leave everything as is. + if (it == ld.end() || it == ld.preBegin()) return; + + int x, absx, absy; + CaretBox *caretBox = nearestCaretBox(it, d->m_caretViewContext, x, absx, absy); + + placeCaretOnLine(caretBox, x, absx, absy); +} + +void KHTMLView::placeCaretOnLine(CaretBox *caretBox, int x, int absx, int absy) +{ + // paranoia sanity check + if (!caretBox) return; + + RenderObject *caretRender = caretBox->object(); + +#if DEBUG_CARETMODE > 0 + kdDebug(6200) << "got valid caretBox " << caretBox << endl; + kdDebug(6200) << "xPos: " << caretBox->xPos() << " yPos: " << caretBox->yPos() + << " width: " << caretBox->width() << " height: " << caretBox->height() << endl; + InlineTextBox *tb = static_cast(caretBox->inlineBox()); + if (caretBox->isInlineTextBox()) { kdDebug(6200) << "contains \"" << QString(static_cast(tb->object())->str->s + tb->m_start, tb->m_len) << "\"" << endl;} +#endif + // inquire height of caret + int caretHeight = caretBox->height(); + bool isText = caretBox->isInlineTextBox(); + int yOfs = 0; // y-offset for text nodes + if (isText) { + // text boxes need extrawurst + RenderText *t = static_cast(caretRender); + const QFontMetrics &fm = t->metrics(caretBox->inlineBox()->m_firstLine); + caretHeight = fm.height(); + yOfs = caretBox->inlineBox()->baseline() - fm.ascent(); + }/*end if*/ + + caretOff(); + + // set new caret node + NodeImpl *caretNode; + long &offset = m_part->d->caretOffset(); + mapRenderPosToDOMPos(caretRender, offset, caretBox->isOutside(), + caretBox->isOutsideEnd(), caretNode, offset); + + // set all variables not needing special treatment + d->m_caretViewContext->y = caretBox->yPos() + yOfs; + d->m_caretViewContext->height = caretHeight; + d->m_caretViewContext->width = 1; // FIXME: regard override + + int xPos = caretBox->xPos(); + int caretBoxWidth = caretBox->width(); + d->m_caretViewContext->x = xPos; + + if (!caretBox->isOutside()) { + // before or at beginning of inline box -> place at beginning + long r_ofs = 0; + if (x <= xPos) { + r_ofs = caretBox->minOffset(); + // somewhere within this block + } else if (x > xPos && x <= xPos + caretBoxWidth) { + if (isText) { // find out where exactly + r_ofs = static_cast(caretBox->inlineBox()) + ->offsetForPoint(x, d->m_caretViewContext->x); +#if DEBUG_CARETMODE > 2 + kdDebug(6200) << "deviation from origX " << d->m_caretViewContext->x - x << endl; +#endif +#if 0 + } else { // snap to nearest end + if (xPos + caretBoxWidth - x < x - xPos) { + d->m_caretViewContext->x = xPos + caretBoxWidth; + r_ofs = caretNode ? caretNode->maxOffset() : 1; + } else { + d->m_caretViewContext->x = xPos; + r_ofs = caretNode ? caretNode->minOffset() : 0; + }/*end if*/ +#endif + }/*end if*/ + } else { // after the inline box -> place at end + d->m_caretViewContext->x = xPos + caretBoxWidth; + r_ofs = caretBox->maxOffset(); + }/*end if*/ + offset = r_ofs; + }/*end if*/ +#if DEBUG_CARETMODE > 0 + kdDebug(6200) << "new offset: " << offset << endl; +#endif + + m_part->d->caretNode() = caretNode; + m_part->d->caretOffset() = offset; + + d->m_caretViewContext->x += absx; + d->m_caretViewContext->y += absy; + +#if DEBUG_CARETMODE > 1 + kdDebug(6200) << "new caret position: x " << d->m_caretViewContext->x << " y " << d->m_caretViewContext->y << " w " << d->m_caretViewContext->width << " h " << d->m_caretViewContext->height << " absx " << absx << " absy " << absy << endl; +#endif + + ensureVisible(d->m_caretViewContext->x, d->m_caretViewContext->y, + d->m_caretViewContext->width, d->m_caretViewContext->height); + d->scrollBarMoved = false; + + ensureNodeHasFocus(caretNode); + caretOn(); +} + +void KHTMLView::moveCaretToLineBoundary(bool end) +{ + Node &caretNodeRef = m_part->d->caretNode(); + if (caretNodeRef.isNull()) return; + + NodeImpl *caretNode = caretNodeRef.handle(); +// kdDebug(6200) << ": caretNode=" << caretNode << endl; + long offset = m_part->d->caretOffset(); + + ElementImpl *baseElem = determineBaseElement(caretNode); + LinearDocument ld(m_part, caretNode, offset, LeafsOnly, baseElem); + + EditableLineIterator it = ld.current(); + if (it == ld.end()) return; // should not happen, but who knows + + EditableCaretBoxIterator fbit(it, end); + Q_ASSERT(fbit != (*it)->end() && fbit != (*it)->preBegin()); + CaretBox *b = *fbit; + + RenderObject *cb = b->containingBlock(); + int absx, absy; + + if (cb) cb->absolutePosition(absx,absy); + else absx = absy = 0; + + int x = b->xPos() + (end && !b->isOutside() ? b->width() : 0); + d->m_caretViewContext->origX = absx + x; + placeCaretOnLine(b, x, absx, absy); +} + +void KHTMLView::moveCaretToDocumentBoundary(bool end) +{ + Node &caretNodeRef = m_part->d->caretNode(); + if (caretNodeRef.isNull()) return; + + NodeImpl *caretNode = caretNodeRef.handle(); +// kdDebug(6200) << ": caretNode=" << caretNode << endl; + long offset = m_part->d->caretOffset(); + + ElementImpl *baseElem = determineBaseElement(caretNode); + LinearDocument ld(m_part, caretNode, offset, IndicatedFlows, baseElem); + + EditableLineIterator it(end ? ld.preEnd() : ld.begin(), end); + if (it == ld.end() || it == ld.preBegin()) return; // should not happen, but who knows + + EditableCaretBoxIterator fbit = it; + Q_ASSERT(fbit != (*it)->end() && fbit != (*it)->preBegin()); + CaretBox *b = *fbit; + + RenderObject *cb = (*it)->containingBlock(); + int absx, absy; + + if (cb) cb->absolutePosition(absx, absy); + else absx = absy = 0; + + int x = b->xPos()/* + (end ? b->width() : 0) reactivate for rtl*/; + d->m_caretViewContext->origX = absx + x; + placeCaretOnLine(b, x, absx, absy); +} + +void KHTMLView::moveCaretBy(bool next, CaretMovement cmv, int count) +{ + if (!m_part) return; + Node &caretNodeRef = m_part->d->caretNode(); + if (caretNodeRef.isNull()) return; + + NodeImpl *caretNode = caretNodeRef.handle(); +// kdDebug(6200) << ": caretNode=" << caretNode << endl; + long &offset = m_part->d->caretOffset(); + + ElementImpl *baseElem = determineBaseElement(caretNode); + CaretAdvancePolicy advpol = cmv != CaretByWord ? IndicatedFlows : LeafsOnly; + LinearDocument ld(m_part, caretNode, offset, advpol, baseElem); + + EditableCharacterIterator it(&ld); + while (!it.isEnd() && count > 0) { + count--; + if (cmv == CaretByCharacter) { + if (next) ++it; + else --it; + } else if (cmv == CaretByWord) { + if (next) moveItToNextWord(it); + else moveItToPrevWord(it); + }/*end if*/ +//kdDebug(6200) << "movecaret" << endl; + }/*wend*/ + CaretBox *hintBox = 0; // make gcc uninit warning disappear + if (!it.isEnd()) { + NodeImpl *node = caretNodeRef.handle(); + hintBox = it.caretBox(); +//kdDebug(6200) << "hintBox = " << hintBox << endl; +//kdDebug(6200) << " outside " << hintBox->isOutside() << " outsideEnd " << hintBox->isOutsideEnd() << " r " << it.renderer() << " ofs " << it.offset() << " cb " << hintBox->containingBlock() << endl; + mapRenderPosToDOMPos(it.renderer(), it.offset(), hintBox->isOutside(), + hintBox->isOutsideEnd(), node, offset); +//kdDebug(6200) << "mapRTD" << endl; + caretNodeRef = node; +#if DEBUG_CARETMODE > 2 + kdDebug(6200) << "set by valid node " << node << " " << (node?node->nodeName().string():QString::null) << " offset: " << offset << endl; +#endif + } else { + offset = next ? caretNode->maxOffset() : caretNode->minOffset(); +#if DEBUG_CARETMODE > 0 + kdDebug(6200) << "set by INvalid node. offset: " << offset << endl; +#endif + }/*end if*/ + placeCaretOnChar(hintBox); +} + +void KHTMLView::placeCaretOnChar(CaretBox *hintBox) +{ + caretOff(); + recalcAndStoreCaretPos(hintBox); + ensureVisible(d->m_caretViewContext->x, d->m_caretViewContext->y, + d->m_caretViewContext->width, d->m_caretViewContext->height); + d->m_caretViewContext->origX = d->m_caretViewContext->x; + d->scrollBarMoved = false; +#if DEBUG_CARETMODE > 3 + //if (caretNode->isTextNode()) kdDebug(6200) << "text[0] = " << (int)*((TextImpl *)caretNode)->data().unicode() << " text :\"" << ((TextImpl *)caretNode)->data().string() << "\"" << endl; +#endif + ensureNodeHasFocus(m_part->d->caretNode().handle()); + caretOn(); +} + +void KHTMLView::moveCaretByPage(bool next) +{ + Node &caretNodeRef = m_part->d->caretNode(); + if (caretNodeRef.isNull()) return; + + NodeImpl *caretNode = caretNodeRef.handle(); +// kdDebug(6200) << ": caretNode=" << caretNode << endl; + long offset = m_part->d->caretOffset(); + + int offs = (clipper()->height() < 30) ? clipper()->height() : 30; + // Minimum distance the caret must be moved + int mindist = clipper()->height() - offs; + + CaretViewContext *cv = d->caretViewContext(); +// int y = cv->y; // we always measure the top border + + ElementImpl *baseElem = determineBaseElement(caretNode); + LinearDocument ld(m_part, caretNode, offset, LeafsOnly, baseElem); + + ErgonomicEditableLineIterator it(ld.current(), cv->origX); + + moveIteratorByPage(ld, it, mindist, next); + + int x, absx, absy; + CaretBox *caretBox = nearestCaretBox(it, d->m_caretViewContext, x, absx, absy); + + placeCaretOnLine(caretBox, x, absx, absy); +} + +void KHTMLView::moveCaretPrevWord() +{ + moveCaretBy(false, CaretByWord, 1); +} + +void KHTMLView::moveCaretNextWord() +{ + moveCaretBy(true, CaretByWord, 1); +} + +void KHTMLView::moveCaretPrevLine(int n) +{ + moveCaretByLine(false, n); +} + +void KHTMLView::moveCaretNextLine(int n) +{ + moveCaretByLine(true, n); +} + +void KHTMLView::moveCaretPrevPage() +{ + moveCaretByPage(false); +} + +void KHTMLView::moveCaretNextPage() +{ + moveCaretByPage(true); +} + +void KHTMLView::moveCaretToLineBegin() +{ + moveCaretToLineBoundary(false); +} + +void KHTMLView::moveCaretToLineEnd() +{ + moveCaretToLineBoundary(true); +} + +#endif // KHTML_NO_CARET + +#undef DEBUG_CARETMODE -- cgit v1.2.1