1 | /* This file is part of the KDE project |
2 | * |
3 | * Copyright (C) 1998, 1999 Torben Weis <weis@kde.org> |
4 | * 1999 Lars Knoll <knoll@kde.org> |
5 | * 1999 Antti Koivisto <koivisto@kde.org> |
6 | * 2000-2004 Dirk Mueller <mueller@kde.org> |
7 | * 2003 Leo Savernik <l.savernik@aon.at> |
8 | * 2003-2008 Apple Computer, Inc. |
9 | * 2008 Allan Sandfeld Jensen <kde@carewolf.com> |
10 | * 2006-2008 Germain Garand <germain@ebooksfrance.org> |
11 | * |
12 | * This library is free software; you can redistribute it and/or |
13 | * modify it under the terms of the GNU Library General Public |
14 | * License as published by the Free Software Foundation; either |
15 | * version 2 of the License, or (at your option) any later version. |
16 | * |
17 | * This library is distributed in the hope that it will be useful, |
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
20 | * Library General Public License for more details. |
21 | * |
22 | * You should have received a copy of the GNU Library General Public License |
23 | * along with this library; see the file COPYING.LIB. If not, write to |
24 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
25 | * Boston, MA 02110-1301, USA. |
26 | */ |
27 | |
28 | |
29 | #include "khtmlview.h" |
30 | |
31 | #include "khtmlview.moc" |
32 | |
33 | #include "khtml_part.h" |
34 | #include "khtml_events.h" |
35 | #ifdef Q_WS_X11 |
36 | #include <qx11info_x11.h> |
37 | #endif |
38 | |
39 | #include "html/html_documentimpl.h" |
40 | #include "html/html_inlineimpl.h" |
41 | #include "html/html_formimpl.h" |
42 | #include "html/htmltokenizer.h" |
43 | #include "editing/editor.h" |
44 | #include "rendering/render_arena.h" |
45 | #include "rendering/render_canvas.h" |
46 | #include "rendering/render_frames.h" |
47 | #include "rendering/render_replaced.h" |
48 | #include "rendering/render_form.h" |
49 | #include "rendering/render_layer.h" |
50 | #include "rendering/render_line.h" |
51 | #include "rendering/render_table.h" |
52 | // removeme |
53 | #define protected public |
54 | #include "rendering/render_text.h" |
55 | #undef protected |
56 | #include "xml/dom2_eventsimpl.h" |
57 | #include "css/cssstyleselector.h" |
58 | #include "css/csshelper.h" |
59 | #include "misc/helper.h" |
60 | #include "misc/loader.h" |
61 | #include "khtml_settings.h" |
62 | #include "khtml_printsettings.h" |
63 | |
64 | #include "khtmlpart_p.h" |
65 | |
66 | #include <kcursor.h> |
67 | #include <kdebug.h> |
68 | #include <kglobalsettings.h> |
69 | #include <kdialog.h> |
70 | #include <kiconloader.h> |
71 | #include <klocale.h> |
72 | #include <knotification.h> |
73 | #include <kdeprintdialog.h> |
74 | #include <kconfig.h> |
75 | #include <kstandarddirs.h> |
76 | #include <kstandardshortcut.h> |
77 | #include <kstringhandler.h> |
78 | #include <kconfiggroup.h> |
79 | |
80 | #include <QtGui/QBitmap> |
81 | #include <QtGui/QLabel> |
82 | #include <QtCore/QObject> |
83 | #include <QtGui/QPainter> |
84 | #include <QtCore/QHash> |
85 | #include <QtGui/QToolTip> |
86 | #include <QtCore/QString> |
87 | #include <QtGui/QTextDocument> |
88 | #include <QtCore/QTimer> |
89 | #include <QtCore/QAbstractEventDispatcher> |
90 | #include <QtCore/QVector> |
91 | #include <QtGui/QAbstractScrollArea> |
92 | #include <QtGui/QPrinter> |
93 | #include <QtGui/QPrintDialog> |
94 | |
95 | //#define DEBUG_FLICKER |
96 | |
97 | #include <limits.h> |
98 | #ifdef Q_WS_X11 |
99 | #include <X11/Xlib.h> |
100 | #include <fixx11h.h> |
101 | #elif defined(Q_WS_WIN) |
102 | #include <windows.h> |
103 | #endif |
104 | |
105 | #if 0 |
106 | namespace khtml { |
107 | void dumpLineBoxes(RenderFlow *flow); |
108 | } |
109 | #endif |
110 | |
111 | using namespace DOM; |
112 | using namespace khtml; |
113 | |
114 | #ifndef NDEBUG |
115 | static const int sFirstLayoutDelay = 520; |
116 | static const int sParsingLayoutsInterval = 380; |
117 | static const int sLayoutAttemptDelay = 300; |
118 | #else |
119 | static const int sFirstLayoutDelay = 280; |
120 | static const int sParsingLayoutsInterval = 320; |
121 | static const int sLayoutAttemptDelay = 200; |
122 | #endif |
123 | static const int sLayoutAttemptIncrement = 20; |
124 | static const int sParsingLayoutsIncrement = 60; |
125 | |
126 | static const int sSmoothScrollTime = 128; |
127 | static const int sSmoothScrollTick = 16; |
128 | static const int sSmoothScrollMinStaticPixels = 320*200; |
129 | |
130 | static const int sMaxMissedDeadlines = 12; |
131 | static const int sWayTooMany = -1; |
132 | |
133 | class KHTMLViewPrivate { |
134 | friend class KHTMLView; |
135 | public: |
136 | |
137 | enum PseudoFocusNodes { |
138 | PFNone, |
139 | PFTop, |
140 | PFBottom |
141 | }; |
142 | |
143 | enum StaticBackgroundState { |
144 | SBNone = 0, |
145 | SBPartial, |
146 | SBFull |
147 | }; |
148 | |
149 | enum CompletedState { |
150 | CSNone = 0, |
151 | CSFull, |
152 | CSActionPending |
153 | }; |
154 | |
155 | KHTMLViewPrivate(KHTMLView* v) |
156 | : underMouse( 0 ), underMouseNonShared( 0 ), oldUnderMouse( 0 ) |
157 | { |
158 | postponed_autorepeat = NULL; |
159 | scrollingFromWheelTimerId = 0; |
160 | smoothScrollMode = KHTMLView::SSMWhenEfficient; |
161 | |
162 | reset(); |
163 | vpolicy = Qt::ScrollBarAsNeeded; |
164 | hpolicy = Qt::ScrollBarAsNeeded; |
165 | formCompletions=0; |
166 | prevScrollbarVisible = true; |
167 | |
168 | possibleTripleClick = false; |
169 | emitCompletedAfterRepaint = CSNone; |
170 | cursorIconWidget = 0; |
171 | cursorIconType = KHTMLView::LINK_NORMAL; |
172 | m_mouseScrollTimer = 0; |
173 | m_mouseScrollIndicator = 0; |
174 | contentsX = 0; |
175 | contentsY = 0; |
176 | view = v; |
177 | } |
178 | ~KHTMLViewPrivate() |
179 | { |
180 | delete formCompletions; |
181 | delete postponed_autorepeat; |
182 | if (underMouse) |
183 | underMouse->deref(); |
184 | if (underMouseNonShared) |
185 | underMouseNonShared->deref(); |
186 | if (oldUnderMouse) |
187 | oldUnderMouse->deref(); |
188 | |
189 | delete cursorIconWidget; |
190 | delete m_mouseScrollTimer; |
191 | delete m_mouseScrollIndicator; |
192 | } |
193 | void reset() |
194 | { |
195 | if (underMouse) |
196 | underMouse->deref(); |
197 | underMouse = 0; |
198 | if (underMouseNonShared) |
199 | underMouseNonShared->deref(); |
200 | underMouseNonShared = 0; |
201 | if (oldUnderMouse) |
202 | oldUnderMouse->deref(); |
203 | oldUnderMouse = 0; |
204 | linkPressed = false; |
205 | staticWidget = SBNone; |
206 | fixedObjectsCount = 0; |
207 | staticObjectsCount = 0; |
208 | tabMovePending = false; |
209 | lastTabbingDirection = true; |
210 | pseudoFocusNode = PFNone; |
211 | zoomLevel = 100; |
212 | #ifndef KHTML_NO_SCROLLBARS |
213 | //We don't turn off the toolbars here |
214 | //since if the user turns them |
215 | //off, then chances are they want them turned |
216 | //off always - even after a reset. |
217 | #else |
218 | vpolicy = ScrollBarAlwaysOff; |
219 | hpolicy = ScrollBarAlwaysOff; |
220 | #endif |
221 | scrollBarMoved = false; |
222 | contentsMoving = false; |
223 | ignoreWheelEvents = false; |
224 | scrollingFromWheel = QPoint(-1,-1); |
225 | borderX = 30; |
226 | borderY = 30; |
227 | steps = 0; |
228 | dx = dy = 0; |
229 | paged = false; |
230 | clickX = -1; |
231 | clickY = -1; |
232 | clickCount = 0; |
233 | isDoubleClick = false; |
234 | scrollingSelf = false; |
235 | delete postponed_autorepeat; |
236 | postponed_autorepeat = NULL; |
237 | layoutTimerId = 0; |
238 | repaintTimerId = 0; |
239 | scrollTimerId = 0; |
240 | scrollSuspended = false; |
241 | scrollSuspendPreActivate = false; |
242 | smoothScrolling = false; |
243 | smoothScrollModeIsDefault = true; |
244 | shouldSmoothScroll = false; |
245 | smoothScrollMissedDeadlines = 0; |
246 | hasFrameset = false; |
247 | complete = false; |
248 | firstLayoutPending = true; |
249 | #ifdef SPEED_DEBUG |
250 | firstRepaintPending = true; |
251 | #endif |
252 | needsFullRepaint = true; |
253 | dirtyLayout = false; |
254 | layoutSchedulingEnabled = true; |
255 | painting = false; |
256 | layoutCounter = 0; |
257 | layoutAttemptCounter = 0; |
258 | scheduledLayoutCounter = 0; |
259 | updateRegion = QRegion(); |
260 | m_dialogsAllowed = true; |
261 | accessKeysActivated = false; |
262 | accessKeysPreActivate = false; |
263 | |
264 | // the view might have been built before the part it will be assigned to, |
265 | // so exceptionally, we need to directly ref/deref KHTMLGlobal to |
266 | // account for this transitory case. |
267 | KHTMLGlobal::ref(); |
268 | accessKeysEnabled = KHTMLGlobal::defaultHTMLSettings()->accessKeysEnabled(); |
269 | KHTMLGlobal::deref(); |
270 | |
271 | emitCompletedAfterRepaint = CSNone; |
272 | m_mouseEventsTarget = 0; |
273 | m_clipHolder = 0; |
274 | } |
275 | void newScrollTimer(QWidget *view, int tid) |
276 | { |
277 | //kDebug(6000) << "newScrollTimer timer " << tid; |
278 | view->killTimer(scrollTimerId); |
279 | scrollTimerId = tid; |
280 | scrollSuspended = false; |
281 | } |
282 | enum ScrollDirection { ScrollLeft, ScrollRight, ScrollUp, ScrollDown }; |
283 | |
284 | void adjustScroller(QWidget *view, ScrollDirection direction, ScrollDirection oppositedir) |
285 | { |
286 | static const struct { int msec, pixels; } timings [] = { |
287 | {320,1}, {224,1}, {160,1}, {112,1}, {80,1}, {56,1}, {40,1}, |
288 | {28,1}, {20,1}, {20,2}, {20,3}, {20,4}, {20,6}, {20,8}, {0,0} |
289 | }; |
290 | if (!scrollTimerId || |
291 | (static_cast<int>(scrollDirection) != direction && |
292 | (static_cast<int>(scrollDirection) != oppositedir || scrollSuspended))) { |
293 | scrollTiming = 6; |
294 | scrollBy = timings[scrollTiming].pixels; |
295 | scrollDirection = direction; |
296 | newScrollTimer(view, view->startTimer(timings[scrollTiming].msec)); |
297 | } else if (scrollDirection == direction && |
298 | timings[scrollTiming+1].msec && !scrollSuspended) { |
299 | scrollBy = timings[++scrollTiming].pixels; |
300 | newScrollTimer(view, view->startTimer(timings[scrollTiming].msec)); |
301 | } else if (scrollDirection == oppositedir) { |
302 | if (scrollTiming) { |
303 | scrollBy = timings[--scrollTiming].pixels; |
304 | newScrollTimer(view, view->startTimer(timings[scrollTiming].msec)); |
305 | } |
306 | } |
307 | scrollSuspended = false; |
308 | } |
309 | |
310 | bool haveZoom() const { return zoomLevel != 100; } |
311 | |
312 | void startScrolling() |
313 | { |
314 | smoothScrolling = true; |
315 | smoothScrollTimer.start(sSmoothScrollTick); |
316 | shouldSmoothScroll = false; |
317 | } |
318 | |
319 | void stopScrolling() |
320 | { |
321 | smoothScrollTimer.stop(); |
322 | dx = dy = 0; |
323 | steps = 0; |
324 | updateContentsXY(); |
325 | smoothScrolling = false; |
326 | shouldSmoothScroll = false; |
327 | } |
328 | |
329 | void updateContentsXY() |
330 | { |
331 | contentsX = QApplication::isRightToLeft() ? |
332 | view->horizontalScrollBar()->maximum()-view->horizontalScrollBar()->value() : view->horizontalScrollBar()->value(); |
333 | contentsY = view->verticalScrollBar()->value(); |
334 | } |
335 | void scrollAccessKeys(int dx, int dy) |
336 | { |
337 | QList<QLabel*> wl = qFindChildren<QLabel*>(view->widget(), "KHTMLAccessKey" ); |
338 | foreach(QLabel* w, wl) { |
339 | w->move( w->pos() + QPoint(dx, dy) ); |
340 | } |
341 | } |
342 | void scrollExternalWidgets(int dx, int dy) |
343 | { |
344 | if (visibleWidgets.isEmpty()) |
345 | return; |
346 | |
347 | QHashIterator<void*, QWidget*> it(visibleWidgets); |
348 | while (it.hasNext()) { |
349 | it.next(); |
350 | it.value()->move( it.value()->pos() + QPoint(dx, dy) ); |
351 | } |
352 | } |
353 | |
354 | NodeImpl *underMouse; |
355 | NodeImpl *underMouseNonShared; |
356 | NodeImpl *oldUnderMouse; |
357 | |
358 | // Do not adjust bitfield enums sizes. |
359 | // They are oversized because they are signed on some platforms. |
360 | bool tabMovePending:1; |
361 | bool lastTabbingDirection:1; |
362 | PseudoFocusNodes pseudoFocusNode:3; |
363 | bool scrollBarMoved:1; |
364 | bool contentsMoving:1; |
365 | |
366 | Qt::ScrollBarPolicy vpolicy; |
367 | Qt::ScrollBarPolicy hpolicy; |
368 | bool prevScrollbarVisible:1; |
369 | bool linkPressed:1; |
370 | bool ignoreWheelEvents:1; |
371 | StaticBackgroundState staticWidget: 3; |
372 | int staticObjectsCount; |
373 | int fixedObjectsCount; |
374 | |
375 | int zoomLevel; |
376 | int borderX, borderY; |
377 | int dx, dy; |
378 | int steps; |
379 | KConfig *formCompletions; |
380 | |
381 | int clickX, clickY, clickCount; |
382 | bool isDoubleClick; |
383 | |
384 | bool paged; |
385 | |
386 | bool scrollingSelf; |
387 | int contentsX, contentsY; |
388 | int layoutTimerId; |
389 | QKeyEvent* postponed_autorepeat; |
390 | |
391 | int repaintTimerId; |
392 | int scrollTimerId; |
393 | int scrollTiming; |
394 | int scrollBy; |
395 | ScrollDirection scrollDirection :3; |
396 | bool scrollSuspended :1; |
397 | bool scrollSuspendPreActivate :1; |
398 | KHTMLView::SmoothScrollingMode smoothScrollMode :3; |
399 | bool smoothScrolling :1; |
400 | bool smoothScrollModeIsDefault :1; |
401 | bool shouldSmoothScroll :1; |
402 | bool hasFrameset :1; |
403 | bool complete :1; |
404 | bool firstLayoutPending :1; |
405 | #ifdef SPEED_DEBUG |
406 | bool firstRepaintPending :1; |
407 | #endif |
408 | bool layoutSchedulingEnabled :1; |
409 | bool needsFullRepaint :1; |
410 | bool painting :1; |
411 | bool possibleTripleClick :1; |
412 | bool dirtyLayout :1; |
413 | bool m_dialogsAllowed :1; |
414 | short smoothScrollMissedDeadlines; |
415 | int layoutCounter; |
416 | int layoutAttemptCounter; |
417 | int scheduledLayoutCounter; |
418 | QRegion updateRegion; |
419 | QTimer smoothScrollTimer; |
420 | QTime smoothScrollStopwatch; |
421 | QHash<void*, QWidget*> visibleWidgets; |
422 | bool accessKeysEnabled; |
423 | bool accessKeysActivated; |
424 | bool accessKeysPreActivate; |
425 | CompletedState emitCompletedAfterRepaint; |
426 | |
427 | QLabel* cursorIconWidget; |
428 | KHTMLView::LinkCursor cursorIconType; |
429 | |
430 | // scrolling activated by MMB |
431 | short m_mouseScroll_byX; |
432 | short m_mouseScroll_byY; |
433 | QPoint scrollingFromWheel; |
434 | int scrollingFromWheelTimerId; |
435 | QTimer *m_mouseScrollTimer; |
436 | QWidget *m_mouseScrollIndicator; |
437 | QPointer<QWidget> m_mouseEventsTarget; |
438 | QStack<QRegion>* m_clipHolder; |
439 | KHTMLView* view; |
440 | }; |
441 | |
442 | #ifndef QT_NO_TOOLTIP |
443 | |
444 | /** calculates the client-side image map rectangle for the given image element |
445 | * @param img image element |
446 | * @param scrollOfs scroll offset of viewport in content coordinates |
447 | * @param p position to be probed in viewport coordinates |
448 | * @param r returns the bounding rectangle in content coordinates |
449 | * @param s returns the title string |
450 | * @return true if an appropriate area was found -- only in this case r and |
451 | * s are valid, false otherwise |
452 | */ |
453 | static bool findImageMapRect(HTMLImageElementImpl *img, const QPoint &scrollOfs, |
454 | const QPoint &p, QRect &r, QString &s) |
455 | { |
456 | HTMLMapElementImpl* map; |
457 | if (img && img->document()->isHTMLDocument() && |
458 | (map = static_cast<HTMLDocumentImpl*>(img->document())->getMap(img->imageMap()))) { |
459 | RenderObject::NodeInfo info(true, false); |
460 | RenderObject *rend = img->renderer(); |
461 | int ax, ay; |
462 | if (!rend || !rend->absolutePosition(ax, ay)) |
463 | return false; |
464 | // we're a client side image map |
465 | bool inside = map->mapMouseEvent(p.x() - ax + scrollOfs.x(), |
466 | p.y() - ay + scrollOfs.y(), rend->contentWidth(), |
467 | rend->contentHeight(), info); |
468 | if (inside && info.URLElement()) { |
469 | HTMLAreaElementImpl *area = static_cast<HTMLAreaElementImpl *>(info.URLElement()); |
470 | Q_ASSERT(area->id() == ID_AREA); |
471 | s = area->getAttribute(ATTR_TITLE).string(); |
472 | QRegion reg = area->cachedRegion(); |
473 | if (!s.isEmpty() && !reg.isEmpty()) { |
474 | r = reg.boundingRect(); |
475 | r.translate(ax, ay); |
476 | return true; |
477 | } |
478 | } |
479 | } |
480 | return false; |
481 | } |
482 | |
483 | bool KHTMLView::event( QEvent* e ) |
484 | { |
485 | switch ( e->type() ) { |
486 | case QEvent::ToolTip: { |
487 | QHelpEvent *he = static_cast<QHelpEvent*>(e); |
488 | QPoint p = he->pos(); |
489 | |
490 | DOM::NodeImpl *node = d->underMouseNonShared; |
491 | QRect region; |
492 | while ( node ) { |
493 | if ( node->isElementNode() ) { |
494 | DOM::ElementImpl *e = static_cast<DOM::ElementImpl*>( node ); |
495 | QRect r; |
496 | QString s; |
497 | bool found = false; |
498 | // for images, check if it is part of a client-side image map, |
499 | // and query the <area>s' title attributes, too |
500 | if (e->id() == ID_IMG && !e->getAttribute( ATTR_USEMAP ).isEmpty()) { |
501 | found = findImageMapRect(static_cast<HTMLImageElementImpl *>(e), |
502 | viewportToContents(QPoint(0, 0)), p, r, s); |
503 | } |
504 | if (!found) { |
505 | s = e->getAttribute( ATTR_TITLE ).string(); |
506 | r = node->getRect(); |
507 | } |
508 | region |= QRect( contentsToViewport( r.topLeft() ), r.size() ); |
509 | if ( !s.isEmpty() ) { |
510 | QToolTip::showText( he->globalPos(), |
511 | Qt::convertFromPlainText( s, Qt::WhiteSpaceNormal ), |
512 | widget(), region ); |
513 | break; |
514 | } |
515 | } |
516 | node = node->parentNode(); |
517 | } |
518 | // Qt makes tooltip events happen nearly immediately when a preceding one was processed in the past few seconds. |
519 | // We don't want that feature to apply to web tootlips however, as it clashes with dhtml menus. |
520 | // So we'll just pretend we did not process that event. |
521 | return false; |
522 | } |
523 | |
524 | case QEvent::DragEnter: |
525 | case QEvent::DragMove: |
526 | case QEvent::DragLeave: |
527 | case QEvent::Drop: |
528 | // In Qt4, one needs to both call accept() on the DND event and return |
529 | // true on ::event for the candidate widget for the drop to be possible. |
530 | // Apps hosting us, such as konq, can do the former but not the later. |
531 | // We will do the second bit, as it's a no-op unless someone else explicitly |
532 | // accepts the event. We need to skip the scrollarea to do that, |
533 | // since it will just skip the events, both killing the drop, and |
534 | // not permitting us to forward it up the part hiearchy in our dragEnterEvent, |
535 | // etc. handlers |
536 | return QWidget::event(e); |
537 | case QEvent::StyleChange: |
538 | case QEvent::LayoutRequest: { |
539 | updateScrollBars(); |
540 | return QAbstractScrollArea::event(e); |
541 | } |
542 | case QEvent::PaletteChange: |
543 | slotPaletteChanged(); |
544 | return QScrollArea::event(e); |
545 | default: |
546 | return QScrollArea::event(e); |
547 | } |
548 | } |
549 | #endif |
550 | |
551 | KHTMLView::KHTMLView( KHTMLPart *part, QWidget *parent ) |
552 | : QScrollArea( parent ), d( new KHTMLViewPrivate( this ) ) |
553 | { |
554 | m_medium = "screen" ; |
555 | |
556 | m_part = part; |
557 | |
558 | QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); |
559 | QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy); |
560 | |
561 | init(); |
562 | widget()->setMouseTracking(true); |
563 | } |
564 | |
565 | KHTMLView::~KHTMLView() |
566 | { |
567 | closeChildDialogs(); |
568 | if (m_part) |
569 | { |
570 | DOM::DocumentImpl *doc = m_part->xmlDocImpl(); |
571 | if (doc) |
572 | doc->detach(); |
573 | } |
574 | delete d; |
575 | } |
576 | |
577 | void KHTMLView::setPart(KHTMLPart *part) |
578 | { |
579 | assert(part && !m_part); |
580 | m_part = part; |
581 | } |
582 | |
583 | void KHTMLView::init() |
584 | { |
585 | // Do not access the part here. It might not be fully constructed. |
586 | |
587 | setFrameStyle(QFrame::NoFrame); |
588 | setFocusPolicy(Qt::StrongFocus); |
589 | viewport()->setFocusProxy(this); |
590 | |
591 | _marginWidth = -1; // undefined |
592 | _marginHeight = -1; |
593 | _width = 0; |
594 | _height = 0; |
595 | |
596 | installEventFilter(this); |
597 | |
598 | setAcceptDrops(true); |
599 | if (!widget()) |
600 | setWidget( new QWidget(this) ); |
601 | widget()->setAttribute( Qt::WA_NoSystemBackground ); |
602 | |
603 | // Do *not* remove this attribute frivolously. |
604 | // You might not notice a change of behaviour in Debug builds |
605 | // but removing opaque events will make QWidget::scroll fail horribly |
606 | // in Release builds. |
607 | widget()->setAttribute( Qt::WA_OpaquePaintEvent ); |
608 | |
609 | verticalScrollBar()->setCursor( Qt::ArrowCursor ); |
610 | horizontalScrollBar()->setCursor( Qt::ArrowCursor ); |
611 | |
612 | connect(&d->smoothScrollTimer, SIGNAL(timeout()), this, SLOT(scrollTick())); |
613 | } |
614 | |
615 | void KHTMLView::resizeContentsToViewport() |
616 | { |
617 | QSize s = viewport()->size(); |
618 | resizeContents(s.width(), s.height()); |
619 | } |
620 | |
621 | |
622 | // called by KHTMLPart::clear() |
623 | void KHTMLView::clear() |
624 | { |
625 | if (d->accessKeysEnabled && d->accessKeysActivated) |
626 | accessKeysTimeout(); |
627 | viewport()->unsetCursor(); |
628 | if ( d->cursorIconWidget ) |
629 | d->cursorIconWidget->hide(); |
630 | if (d->smoothScrolling) |
631 | d->stopScrolling(); |
632 | d->reset(); |
633 | QAbstractEventDispatcher::instance()->unregisterTimers(this); |
634 | emit cleared(); |
635 | |
636 | QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy); |
637 | QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); |
638 | verticalScrollBar()->setEnabled( false ); |
639 | horizontalScrollBar()->setEnabled( false ); |
640 | |
641 | } |
642 | |
643 | void KHTMLView::hideEvent(QHideEvent* e) |
644 | { |
645 | QScrollArea::hideEvent(e); |
646 | } |
647 | |
648 | void KHTMLView::showEvent(QShowEvent* e) |
649 | { |
650 | QScrollArea::showEvent(e); |
651 | } |
652 | |
653 | void KHTMLView::setMouseEventsTarget( QWidget* w ) |
654 | { |
655 | d->m_mouseEventsTarget = w; |
656 | } |
657 | |
658 | QWidget* KHTMLView::mouseEventsTarget() const |
659 | { |
660 | return d->m_mouseEventsTarget; |
661 | } |
662 | |
663 | void KHTMLView::setClipHolder( QStack<QRegion>* ch ) |
664 | { |
665 | d->m_clipHolder = ch; |
666 | } |
667 | |
668 | QStack<QRegion>* KHTMLView::clipHolder() const |
669 | { |
670 | return d->m_clipHolder; |
671 | } |
672 | |
673 | int KHTMLView::contentsWidth() const |
674 | { |
675 | return widget() ? widget()->width() : 0; |
676 | } |
677 | |
678 | int KHTMLView::contentsHeight() const |
679 | { |
680 | return widget() ? widget()->height() : 0; |
681 | } |
682 | |
683 | void KHTMLView::resizeContents(int w, int h) |
684 | { |
685 | if (!widget()) |
686 | return; |
687 | widget()->resize(w, h); |
688 | if (!widget()->isVisible()) |
689 | updateScrollBars(); |
690 | } |
691 | |
692 | int KHTMLView::contentsX() const |
693 | { |
694 | return d->contentsX; |
695 | } |
696 | |
697 | int KHTMLView::contentsY() const |
698 | { |
699 | return d->contentsY; |
700 | } |
701 | |
702 | int KHTMLView::visibleWidth() const |
703 | { |
704 | if (m_kwp->isRedirected()) { |
705 | // our RenderWidget knows better |
706 | if (RenderWidget* rw = m_kwp->renderWidget()) { |
707 | int ret = rw->width()-rw->paddingLeft()-rw->paddingRight()-rw->borderLeft()-rw->borderRight(); |
708 | if (verticalScrollBar()->isVisible()) { |
709 | ret -= verticalScrollBar()->sizeHint().width(); |
710 | ret = qMax(0, ret); |
711 | } |
712 | return ret; |
713 | } |
714 | } |
715 | return viewport()->width(); |
716 | } |
717 | |
718 | int KHTMLView::visibleHeight() const |
719 | { |
720 | if (m_kwp->isRedirected()) { |
721 | // our RenderWidget knows better |
722 | if (RenderWidget* rw = m_kwp->renderWidget()) { |
723 | int ret = rw->height()-rw->paddingBottom()-rw->paddingTop()-rw->borderTop()-rw->borderBottom(); |
724 | if (horizontalScrollBar()->isVisible()) { |
725 | ret -= horizontalScrollBar()->sizeHint().height(); |
726 | ret = qMax(0, ret); |
727 | } |
728 | return ret; |
729 | } |
730 | } |
731 | return viewport()->height(); |
732 | } |
733 | |
734 | void KHTMLView::setContentsPos( int x, int y) |
735 | { |
736 | horizontalScrollBar()->setValue( QApplication::isRightToLeft() ? |
737 | horizontalScrollBar()->maximum()-x : x ); |
738 | verticalScrollBar()->setValue( y ); |
739 | } |
740 | |
741 | void KHTMLView::scrollBy(int x, int y) |
742 | { |
743 | if (d->scrollTimerId) |
744 | d->newScrollTimer(this, 0); |
745 | horizontalScrollBar()->setValue( horizontalScrollBar()->value()+x ); |
746 | verticalScrollBar()->setValue( verticalScrollBar()->value()+y ); |
747 | } |
748 | |
749 | QPoint KHTMLView::contentsToViewport(const QPoint& p) const |
750 | { |
751 | return QPoint(p.x()-contentsX(), p.y()-contentsY()); |
752 | } |
753 | |
754 | void KHTMLView::contentsToViewport(int x, int y, int& cx, int& cy) const |
755 | { |
756 | QPoint p(x,y); |
757 | p = contentsToViewport(p); |
758 | cx = p.x(); |
759 | cy = p.y(); |
760 | } |
761 | |
762 | QPoint KHTMLView::viewportToContents(const QPoint& p) const |
763 | { |
764 | return QPoint(p.x()+contentsX(), p.y()+contentsY()); |
765 | } |
766 | |
767 | void KHTMLView::viewportToContents(int x, int y, int& cx, int& cy) const |
768 | { |
769 | QPoint p(x,y); |
770 | p = viewportToContents(p); |
771 | cx = p.x(); |
772 | cy = p.y(); |
773 | } |
774 | |
775 | void KHTMLView::updateContents(int x, int y, int w, int h) |
776 | { |
777 | applyTransforms(x, y, w, h); |
778 | if (m_kwp->isRedirected()) { |
779 | QPoint off = m_kwp->absolutePos(); |
780 | KHTMLView* pview = m_part->parentPart()->view(); |
781 | pview->updateContents(x+off.x(), y+off.y(), w, h); |
782 | } else |
783 | widget()->update(x, y, w, h); |
784 | } |
785 | |
786 | void KHTMLView::updateContents( const QRect& r ) |
787 | { |
788 | updateContents( r.x(), r.y(), r.width(), r.height() ); |
789 | } |
790 | |
791 | void KHTMLView::repaintContents(int x, int y, int w, int h) |
792 | { |
793 | applyTransforms(x, y, w, h); |
794 | if (m_kwp->isRedirected()) { |
795 | QPoint off = m_kwp->absolutePos(); |
796 | KHTMLView* pview = m_part->parentPart()->view(); |
797 | pview->repaintContents(x+off.x(), y+off.y(), w, h); |
798 | } else |
799 | widget()->repaint(x, y, w, h); |
800 | } |
801 | |
802 | void KHTMLView::repaintContents( const QRect& r ) |
803 | { |
804 | repaintContents( r.x(), r.y(), r.width(), r.height() ); |
805 | } |
806 | |
807 | void KHTMLView::applyTransforms( int& x, int& y, int& w, int& h) const |
808 | { |
809 | if (d->haveZoom()) { |
810 | const int z = d->zoomLevel; |
811 | x = x*z/100; |
812 | y = y*z/100; |
813 | w = w*z/100; |
814 | h = h*z/100; |
815 | } |
816 | x -= contentsX(); |
817 | y -= contentsY(); |
818 | } |
819 | |
820 | void KHTMLView::revertTransforms( int& x, int& y, int& w, int& h) const |
821 | { |
822 | x += contentsX(); |
823 | y += contentsY(); |
824 | if (d->haveZoom()) { |
825 | const int z = d->zoomLevel; |
826 | x = x*100/z; |
827 | y = y*100/z; |
828 | w = w*100/z; |
829 | h = h*100/z; |
830 | } |
831 | } |
832 | |
833 | void KHTMLView::revertTransforms( int& x, int& y ) const |
834 | { |
835 | int dummy = 0; |
836 | revertTransforms(x, y, dummy, dummy); |
837 | } |
838 | |
839 | void KHTMLView::resizeEvent (QResizeEvent* /*e*/) |
840 | { |
841 | updateScrollBars(); |
842 | |
843 | // If we didn't load anything, make white area as big as the view |
844 | if (!m_part->xmlDocImpl()) |
845 | resizeContentsToViewport(); |
846 | |
847 | // Viewport-dependent media queries may cause us to need completely different style information. |
848 | if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->styleSelector()->affectedByViewportChange()) { |
849 | m_part->xmlDocImpl()->updateStyleSelector(); |
850 | } |
851 | |
852 | if (d->layoutSchedulingEnabled) |
853 | layout(); |
854 | |
855 | QApplication::sendPostedEvents(viewport(), QEvent::Paint); |
856 | |
857 | if ( m_part && m_part->xmlDocImpl() ) { |
858 | if (m_part->parentPart()) { |
859 | // sub-frame : queue the resize event until our toplevel is done layouting |
860 | khtml::ChildFrame *cf = m_part->parentPart()->frame( m_part ); |
861 | if (cf && !cf->m_partContainerElement.isNull()) |
862 | cf->m_partContainerElement.data()->postResizeEvent(); |
863 | } else { |
864 | // toplevel : dispatch sub-frames'resize events before our own |
865 | HTMLPartContainerElementImpl::sendPostedResizeEvents(); |
866 | m_part->xmlDocImpl()->dispatchWindowEvent( EventImpl::RESIZE_EVENT, false, false ); |
867 | } |
868 | } |
869 | } |
870 | |
871 | void KHTMLView::paintEvent( QPaintEvent *e ) |
872 | { |
873 | QRect r = e->rect(); |
874 | QRect v(contentsX(), contentsY(), visibleWidth(), visibleHeight()); |
875 | QPoint off(contentsX(),contentsY()); |
876 | r.translate(off); |
877 | r = r.intersect(v); |
878 | if (!r.isValid() || r.isEmpty()) return; |
879 | |
880 | QPainter p(widget()); |
881 | p.translate(-off); |
882 | |
883 | if (d->haveZoom()) { |
884 | p.scale( d->zoomLevel/100., d->zoomLevel/100.); |
885 | |
886 | r.setX(r.x()*100/d->zoomLevel); |
887 | r.setY(r.y()*100/d->zoomLevel); |
888 | r.setWidth(r.width()*100/d->zoomLevel); |
889 | r.setHeight(r.height()*100/d->zoomLevel); |
890 | r.adjust(-1,-1,1,1); |
891 | } |
892 | p.setClipRect(r); |
893 | |
894 | int ex = r.x(); |
895 | int ey = r.y(); |
896 | int ew = r.width(); |
897 | int eh = r.height(); |
898 | |
899 | if(!m_part || !m_part->xmlDocImpl() || !m_part->xmlDocImpl()->renderer()) { |
900 | p.fillRect(ex, ey, ew, eh, palette().brush(QPalette::Active, QPalette::Base)); |
901 | return; |
902 | } else if ( d->complete && static_cast<RenderCanvas*>(m_part->xmlDocImpl()->renderer())->needsLayout() ) { |
903 | // an external update request happens while we have a layout scheduled |
904 | unscheduleRelayout(); |
905 | layout(); |
906 | } else if (m_part->xmlDocImpl()->tokenizer()) { |
907 | m_part->xmlDocImpl()->tokenizer()->setNormalYieldDelay(); |
908 | } |
909 | |
910 | if (d->painting) { |
911 | kDebug( 6000 ) << "WARNING: paintEvent reentered! " ; |
912 | kDebug( 6000 ) << kBacktrace(); |
913 | return; |
914 | } |
915 | d->painting = true; |
916 | |
917 | m_part->xmlDocImpl()->renderer()->layer()->paint(&p, r); |
918 | |
919 | if (d->hasFrameset) { |
920 | NodeImpl *body = static_cast<HTMLDocumentImpl*>(m_part->xmlDocImpl())->body(); |
921 | if(body && body->renderer() && body->id() == ID_FRAMESET) |
922 | static_cast<RenderFrameSet*>(body->renderer())->paintFrameSetRules(&p, r); |
923 | else |
924 | d->hasFrameset = false; |
925 | } |
926 | |
927 | khtml::DrawContentsEvent event( &p, ex, ey, ew, eh ); |
928 | QApplication::sendEvent( m_part, &event ); |
929 | |
930 | if (d->contentsMoving && !d->smoothScrolling && widget()->underMouse()) { |
931 | QMouseEvent *tempEvent = new QMouseEvent( QEvent::MouseMove, widget()->mapFromGlobal( QCursor::pos() ), |
932 | Qt::NoButton, Qt::NoButton, Qt::NoModifier ); |
933 | QApplication::postEvent(widget(), tempEvent); |
934 | } |
935 | #ifdef SPEED_DEBUG |
936 | if (d->firstRepaintPending && !m_part->parentPart()) { |
937 | kDebug(6080) << "FIRST PAINT:" << m_part->d->m_parsetime.elapsed(); |
938 | } |
939 | d->firstRepaintPending = false; |
940 | #endif |
941 | d->painting = false; |
942 | } |
943 | |
944 | void KHTMLView::setMarginWidth(int w) |
945 | { |
946 | // make it update the rendering area when set |
947 | _marginWidth = w; |
948 | } |
949 | |
950 | void KHTMLView::setMarginHeight(int h) |
951 | { |
952 | // make it update the rendering area when set |
953 | _marginHeight = h; |
954 | } |
955 | |
956 | void KHTMLView::layout() |
957 | { |
958 | if( m_part && m_part->xmlDocImpl() ) { |
959 | DOM::DocumentImpl *document = m_part->xmlDocImpl(); |
960 | |
961 | khtml::RenderCanvas* canvas = static_cast<khtml::RenderCanvas *>(document->renderer()); |
962 | if ( !canvas ) return; |
963 | |
964 | d->layoutSchedulingEnabled=false; |
965 | d->dirtyLayout = true; |
966 | |
967 | // the reference object for the overflow property on canvas |
968 | RenderObject * ref = 0; |
969 | RenderObject* root = document->documentElement() ? document->documentElement()->renderer() : 0; |
970 | |
971 | if (document->isHTMLDocument()) { |
972 | NodeImpl *body = static_cast<HTMLDocumentImpl*>(document)->body(); |
973 | if(body && body->renderer() && body->id() == ID_FRAMESET) { |
974 | QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
975 | QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
976 | body->renderer()->setNeedsLayout(true); |
977 | d->hasFrameset = true; |
978 | } |
979 | else if (root) // only apply body's overflow to canvas if root has a visible overflow |
980 | ref = (!body || root->style()->hidesOverflow()) ? root : body->renderer(); |
981 | } else { |
982 | ref = root; |
983 | } |
984 | if (ref) { |
985 | if( ref->style()->overflowX() == OHIDDEN ) { |
986 | if (d->hpolicy == Qt::ScrollBarAsNeeded) QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
987 | } else if (ref->style()->overflowX() == OSCROLL ) { |
988 | if (d->hpolicy == Qt::ScrollBarAsNeeded) QScrollArea::setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); |
989 | } else if (horizontalScrollBarPolicy() != d->hpolicy) { |
990 | QScrollArea::setHorizontalScrollBarPolicy(d->hpolicy); |
991 | } |
992 | if ( ref->style()->overflowY() == OHIDDEN ) { |
993 | if (d->vpolicy == Qt::ScrollBarAsNeeded) QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
994 | } else if (ref->style()->overflowY() == OSCROLL ) { |
995 | if (d->vpolicy == Qt::ScrollBarAsNeeded) QScrollArea::setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); |
996 | } else if (verticalScrollBarPolicy() != d->vpolicy) { |
997 | QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); |
998 | } |
999 | } |
1000 | d->needsFullRepaint = d->firstLayoutPending; |
1001 | if (_height != visibleHeight() || _width != visibleWidth()) {; |
1002 | d->needsFullRepaint = true; |
1003 | _height = visibleHeight(); |
1004 | _width = visibleWidth(); |
1005 | } |
1006 | |
1007 | canvas->layout(); |
1008 | |
1009 | emit finishedLayout(); |
1010 | if (d->firstLayoutPending) { |
1011 | // make sure firstLayoutPending is set to false now in case this layout |
1012 | // wasn't scheduled |
1013 | d->firstLayoutPending = false; |
1014 | verticalScrollBar()->setEnabled( true ); |
1015 | horizontalScrollBar()->setEnabled( true ); |
1016 | } |
1017 | d->layoutCounter++; |
1018 | |
1019 | if (d->accessKeysEnabled && d->accessKeysActivated) { |
1020 | emit hideAccessKeys(); |
1021 | displayAccessKeys(); |
1022 | } |
1023 | } |
1024 | else |
1025 | _width = visibleWidth(); |
1026 | |
1027 | if (d->layoutTimerId) |
1028 | killTimer(d->layoutTimerId); |
1029 | d->layoutTimerId = 0; |
1030 | d->layoutSchedulingEnabled=true; |
1031 | } |
1032 | |
1033 | void KHTMLView::closeChildDialogs() |
1034 | { |
1035 | QList<QDialog *> dlgs = findChildren<QDialog *>(); |
1036 | foreach (QDialog *dlg, dlgs) |
1037 | { |
1038 | KDialog* dlgbase = dynamic_cast<KDialog*>( dlg ); |
1039 | if ( dlgbase ) { |
1040 | if ( dlgbase->testAttribute( Qt::WA_ShowModal ) ) { |
1041 | kDebug(6000) << "closeChildDialogs: closing dialog " << dlgbase; |
1042 | // close() ends up calling QButton::animateClick, which isn't immediate |
1043 | // we need something the exits the event loop immediately (#49068) |
1044 | dlgbase->reject(); |
1045 | } |
1046 | } |
1047 | else |
1048 | { |
1049 | kWarning() << "closeChildDialogs: not a KDialog! Don't use QDialogs in KDE! " << static_cast<QWidget*>(dlg); |
1050 | static_cast<QWidget*>(dlg)->hide(); |
1051 | } |
1052 | } |
1053 | d->m_dialogsAllowed = false; |
1054 | } |
1055 | |
1056 | bool KHTMLView::dialogsAllowed() { |
1057 | bool allowed = d->m_dialogsAllowed; |
1058 | KHTMLPart* p = m_part->parentPart(); |
1059 | if (p && p->view()) |
1060 | allowed &= p->view()->dialogsAllowed(); |
1061 | return allowed; |
1062 | } |
1063 | |
1064 | void KHTMLView::closeEvent( QCloseEvent* ev ) |
1065 | { |
1066 | closeChildDialogs(); |
1067 | QScrollArea::closeEvent( ev ); |
1068 | } |
1069 | |
1070 | void KHTMLView::setZoomLevel(int percent) |
1071 | { |
1072 | percent = percent < 20 ? 20 : (percent > 800 ? 800 : percent); |
1073 | int oldpercent = d->zoomLevel; |
1074 | d->zoomLevel = percent; |
1075 | if (percent != oldpercent) { |
1076 | if (d->layoutSchedulingEnabled) |
1077 | layout(); |
1078 | widget()->update(); |
1079 | } |
1080 | } |
1081 | |
1082 | int KHTMLView::zoomLevel() const |
1083 | { |
1084 | return d->zoomLevel; |
1085 | } |
1086 | |
1087 | void KHTMLView::setSmoothScrollingMode( SmoothScrollingMode m ) |
1088 | { |
1089 | d->smoothScrollMode = m; |
1090 | d->smoothScrollModeIsDefault = false; |
1091 | if (d->smoothScrolling && !m) |
1092 | d->stopScrolling(); |
1093 | } |
1094 | |
1095 | void KHTMLView::setSmoothScrollingModeDefault( SmoothScrollingMode m ) |
1096 | { |
1097 | // check for manual override |
1098 | if (!d->smoothScrollModeIsDefault) |
1099 | return; |
1100 | d->smoothScrollMode = m; |
1101 | if (d->smoothScrolling && !m) |
1102 | d->stopScrolling(); |
1103 | } |
1104 | |
1105 | KHTMLView::SmoothScrollingMode KHTMLView::smoothScrollingMode( ) const |
1106 | { |
1107 | return d->smoothScrollMode; |
1108 | } |
1109 | |
1110 | // |
1111 | // Event Handling |
1112 | // |
1113 | ///////////////// |
1114 | |
1115 | void KHTMLView::mousePressEvent( QMouseEvent *_mouse ) |
1116 | { |
1117 | if (!m_part->xmlDocImpl()) return; |
1118 | if (d->possibleTripleClick && ( _mouse->button() & Qt::MouseButtonMask ) == Qt::LeftButton) |
1119 | { |
1120 | mouseDoubleClickEvent( _mouse ); // it handles triple clicks too |
1121 | return; |
1122 | } |
1123 | |
1124 | int xm = _mouse->x(); |
1125 | int ym = _mouse->y(); |
1126 | revertTransforms(xm, ym); |
1127 | |
1128 | // kDebug( 6000 ) << "mousePressEvent: viewport=("<<_mouse->x()-contentsX()<<"/"<<_mouse->y()-contentsY()<<"), contents=(" << xm << "/" << ym << ")\n"; |
1129 | |
1130 | d->isDoubleClick = false; |
1131 | |
1132 | DOM::NodeImpl::MouseEvent mev( _mouse->buttons(), DOM::NodeImpl::MousePress ); |
1133 | m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev ); |
1134 | |
1135 | //kDebug(6000) << "innerNode="<<mev.innerNode.nodeName().string(); |
1136 | |
1137 | if ( (_mouse->button() == Qt::MidButton) && |
1138 | !m_part->d->m_bOpenMiddleClick && !d->m_mouseScrollTimer && |
1139 | mev.url.isNull() && (mev.innerNode.elementId() != ID_INPUT) ) { |
1140 | QPoint point = mapFromGlobal( _mouse->globalPos() ); |
1141 | |
1142 | d->m_mouseScroll_byX = 0; |
1143 | d->m_mouseScroll_byY = 0; |
1144 | |
1145 | d->m_mouseScrollTimer = new QTimer( this ); |
1146 | connect( d->m_mouseScrollTimer, SIGNAL(timeout()), this, SLOT(slotMouseScrollTimer()) ); |
1147 | |
1148 | if ( !d->m_mouseScrollIndicator ) { |
1149 | QPixmap pixmap( 48, 48 ), icon; |
1150 | pixmap.fill( QColor( qRgba( 127, 127, 127, 127 ) ) ); |
1151 | |
1152 | QPainter p( &pixmap ); |
1153 | QStyleOption option; |
1154 | |
1155 | option.rect.setRect( 16, 0, 16, 16 ); |
1156 | QApplication::style()->drawPrimitive( QStyle::PE_IndicatorArrowUp, &option, &p ); |
1157 | option.rect.setRect( 0, 16, 16, 16 ); |
1158 | QApplication::style()->drawPrimitive( QStyle::PE_IndicatorArrowLeft, &option, &p ); |
1159 | option.rect.setRect( 16, 32, 16, 16 ); |
1160 | QApplication::style()->drawPrimitive( QStyle::PE_IndicatorArrowDown, &option, &p ); |
1161 | option.rect.setRect( 32, 16, 16, 16 ); |
1162 | QApplication::style()->drawPrimitive( QStyle::PE_IndicatorArrowRight, &option, &p ); |
1163 | p.drawEllipse( 23, 23, 2, 2 ); |
1164 | |
1165 | d->m_mouseScrollIndicator = new QWidget( this ); |
1166 | d->m_mouseScrollIndicator->setFixedSize( 48, 48 ); |
1167 | QPalette palette; |
1168 | palette.setBrush( d->m_mouseScrollIndicator->backgroundRole(), QBrush( pixmap ) ); |
1169 | d->m_mouseScrollIndicator->setPalette( palette ); |
1170 | } |
1171 | d->m_mouseScrollIndicator->move( point.x()-24, point.y()-24 ); |
1172 | |
1173 | bool hasHorBar = visibleWidth() < contentsWidth(); |
1174 | bool hasVerBar = visibleHeight() < contentsHeight(); |
1175 | |
1176 | KConfigGroup cg( KGlobal::config(), "HTML Settings" ); |
1177 | if ( cg.readEntry( "ShowMouseScrollIndicator" , true ) ) { |
1178 | d->m_mouseScrollIndicator->show(); |
1179 | d->m_mouseScrollIndicator->unsetCursor(); |
1180 | |
1181 | QBitmap mask = d->m_mouseScrollIndicator->palette().brush(d->m_mouseScrollIndicator->backgroundRole()).texture().createHeuristicMask( true ); |
1182 | |
1183 | if ( hasHorBar && !hasVerBar ) { |
1184 | QBitmap bm( 16, 16 ); |
1185 | bm.clear(); |
1186 | QPainter painter( &mask ); |
1187 | painter.drawPixmap( QRectF( 16, 0, bm.width(), bm.height() ), bm, bm.rect() ); |
1188 | painter.drawPixmap( QRectF( 16, 32, bm.width(), bm.height() ), bm, bm.rect() ); |
1189 | d->m_mouseScrollIndicator->setCursor( Qt::SizeHorCursor ); |
1190 | } |
1191 | else if ( !hasHorBar && hasVerBar ) { |
1192 | QBitmap bm( 16, 16 ); |
1193 | bm.clear(); |
1194 | QPainter painter( &mask ); |
1195 | painter.drawPixmap( QRectF( 0, 16, bm.width(), bm.height() ), bm, bm.rect() ); |
1196 | painter.drawPixmap( QRectF( 32, 16, bm.width(), bm.height() ), bm, bm.rect() ); |
1197 | d->m_mouseScrollIndicator->setCursor( Qt::SizeVerCursor ); |
1198 | } |
1199 | else |
1200 | d->m_mouseScrollIndicator->setCursor( Qt::SizeAllCursor ); |
1201 | |
1202 | d->m_mouseScrollIndicator->setMask( mask ); |
1203 | } |
1204 | else { |
1205 | if ( hasHorBar && !hasVerBar ) |
1206 | viewport()->setCursor( Qt::SizeHorCursor ); |
1207 | else if ( !hasHorBar && hasVerBar ) |
1208 | viewport()->setCursor( Qt::SizeVerCursor ); |
1209 | else |
1210 | viewport()->setCursor( Qt::SizeAllCursor ); |
1211 | } |
1212 | |
1213 | return; |
1214 | } |
1215 | else if ( d->m_mouseScrollTimer ) { |
1216 | delete d->m_mouseScrollTimer; |
1217 | d->m_mouseScrollTimer = 0; |
1218 | |
1219 | if ( d->m_mouseScrollIndicator ) |
1220 | d->m_mouseScrollIndicator->hide(); |
1221 | } |
1222 | |
1223 | if (d->clickCount > 0 && |
1224 | QPoint(d->clickX-xm,d->clickY-ym).manhattanLength() <= QApplication::startDragDistance()) |
1225 | d->clickCount++; |
1226 | else { |
1227 | d->clickCount = 1; |
1228 | d->clickX = xm; |
1229 | d->clickY = ym; |
1230 | } |
1231 | |
1232 | bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEDOWN_EVENT,mev.innerNode.handle(),mev.innerNonSharedNode.handle(),true, |
1233 | d->clickCount,_mouse,true,DOM::NodeImpl::MousePress); |
1234 | |
1235 | if (!swallowEvent) { |
1236 | emit m_part->nodeActivated(mev.innerNode); |
1237 | |
1238 | khtml::MousePressEvent event( _mouse, xm, ym, mev.url, mev.target, mev.innerNode ); |
1239 | QApplication::sendEvent( m_part, &event ); |
1240 | // we might be deleted after this |
1241 | } |
1242 | } |
1243 | |
1244 | void KHTMLView::mouseDoubleClickEvent( QMouseEvent *_mouse ) |
1245 | { |
1246 | if(!m_part->xmlDocImpl()) return; |
1247 | |
1248 | int xm = _mouse->x(); |
1249 | int ym = _mouse->y(); |
1250 | revertTransforms(xm, ym); |
1251 | |
1252 | // kDebug( 6000 ) << "mouseDblClickEvent: x=" << xm << ", y=" << ym; |
1253 | |
1254 | d->isDoubleClick = true; |
1255 | |
1256 | DOM::NodeImpl::MouseEvent mev( _mouse->buttons(), DOM::NodeImpl::MouseDblClick ); |
1257 | m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev ); |
1258 | |
1259 | // We do the same thing as mousePressEvent() here, since the DOM does not treat |
1260 | // single and double-click events as separate (only the detail, i.e. number of clicks differs) |
1261 | if (d->clickCount > 0 && |
1262 | QPoint(d->clickX-xm,d->clickY-ym).manhattanLength() <= QApplication::startDragDistance()) |
1263 | d->clickCount++; |
1264 | else { // shouldn't happen, if Qt has the same criterias for double clicks. |
1265 | d->clickCount = 1; |
1266 | d->clickX = xm; |
1267 | d->clickY = ym; |
1268 | } |
1269 | bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEDOWN_EVENT,mev.innerNode.handle(),mev.innerNonSharedNode.handle(),true, |
1270 | d->clickCount,_mouse,true,DOM::NodeImpl::MouseDblClick); |
1271 | |
1272 | if (!swallowEvent) { |
1273 | khtml::MouseDoubleClickEvent event( _mouse, xm, ym, mev.url, mev.target, mev.innerNode, d->clickCount ); |
1274 | QApplication::sendEvent( m_part, &event ); |
1275 | } |
1276 | |
1277 | d->possibleTripleClick=true; |
1278 | QTimer::singleShot(QApplication::doubleClickInterval(),this,SLOT(tripleClickTimeout())); |
1279 | } |
1280 | |
1281 | void KHTMLView::tripleClickTimeout() |
1282 | { |
1283 | d->possibleTripleClick = false; |
1284 | d->clickCount = 0; |
1285 | } |
1286 | |
1287 | static bool targetOpensNewWindow(KHTMLPart *part, QString target) |
1288 | { |
1289 | if (!target.isEmpty() && (target.toLower() != "_top" ) && |
1290 | (target.toLower() != "_self" ) && (target.toLower() != "_parent" )) { |
1291 | if (target.toLower() == "_blank" ) |
1292 | return true; |
1293 | else { |
1294 | while (part->parentPart()) |
1295 | part = part->parentPart(); |
1296 | if (!part->frameExists(target)) |
1297 | return true; |
1298 | } |
1299 | } |
1300 | return false; |
1301 | } |
1302 | |
1303 | void KHTMLView::mouseMoveEvent( QMouseEvent * _mouse ) |
1304 | { |
1305 | if ( d->m_mouseScrollTimer ) { |
1306 | QPoint point = mapFromGlobal( _mouse->globalPos() ); |
1307 | |
1308 | int deltaX = point.x() - d->m_mouseScrollIndicator->x() - 24; |
1309 | int deltaY = point.y() - d->m_mouseScrollIndicator->y() - 24; |
1310 | |
1311 | (deltaX > 0) ? d->m_mouseScroll_byX = 1 : d->m_mouseScroll_byX = -1; |
1312 | (deltaY > 0) ? d->m_mouseScroll_byY = 1 : d->m_mouseScroll_byY = -1; |
1313 | |
1314 | double adX = qAbs(deltaX)/30.0; |
1315 | double adY = qAbs(deltaY)/30.0; |
1316 | |
1317 | d->m_mouseScroll_byX = qMax(qMin(d->m_mouseScroll_byX * int(adX*adX), SHRT_MAX), SHRT_MIN); |
1318 | d->m_mouseScroll_byY = qMax(qMin(d->m_mouseScroll_byY * int(adY*adY), SHRT_MAX), SHRT_MIN); |
1319 | |
1320 | if (d->m_mouseScroll_byX == 0 && d->m_mouseScroll_byY == 0) { |
1321 | d->m_mouseScrollTimer->stop(); |
1322 | } |
1323 | else if (!d->m_mouseScrollTimer->isActive()) { |
1324 | d->m_mouseScrollTimer->start( 20 ); |
1325 | } |
1326 | } |
1327 | |
1328 | if(!m_part->xmlDocImpl()) return; |
1329 | |
1330 | int xm = _mouse->x(); |
1331 | int ym = _mouse->y(); |
1332 | revertTransforms(xm, ym); |
1333 | |
1334 | DOM::NodeImpl::MouseEvent mev( _mouse->buttons(), DOM::NodeImpl::MouseMove ); |
1335 | // Do not modify :hover/:active state while mouse is pressed. |
1336 | m_part->xmlDocImpl()->prepareMouseEvent( _mouse->buttons() /*readonly ?*/, xm, ym, &mev ); |
1337 | |
1338 | // kDebug(6000) << "mouse move: " << _mouse->pos() |
1339 | // << " button " << _mouse->button() |
1340 | // << " state " << _mouse->state() << endl; |
1341 | |
1342 | DOM::NodeImpl* target = mev.innerNode.handle(); |
1343 | DOM::NodeImpl* fn = m_part->xmlDocImpl()->focusNode(); |
1344 | |
1345 | // a widget may be the real target of this event (e.g. if a scrollbar's slider is being moved) |
1346 | if (d->m_mouseEventsTarget && fn && fn->renderer() && fn->renderer()->isWidget()) |
1347 | target = fn; |
1348 | |
1349 | bool swallowEvent = dispatchMouseEvent(EventImpl::MOUSEMOVE_EVENT,target,mev.innerNonSharedNode.handle(),false, |
1350 | 0,_mouse,true,DOM::NodeImpl::MouseMove); |
1351 | |
1352 | if (d->clickCount > 0 && |
1353 | QPoint(d->clickX-xm,d->clickY-ym).manhattanLength() > QApplication::startDragDistance()) { |
1354 | d->clickCount = 0; // moving the mouse outside the threshold invalidates the click |
1355 | } |
1356 | |
1357 | khtml::RenderObject* r = target ? target->renderer() : 0; |
1358 | bool setCursor = true; |
1359 | bool forceDefault = false; |
1360 | if (r && r->isWidget()) { |
1361 | RenderWidget* rw = static_cast<RenderWidget*>(r); |
1362 | KHTMLWidget* kw = qobject_cast<KHTMLView*>(rw->widget())? dynamic_cast<KHTMLWidget*>(rw->widget()) : 0; |
1363 | if (kw && kw->m_kwp->isRedirected()) |
1364 | setCursor = false; |
1365 | else if (QLineEdit* le = qobject_cast<QLineEdit*>(rw->widget())) { |
1366 | QList<QWidget*> wl = qFindChildren<QWidget *>( le, "KLineEditButton" ); |
1367 | // force arrow cursor above lineedit clear button |
1368 | foreach (QWidget*w, wl) { |
1369 | if (w->underMouse()) { |
1370 | forceDefault = true; |
1371 | break; |
1372 | } |
1373 | } |
1374 | } |
1375 | else if (QTextEdit* te = qobject_cast<QTextEdit*>(rw->widget())) { |
1376 | if (te->verticalScrollBar()->underMouse() || te->horizontalScrollBar()->underMouse()) |
1377 | forceDefault = true; |
1378 | } |
1379 | } |
1380 | khtml::RenderStyle* style = (r && r->style()) ? r->style() : 0; |
1381 | QCursor c; |
1382 | LinkCursor linkCursor = LINK_NORMAL; |
1383 | switch (!forceDefault ? (style ? style->cursor() : CURSOR_AUTO) : CURSOR_DEFAULT) { |
1384 | case CURSOR_AUTO: |
1385 | if ( r && r->isText() && ((m_part->d->m_bMousePressed && m_part->d->editor_context.m_beganSelectingText) || |
1386 | !r->isPointInsideSelection(xm, ym, m_part->caret())) ) |
1387 | c = QCursor(Qt::IBeamCursor); |
1388 | if ( mev.url.length() && m_part->settings()->changeCursor() ) { |
1389 | c = m_part->urlCursor(); |
1390 | if (mev.url.string().startsWith("mailto:" ) && mev.url.string().indexOf('@')>0) |
1391 | linkCursor = LINK_MAILTO; |
1392 | else |
1393 | if ( targetOpensNewWindow( m_part, mev.target.string() ) ) |
1394 | linkCursor = LINK_NEWWINDOW; |
1395 | } |
1396 | |
1397 | if (r && r->isFrameSet() && !static_cast<RenderFrameSet*>(r)->noResize()) |
1398 | c = QCursor(static_cast<RenderFrameSet*>(r)->cursorShape()); |
1399 | |
1400 | break; |
1401 | case CURSOR_CROSS: |
1402 | c = QCursor(Qt::CrossCursor); |
1403 | break; |
1404 | case CURSOR_POINTER: |
1405 | c = m_part->urlCursor(); |
1406 | if (mev.url.string().startsWith("mailto:" ) && mev.url.string().indexOf('@')>0) |
1407 | linkCursor = LINK_MAILTO; |
1408 | else |
1409 | if ( targetOpensNewWindow( m_part, mev.target.string() ) ) |
1410 | linkCursor = LINK_NEWWINDOW; |
1411 | break; |
1412 | case CURSOR_PROGRESS: |
1413 | c = QCursor(Qt::BusyCursor); // working_cursor |
1414 | break; |
1415 | case CURSOR_MOVE: |
1416 | case CURSOR_ALL_SCROLL: |
1417 | c = QCursor(Qt::SizeAllCursor); |
1418 | break; |
1419 | case CURSOR_E_RESIZE: |
1420 | case CURSOR_W_RESIZE: |
1421 | case CURSOR_EW_RESIZE: |
1422 | c = QCursor(Qt::SizeHorCursor); |
1423 | break; |
1424 | case CURSOR_N_RESIZE: |
1425 | case CURSOR_S_RESIZE: |
1426 | case CURSOR_NS_RESIZE: |
1427 | c = QCursor(Qt::SizeVerCursor); |
1428 | break; |
1429 | case CURSOR_NE_RESIZE: |
1430 | case CURSOR_SW_RESIZE: |
1431 | case CURSOR_NESW_RESIZE: |
1432 | c = QCursor(Qt::SizeBDiagCursor); |
1433 | break; |
1434 | case CURSOR_NW_RESIZE: |
1435 | case CURSOR_SE_RESIZE: |
1436 | case CURSOR_NWSE_RESIZE: |
1437 | c = QCursor(Qt::SizeFDiagCursor); |
1438 | break; |
1439 | case CURSOR_TEXT: |
1440 | c = QCursor(Qt::IBeamCursor); |
1441 | break; |
1442 | case CURSOR_WAIT: |
1443 | c = QCursor(Qt::WaitCursor); |
1444 | break; |
1445 | case CURSOR_HELP: |
1446 | c = QCursor(Qt::WhatsThisCursor); |
1447 | break; |
1448 | case CURSOR_DEFAULT: |
1449 | break; |
1450 | case CURSOR_NONE: |
1451 | case CURSOR_NOT_ALLOWED: |
1452 | c = QCursor(Qt::ForbiddenCursor); |
1453 | break; |
1454 | case CURSOR_ROW_RESIZE: |
1455 | c = QCursor(Qt::SplitVCursor); |
1456 | break; |
1457 | case CURSOR_COL_RESIZE: |
1458 | c = QCursor(Qt::SplitHCursor); |
1459 | break; |
1460 | case CURSOR_VERTICAL_TEXT: |
1461 | case CURSOR_CONTEXT_MENU: |
1462 | case CURSOR_NO_DROP: |
1463 | case CURSOR_CELL: |
1464 | case CURSOR_COPY: |
1465 | case CURSOR_ALIAS: |
1466 | c = QCursor(Qt::ArrowCursor); |
1467 | break; |
1468 | } |
1469 | |
1470 | if (!setCursor && style && style->cursor() != CURSOR_AUTO) |
1471 | setCursor = true; |
1472 | |
1473 | QWidget* vp = viewport(); |
1474 | for (KHTMLPart* p = m_part; p; p = p->parentPart()) |
1475 | if (!p->parentPart()) |
1476 | vp = p->view()->viewport(); |
1477 | if ( setCursor && vp->cursor().handle() != c.handle() ) { |
1478 | if( c.shape() == Qt::ArrowCursor) { |
1479 | for (KHTMLPart* p = m_part; p; p = p->parentPart()) |
1480 | p->view()->viewport()->unsetCursor(); |
1481 | } |
1482 | else { |
1483 | vp->setCursor( c ); |
1484 | } |
1485 | } |
1486 | |
1487 | if ( linkCursor!=LINK_NORMAL && isVisible() && hasFocus() ) { |
1488 | #ifdef Q_WS_X11 |
1489 | |
1490 | if( !d->cursorIconWidget ) { |
1491 | #ifdef Q_WS_X11 |
1492 | d->cursorIconWidget = new QLabel( 0, Qt::X11BypassWindowManagerHint ); |
1493 | XSetWindowAttributes attr; |
1494 | attr.save_under = True; |
1495 | XChangeWindowAttributes( QX11Info::display(), d->cursorIconWidget->winId(), CWSaveUnder, &attr ); |
1496 | #else |
1497 | d->cursorIconWidget = new QLabel( NULL, NULL ); |
1498 | //TODO |
1499 | #endif |
1500 | } |
1501 | |
1502 | // Update the pixmap if need be. |
1503 | if (linkCursor != d->cursorIconType) { |
1504 | d->cursorIconType = linkCursor; |
1505 | QString cursorIcon; |
1506 | switch (linkCursor) |
1507 | { |
1508 | case LINK_MAILTO: cursorIcon = "mail-message-new" ; break; |
1509 | case LINK_NEWWINDOW: cursorIcon = "window-new" ; break; |
1510 | default: cursorIcon = "dialog-error" ; break; |
1511 | } |
1512 | |
1513 | QPixmap icon_pixmap = KHTMLGlobal::iconLoader()->loadIcon( cursorIcon, KIconLoader::Small, 0, KIconLoader::DefaultState, QStringList(), 0, true ); |
1514 | |
1515 | d->cursorIconWidget->resize( icon_pixmap.width(), icon_pixmap.height()); |
1516 | d->cursorIconWidget->setMask( icon_pixmap.createMaskFromColor(Qt::transparent)); |
1517 | d->cursorIconWidget->setPixmap( icon_pixmap); |
1518 | d->cursorIconWidget->update(); |
1519 | } |
1520 | |
1521 | QPoint c_pos = QCursor::pos(); |
1522 | d->cursorIconWidget->move( c_pos.x() + 15, c_pos.y() + 15 ); |
1523 | #ifdef Q_WS_X11 |
1524 | XRaiseWindow( QX11Info::display(), d->cursorIconWidget->winId()); |
1525 | QApplication::flush(); |
1526 | #elif defined(Q_WS_WIN) |
1527 | SetWindowPos( d->cursorIconWidget->winId(), HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE ); |
1528 | #else |
1529 | //TODO? |
1530 | #endif |
1531 | d->cursorIconWidget->show(); |
1532 | #endif |
1533 | } |
1534 | else if ( d->cursorIconWidget ) |
1535 | d->cursorIconWidget->hide(); |
1536 | |
1537 | if (r && r->isWidget()) { |
1538 | _mouse->ignore(); |
1539 | } |
1540 | |
1541 | if (!swallowEvent) { |
1542 | khtml::MouseMoveEvent event( _mouse, xm, ym, mev.url, mev.target, mev.innerNode ); |
1543 | QApplication::sendEvent( m_part, &event ); |
1544 | } |
1545 | } |
1546 | |
1547 | void KHTMLView::mouseReleaseEvent( QMouseEvent * _mouse ) |
1548 | { |
1549 | bool swallowEvent = false; |
1550 | |
1551 | int xm = _mouse->x(); |
1552 | int ym = _mouse->y(); |
1553 | revertTransforms(xm, ym); |
1554 | |
1555 | DOM::NodeImpl::MouseEvent mev( _mouse->buttons(), DOM::NodeImpl::MouseRelease ); |
1556 | |
1557 | if ( m_part->xmlDocImpl() ) |
1558 | { |
1559 | m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev ); |
1560 | |
1561 | DOM::NodeImpl* target = mev.innerNode.handle(); |
1562 | DOM::NodeImpl* fn = m_part->xmlDocImpl()->focusNode(); |
1563 | |
1564 | // a widget may be the real target of this event (e.g. if a scrollbar's slider is being moved) |
1565 | if (d->m_mouseEventsTarget && fn && fn->renderer() && fn->renderer()->isWidget()) |
1566 | target = fn; |
1567 | |
1568 | swallowEvent = dispatchMouseEvent(EventImpl::MOUSEUP_EVENT,target,mev.innerNonSharedNode.handle(),true, |
1569 | d->clickCount,_mouse,false,DOM::NodeImpl::MouseRelease); |
1570 | |
1571 | // clear our sticky event target on any mouseRelease event |
1572 | if (d->m_mouseEventsTarget) |
1573 | d->m_mouseEventsTarget = 0; |
1574 | |
1575 | if (d->clickCount > 0 && |
1576 | QPoint(d->clickX-xm,d->clickY-ym).manhattanLength() <= QApplication::startDragDistance()) { |
1577 | QMouseEvent me(d->isDoubleClick ? QEvent::MouseButtonDblClick : QEvent::MouseButtonRelease, |
1578 | _mouse->pos(), _mouse->button(), _mouse->buttons(), _mouse->modifiers()); |
1579 | dispatchMouseEvent(EventImpl::CLICK_EVENT, mev.innerNode.handle(),mev.innerNonSharedNode.handle(),true, |
1580 | d->clickCount, &me, true, DOM::NodeImpl::MouseRelease); |
1581 | } |
1582 | |
1583 | khtml::RenderObject* r = target ? target->renderer() : 0; |
1584 | if (r && r->isWidget()) |
1585 | _mouse->ignore(); |
1586 | } |
1587 | |
1588 | if (!swallowEvent) { |
1589 | khtml::MouseReleaseEvent event( _mouse, xm, ym, mev.url, mev.target, mev.innerNode ); |
1590 | QApplication::sendEvent( m_part, &event ); |
1591 | } |
1592 | } |
1593 | |
1594 | // returns true if event should be swallowed |
1595 | bool KHTMLView::dispatchKeyEvent( QKeyEvent *_ke ) |
1596 | { |
1597 | if (!m_part->xmlDocImpl()) |
1598 | return false; |
1599 | // Pressing and releasing a key should generate keydown, keypress and keyup events |
1600 | // Holding it down should generated keydown, keypress (repeatedly) and keyup events |
1601 | // The problem here is that Qt generates two autorepeat events (keyrelease+keypress) |
1602 | // for autorepeating, while DOM wants only one autorepeat event (keypress), so one |
1603 | // of the Qt events shouldn't be passed to DOM, but it should be still filtered |
1604 | // out if DOM would filter the autorepeat event. Additional problem is that Qt keyrelease |
1605 | // events don't have text() set (Qt bug?), so DOM often would ignore the keypress event |
1606 | // if it was created using Qt keyrelease, but Qt autorepeat keyrelease comes |
1607 | // before Qt autorepeat keypress (i.e. problem whether to filter it out or not). |
1608 | // The solution is to filter out and postpone the Qt autorepeat keyrelease until |
1609 | // the following Qt keypress event comes. If DOM accepts the DOM keypress event, |
1610 | // the postponed event will be simply discarded. If not, it will be passed to keyPressEvent() |
1611 | // again, and here it will be ignored. |
1612 | // |
1613 | // Qt: Press | Release(autorepeat) Press(autorepeat) etc. | Release |
1614 | // DOM: Down + Press | (nothing) Press | Up |
1615 | |
1616 | // It's also possible to get only Releases. E.g. the release of alt-tab, |
1617 | // or when the keypresses get captured by an accel. |
1618 | |
1619 | if( _ke == d->postponed_autorepeat ) // replayed event |
1620 | { |
1621 | return false; |
1622 | } |
1623 | |
1624 | if( _ke->type() == QEvent::KeyPress ) |
1625 | { |
1626 | if( !_ke->isAutoRepeat()) |
1627 | { |
1628 | bool ret = dispatchKeyEventHelper( _ke, false ); // keydown |
1629 | // don't send keypress even if keydown was blocked, like IE (and unlike Mozilla) |
1630 | if( !ret && dispatchKeyEventHelper( _ke, true )) // keypress |
1631 | ret = true; |
1632 | return ret; |
1633 | } |
1634 | else // autorepeat |
1635 | { |
1636 | bool ret = dispatchKeyEventHelper( _ke, true ); // keypress |
1637 | if( !ret && d->postponed_autorepeat ) |
1638 | keyPressEvent( d->postponed_autorepeat ); |
1639 | delete d->postponed_autorepeat; |
1640 | d->postponed_autorepeat = NULL; |
1641 | return ret; |
1642 | } |
1643 | } |
1644 | else // QEvent::KeyRelease |
1645 | { |
1646 | // Discard postponed "autorepeat key-release" events that didn't see |
1647 | // a keypress after them (e.g. due to QAccel) |
1648 | delete d->postponed_autorepeat; |
1649 | d->postponed_autorepeat = 0; |
1650 | |
1651 | if( !_ke->isAutoRepeat()) { |
1652 | return dispatchKeyEventHelper( _ke, false ); // keyup |
1653 | } |
1654 | else |
1655 | { |
1656 | d->postponed_autorepeat = new QKeyEvent( _ke->type(), _ke->key(), _ke->modifiers(), |
1657 | _ke->text(), _ke->isAutoRepeat(), _ke->count()); |
1658 | if( _ke->isAccepted()) |
1659 | d->postponed_autorepeat->accept(); |
1660 | else |
1661 | d->postponed_autorepeat->ignore(); |
1662 | return true; |
1663 | } |
1664 | } |
1665 | } |
1666 | |
1667 | // returns true if event should be swallowed |
1668 | bool KHTMLView::dispatchKeyEventHelper( QKeyEvent *_ke, bool keypress ) |
1669 | { |
1670 | DOM::NodeImpl* keyNode = m_part->xmlDocImpl()->focusNode(); |
1671 | if (keyNode) { |
1672 | return keyNode->dispatchKeyEvent(_ke, keypress); |
1673 | } else { // no focused node, send to document |
1674 | return m_part->xmlDocImpl()->dispatchKeyEvent(_ke, keypress); |
1675 | } |
1676 | } |
1677 | |
1678 | void KHTMLView::keyPressEvent( QKeyEvent *_ke ) |
1679 | { |
1680 | // If CTRL was hit, be prepared for access keys |
1681 | if (d->accessKeysEnabled && _ke->key() == Qt::Key_Control && !(_ke->modifiers() & ~Qt::ControlModifier) && !d->accessKeysActivated) |
1682 | { |
1683 | d->accessKeysPreActivate=true; |
1684 | _ke->accept(); |
1685 | return; |
1686 | } |
1687 | |
1688 | if (_ke->key() == Qt::Key_Shift && !(_ke->modifiers() & ~Qt::ShiftModifier)) |
1689 | d->scrollSuspendPreActivate=true; |
1690 | |
1691 | // accesskey handling needs to be done before dispatching, otherwise e.g. lineedits |
1692 | // may eat the event |
1693 | |
1694 | if (d->accessKeysEnabled && d->accessKeysActivated) |
1695 | { |
1696 | int state = ( _ke->modifiers() & ( Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | Qt::MetaModifier )); |
1697 | if ( state==0 || state==Qt::ShiftModifier ) { |
1698 | if (_ke->key() != Qt::Key_Shift) |
1699 | accessKeysTimeout(); |
1700 | handleAccessKey( _ke ); |
1701 | _ke->accept(); |
1702 | return; |
1703 | } |
1704 | accessKeysTimeout(); |
1705 | _ke->accept(); |
1706 | return; |
1707 | } |
1708 | |
1709 | if ( dispatchKeyEvent( _ke )) { |
1710 | // If either keydown or keypress was accepted by a widget, or canceled by JS, stop here. |
1711 | _ke->accept(); |
1712 | return; |
1713 | } |
1714 | |
1715 | int offs = (viewport()->height() < 30) ? viewport()->height() : 30; // ### ?? |
1716 | if (_ke->modifiers() & Qt::ShiftModifier) |
1717 | switch(_ke->key()) |
1718 | { |
1719 | case Qt::Key_Space: |
1720 | verticalScrollBar()->setValue( verticalScrollBar()->value() -viewport()->height() + offs ); |
1721 | if(d->scrollSuspended) |
1722 | d->newScrollTimer(this, 0); |
1723 | break; |
1724 | |
1725 | case Qt::Key_Down: |
1726 | case Qt::Key_J: |
1727 | d->adjustScroller(this, KHTMLViewPrivate::ScrollDown, KHTMLViewPrivate::ScrollUp); |
1728 | break; |
1729 | |
1730 | case Qt::Key_Up: |
1731 | case Qt::Key_K: |
1732 | d->adjustScroller(this, KHTMLViewPrivate::ScrollUp, KHTMLViewPrivate::ScrollDown); |
1733 | break; |
1734 | |
1735 | case Qt::Key_Left: |
1736 | case Qt::Key_H: |
1737 | d->adjustScroller(this, KHTMLViewPrivate::ScrollLeft, KHTMLViewPrivate::ScrollRight); |
1738 | break; |
1739 | |
1740 | case Qt::Key_Right: |
1741 | case Qt::Key_L: |
1742 | d->adjustScroller(this, KHTMLViewPrivate::ScrollRight, KHTMLViewPrivate::ScrollLeft); |
1743 | break; |
1744 | } |
1745 | else |
1746 | switch ( _ke->key() ) |
1747 | { |
1748 | case Qt::Key_Down: |
1749 | case Qt::Key_J: |
1750 | if (!d->scrollTimerId || d->scrollSuspended) |
1751 | verticalScrollBar()->setValue( verticalScrollBar()->value()+10 ); |
1752 | if (d->scrollTimerId) |
1753 | d->newScrollTimer(this, 0); |
1754 | break; |
1755 | |
1756 | case Qt::Key_Space: |
1757 | case Qt::Key_PageDown: |
1758 | d->shouldSmoothScroll = true; |
1759 | verticalScrollBar()->setValue( verticalScrollBar()->value() +viewport()->height() - offs ); |
1760 | if(d->scrollSuspended) |
1761 | d->newScrollTimer(this, 0); |
1762 | break; |
1763 | |
1764 | case Qt::Key_Up: |
1765 | case Qt::Key_K: |
1766 | if (!d->scrollTimerId || d->scrollSuspended) |
1767 | verticalScrollBar()->setValue( verticalScrollBar()->value()-10 ); |
1768 | if (d->scrollTimerId) |
1769 | d->newScrollTimer(this, 0); |
1770 | break; |
1771 | |
1772 | case Qt::Key_PageUp: |
1773 | d->shouldSmoothScroll = true; |
1774 | verticalScrollBar()->setValue( verticalScrollBar()->value() -viewport()->height() + offs ); |
1775 | if(d->scrollSuspended) |
1776 | d->newScrollTimer(this, 0); |
1777 | break; |
1778 | case Qt::Key_Right: |
1779 | case Qt::Key_L: |
1780 | if (!d->scrollTimerId || d->scrollSuspended) |
1781 | horizontalScrollBar()->setValue( horizontalScrollBar()->value()+10 ); |
1782 | if (d->scrollTimerId) |
1783 | d->newScrollTimer(this, 0); |
1784 | break; |
1785 | |
1786 | case Qt::Key_Left: |
1787 | case Qt::Key_H: |
1788 | if (!d->scrollTimerId || d->scrollSuspended) |
1789 | horizontalScrollBar()->setValue( horizontalScrollBar()->value()-10 ); |
1790 | if (d->scrollTimerId) |
1791 | d->newScrollTimer(this, 0); |
1792 | break; |
1793 | case Qt::Key_Enter: |
1794 | case Qt::Key_Return: |
1795 | // ### FIXME: |
1796 | // or even better to HTMLAnchorElementImpl::event() |
1797 | if (m_part->xmlDocImpl()) { |
1798 | NodeImpl *n = m_part->xmlDocImpl()->focusNode(); |
1799 | if (n) |
1800 | n->setActive(); |
1801 | } |
1802 | break; |
1803 | case Qt::Key_Home: |
1804 | verticalScrollBar()->setValue( 0 ); |
1805 | horizontalScrollBar()->setValue( 0 ); |
1806 | if(d->scrollSuspended) |
1807 | d->newScrollTimer(this, 0); |
1808 | break; |
1809 | case Qt::Key_End: |
1810 | verticalScrollBar()->setValue( contentsHeight() - visibleHeight() ); |
1811 | if(d->scrollSuspended) |
1812 | d->newScrollTimer(this, 0); |
1813 | break; |
1814 | case Qt::Key_Shift: |
1815 | // what are you doing here? |
1816 | _ke->ignore(); |
1817 | return; |
1818 | default: |
1819 | if (d->scrollTimerId) |
1820 | d->newScrollTimer(this, 0); |
1821 | _ke->ignore(); |
1822 | return; |
1823 | } |
1824 | |
1825 | _ke->accept(); |
1826 | } |
1827 | |
1828 | void KHTMLView::keyReleaseEvent(QKeyEvent *_ke) |
1829 | { |
1830 | if( d->scrollSuspendPreActivate && _ke->key() != Qt::Key_Shift ) |
1831 | d->scrollSuspendPreActivate = false; |
1832 | if( _ke->key() == Qt::Key_Shift && d->scrollSuspendPreActivate && !(_ke->modifiers() & Qt::ShiftModifier)) |
1833 | if (d->scrollTimerId) { |
1834 | d->scrollSuspended = !d->scrollSuspended; |
1835 | if (d->scrollSuspended) |
1836 | d->stopScrolling(); |
1837 | } |
1838 | |
1839 | if (d->accessKeysEnabled) |
1840 | { |
1841 | if (d->accessKeysPreActivate && _ke->key() != Qt::Key_Control) |
1842 | d->accessKeysPreActivate=false; |
1843 | if (d->accessKeysPreActivate && !(_ke->modifiers() & Qt::ControlModifier)) |
1844 | { |
1845 | displayAccessKeys(); |
1846 | m_part->setStatusBarText(i18n("Access Keys activated" ),KHTMLPart::BarOverrideText); |
1847 | d->accessKeysActivated = true; |
1848 | d->accessKeysPreActivate = false; |
1849 | _ke->accept(); |
1850 | return; |
1851 | } |
1852 | else if (d->accessKeysActivated) |
1853 | { |
1854 | accessKeysTimeout(); |
1855 | _ke->accept(); |
1856 | return; |
1857 | } |
1858 | } |
1859 | |
1860 | // Send keyup event |
1861 | if ( dispatchKeyEvent( _ke ) ) |
1862 | { |
1863 | _ke->accept(); |
1864 | return; |
1865 | } |
1866 | |
1867 | QScrollArea::keyReleaseEvent(_ke); |
1868 | } |
1869 | |
1870 | bool KHTMLView::focusNextPrevChild( bool next ) |
1871 | { |
1872 | // Now try to find the next child |
1873 | if (m_part->xmlDocImpl() && focusNextPrevNode(next)) |
1874 | { |
1875 | if (m_part->xmlDocImpl()->focusNode()) |
1876 | kDebug() << "focusNode.name: " |
1877 | << m_part->xmlDocImpl()->focusNode()->nodeName().string() << endl; |
1878 | return true; // focus node found |
1879 | } |
1880 | |
1881 | // If we get here, pass tabbing control up to the next/previous child in our parent |
1882 | d->pseudoFocusNode = KHTMLViewPrivate::PFNone; |
1883 | if (m_part->parentPart() && m_part->parentPart()->view()) |
1884 | return m_part->parentPart()->view()->focusNextPrevChild(next); |
1885 | |
1886 | return QWidget::focusNextPrevChild(next); |
1887 | } |
1888 | |
1889 | void KHTMLView::doAutoScroll() |
1890 | { |
1891 | QPoint pos = QCursor::pos(); |
1892 | QPoint off; |
1893 | KHTMLView* v = m_kwp->isRedirected() ? m_kwp->rootViewPos(off) : this; |
1894 | pos = v->viewport()->mapFromGlobal( pos ); |
1895 | pos -= off; |
1896 | int xm, ym; |
1897 | viewportToContents(pos.x(), pos.y(), xm, ym); // ### |
1898 | |
1899 | pos = QPoint(pos.x() - viewport()->x(), pos.y() - viewport()->y()); |
1900 | if ( (pos.y() < 0) || (pos.y() > visibleHeight()) || |
1901 | (pos.x() < 0) || (pos.x() > visibleWidth()) ) |
1902 | { |
1903 | ensureVisible( xm, ym, 0, 5 ); |
1904 | |
1905 | #ifndef KHTML_NO_SELECTION |
1906 | // extend the selection while scrolling |
1907 | DOM::Node innerNode; |
1908 | if (m_part->isExtendingSelection()) { |
1909 | RenderObject::NodeInfo renderInfo(true/*readonly*/, false/*active*/); |
1910 | m_part->xmlDocImpl()->renderer()->layer() |
1911 | ->nodeAtPoint(renderInfo, xm, ym); |
1912 | innerNode = renderInfo.innerNode(); |
1913 | }/*end if*/ |
1914 | |
1915 | if (innerNode.handle() && innerNode.handle()->renderer() |
1916 | && innerNode.handle()->renderer()->shouldSelect()) { |
1917 | m_part->extendSelectionTo(xm, ym, innerNode); |
1918 | }/*end if*/ |
1919 | #endif // KHTML_NO_SELECTION |
1920 | } |
1921 | } |
1922 | |
1923 | // KHTML defines its own stacking order for any object and thus takes |
1924 | // control of widget painting whenever it can. This is called "redirection". |
1925 | // |
1926 | // Redirected widgets are placed off screen. When they are declared as a child of our view (ChildPolished event), |
1927 | // an event filter is installed, so as to catch any paint event and translate them as update() of the view's main widget. |
1928 | // |
1929 | // Painting also happens spontaneously within widgets. In this case, the widget would update() parts of itself. |
1930 | // While this ordinarily results in a paintEvent being schedduled, it is not the case with off screen widgets. |
1931 | // Thus update() is monitored by using the mechanism that deffers any update call happening during a paint event, |
1932 | // transforming it into a posted UpdateLater event. Hence the need to set Qt::WA_WState_InPaintEvent on redirected widgets. |
1933 | // |
1934 | // Once the UpdateLater event has been received, Qt::WA_WState_InPaintEvent is removed and the process continues |
1935 | // with the update of the corresponding rect on the view. That in turn will make our painting subsystem render() |
1936 | // the widget at the correct stacking position. |
1937 | // |
1938 | // For non-redirected (e.g. external) widgets, z-order is honoured through masking. cf.RenderLayer::updateWidgetMasks |
1939 | |
1940 | static void handleWidget(QWidget* w, KHTMLView* view, bool recurse=true) |
1941 | { |
1942 | if (w->isWindow()) |
1943 | return; |
1944 | |
1945 | if (!qobject_cast<QFrame*>(w)) |
1946 | w->setAttribute( Qt::WA_NoSystemBackground ); |
1947 | |
1948 | w->setAttribute(Qt::WA_WState_InPaintEvent); |
1949 | |
1950 | if (!(w->objectName() == "KLineEditButton" )) |
1951 | w->setAttribute(Qt::WA_OpaquePaintEvent); |
1952 | |
1953 | w->installEventFilter(view); |
1954 | |
1955 | if (!recurse) |
1956 | return; |
1957 | if (qobject_cast<KHTMLView*>(w)) { |
1958 | handleWidget(static_cast<KHTMLView*>(w)->widget(), view, false); |
1959 | handleWidget(static_cast<KHTMLView*>(w)->horizontalScrollBar(), view, false); |
1960 | handleWidget(static_cast<KHTMLView*>(w)->verticalScrollBar(), view, false); |
1961 | return; |
1962 | } |
1963 | |
1964 | QObjectList children = w->children(); |
1965 | foreach (QObject* object, children) { |
1966 | QWidget *widget = qobject_cast<QWidget*>(object); |
1967 | if (widget) |
1968 | handleWidget(widget, view); |
1969 | } |
1970 | } |
1971 | |
1972 | class KHTMLBackingStoreHackWidget : public QWidget |
1973 | { |
1974 | public: |
1975 | void publicEvent(QEvent *e) |
1976 | { |
1977 | QWidget::event(e); |
1978 | } |
1979 | }; |
1980 | |
1981 | bool KHTMLView::viewportEvent ( QEvent * e ) |
1982 | { |
1983 | switch (e->type()) { |
1984 | // those must not be dispatched to the specialized handlers |
1985 | // as widgetEvent() already took care of that |
1986 | case QEvent::MouseButtonPress: |
1987 | case QEvent::MouseButtonRelease: |
1988 | case QEvent::MouseButtonDblClick: |
1989 | case QEvent::MouseMove: |
1990 | #ifndef QT_NO_WHEELEVENT |
1991 | case QEvent::Wheel: |
1992 | #endif |
1993 | case QEvent::ContextMenu: |
1994 | case QEvent::DragEnter: |
1995 | case QEvent::DragMove: |
1996 | case QEvent::DragLeave: |
1997 | case QEvent::Drop: |
1998 | return false; |
1999 | default: |
2000 | break; |
2001 | } |
2002 | return QScrollArea::viewportEvent(e); |
2003 | } |
2004 | |
2005 | static void setInPaintEventFlag(QWidget* w, bool b = true, bool recurse=true) |
2006 | { |
2007 | w->setAttribute(Qt::WA_WState_InPaintEvent, b); |
2008 | |
2009 | if (!recurse) |
2010 | return; |
2011 | if (qobject_cast<KHTMLView*>(w)) { |
2012 | setInPaintEventFlag(static_cast<KHTMLView*>(w)->widget(), b, false); |
2013 | setInPaintEventFlag(static_cast<KHTMLView*>(w)->horizontalScrollBar(), b, false); |
2014 | setInPaintEventFlag(static_cast<KHTMLView*>(w)->verticalScrollBar(), b, false); |
2015 | return; |
2016 | } |
2017 | |
2018 | foreach(QObject* cw, w->children()) { |
2019 | if (cw->isWidgetType() && ! static_cast<QWidget*>(cw)->isWindow() |
2020 | && !(static_cast<QWidget*>(cw)->windowModality() & Qt::ApplicationModal)) { |
2021 | setInPaintEventFlag(static_cast<QWidget*>(cw), b); |
2022 | } |
2023 | } |
2024 | } |
2025 | |
2026 | bool KHTMLView::eventFilter(QObject *o, QEvent *e) |
2027 | { |
2028 | if ( e->type() == QEvent::ShortcutOverride ) { |
2029 | QKeyEvent* ke = (QKeyEvent*) e; |
2030 | if (m_part->isEditable() || m_part->isCaretMode() |
2031 | || (m_part->xmlDocImpl() && m_part->xmlDocImpl()->focusNode() |
2032 | && m_part->xmlDocImpl()->focusNode()->isContentEditable())) { |
2033 | if ( (ke->modifiers() & Qt::ControlModifier) || (ke->modifiers() & Qt::ShiftModifier) ) { |
2034 | switch ( ke->key() ) { |
2035 | case Qt::Key_Left: |
2036 | case Qt::Key_Right: |
2037 | case Qt::Key_Up: |
2038 | case Qt::Key_Down: |
2039 | case Qt::Key_Home: |
2040 | case Qt::Key_End: |
2041 | ke->accept(); |
2042 | return true; |
2043 | default: |
2044 | break; |
2045 | } |
2046 | } |
2047 | } |
2048 | } |
2049 | |
2050 | if ( e->type() == QEvent::Leave ) { |
2051 | if ( d->cursorIconWidget ) |
2052 | d->cursorIconWidget->hide(); |
2053 | m_part->resetHoverText(); |
2054 | } |
2055 | |
2056 | QWidget *view = widget(); |
2057 | if (o == view) { |
2058 | if (widgetEvent(e)) |
2059 | return true; |
2060 | else if (e->type() == QEvent::Resize) { |
2061 | updateScrollBars(); |
2062 | return false; |
2063 | } |
2064 | } else if (o->isWidgetType()) { |
2065 | QWidget *v = static_cast<QWidget *>(o); |
2066 | QWidget *c = v; |
2067 | while (v && v != view) { |
2068 | c = v; |
2069 | v = v->parentWidget(); |
2070 | } |
2071 | KHTMLWidget* k = dynamic_cast<KHTMLWidget*>(c); |
2072 | if (v && k && k->m_kwp->isRedirected()) { |
2073 | bool block = false; |
2074 | bool isUpdate = false; |
2075 | QWidget *w = static_cast<QWidget *>(o); |
2076 | switch(e->type()) { |
2077 | case QEvent::UpdateRequest: { |
2078 | // implicitly call qt_syncBackingStore(w) |
2079 | static_cast<KHTMLBackingStoreHackWidget *>(w)->publicEvent(e); |
2080 | block = true; |
2081 | break; |
2082 | } |
2083 | case QEvent::UpdateLater: |
2084 | isUpdate = true; |
2085 | // no break; |
2086 | case QEvent::Paint: |
2087 | if (!allowWidgetPaintEvents) { |
2088 | // eat the event. Like this we can control exactly when the widget |
2089 | // gets repainted. |
2090 | block = true; |
2091 | int x = 0, y = 0; |
2092 | QWidget *v = w; |
2093 | while (v && v->parentWidget() != view) { |
2094 | x += v->x(); |
2095 | y += v->y(); |
2096 | v = v->parentWidget(); |
2097 | } |
2098 | |
2099 | QPoint ap = k->m_kwp->absolutePos(); |
2100 | x += ap.x(); |
2101 | y += ap.y(); |
2102 | |
2103 | QRect pr = isUpdate ? static_cast<QUpdateLaterEvent*>(e)->region().boundingRect() : static_cast<QPaintEvent*>(e)->rect(); |
2104 | bool asap = !d->contentsMoving && qobject_cast<QAbstractScrollArea*>(c); |
2105 | |
2106 | if (isUpdate) { |
2107 | setInPaintEventFlag(w, false); |
2108 | if (asap) |
2109 | w->repaint(static_cast<QUpdateLaterEvent*>(e)->region()); |
2110 | else |
2111 | w->update(static_cast<QUpdateLaterEvent*>(e)->region()); |
2112 | setInPaintEventFlag(w); |
2113 | } |
2114 | |
2115 | // QScrollView needs fast repaints |
2116 | if ( asap && !isUpdate && !d->painting && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer() && |
2117 | !static_cast<khtml::RenderCanvas *>(m_part->xmlDocImpl()->renderer())->needsLayout() ) { |
2118 | repaintContents(x + pr.x(), y + pr.y(), |
2119 | pr.width(), pr.height()+1); // ### investigate that +1 (shows up when |
2120 | // updating e.g a textarea's blinking cursor) |
2121 | } else if (!d->painting) { |
2122 | scheduleRepaint(x + pr.x(), y + pr.y(), |
2123 | pr.width(), pr.height()+1, asap); |
2124 | } |
2125 | } |
2126 | break; |
2127 | case QEvent::MouseMove: |
2128 | case QEvent::MouseButtonPress: |
2129 | case QEvent::MouseButtonRelease: |
2130 | case QEvent::MouseButtonDblClick: { |
2131 | |
2132 | if (0 && w->parentWidget() == view && !qobject_cast<QScrollBar*>(w) && !::qobject_cast<QScrollBar *>(w)) { |
2133 | QMouseEvent *me = static_cast<QMouseEvent *>(e); |
2134 | QPoint pt = w->mapTo( view, me->pos()); |
2135 | QMouseEvent me2(me->type(), pt, me->button(), me->buttons(), me->modifiers()); |
2136 | |
2137 | if (e->type() == QEvent::MouseMove) |
2138 | mouseMoveEvent(&me2); |
2139 | else if(e->type() == QEvent::MouseButtonPress) |
2140 | mousePressEvent(&me2); |
2141 | else if(e->type() == QEvent::MouseButtonRelease) |
2142 | mouseReleaseEvent(&me2); |
2143 | else |
2144 | mouseDoubleClickEvent(&me2); |
2145 | block = true; |
2146 | } |
2147 | break; |
2148 | } |
2149 | case QEvent::KeyPress: |
2150 | case QEvent::KeyRelease: |
2151 | if (w->parentWidget() == view && !qobject_cast<QScrollBar*>(w)) { |
2152 | QKeyEvent *ke = static_cast<QKeyEvent *>(e); |
2153 | if (e->type() == QEvent::KeyPress) { |
2154 | keyPressEvent(ke); |
2155 | ke->accept(); |
2156 | } else{ |
2157 | keyReleaseEvent(ke); |
2158 | ke->accept(); |
2159 | } |
2160 | block = true; |
2161 | } |
2162 | |
2163 | if (qobject_cast<KUrlRequester*>(w->parentWidget()) && |
2164 | e->type() == QEvent::KeyPress) { |
2165 | // Since keypress events on the upload widget will |
2166 | // be forwarded to the lineedit anyway, |
2167 | // block the original copy at this level to prevent |
2168 | // double-emissions of events it doesn't accept |
2169 | e->ignore(); |
2170 | block = true; |
2171 | } |
2172 | |
2173 | break; |
2174 | case QEvent::FocusIn: |
2175 | case QEvent::FocusOut: { |
2176 | QPoint dummy; |
2177 | KHTMLView* root = m_kwp->rootViewPos(dummy); |
2178 | if (!root) |
2179 | root = this; |
2180 | block = static_cast<QFocusEvent*>(e)->reason() != Qt::MouseFocusReason || root->underMouse(); |
2181 | break; |
2182 | } |
2183 | default: |
2184 | break; |
2185 | } |
2186 | if (block) { |
2187 | //qDebug("eating event"); |
2188 | return true; |
2189 | } |
2190 | } |
2191 | } |
2192 | |
2193 | // kDebug(6000) <<"passing event on to sv event filter object=" << o->className() << " event=" << e->type(); |
2194 | return QScrollArea::eventFilter(o, e); |
2195 | } |
2196 | |
2197 | bool KHTMLView::widgetEvent(QEvent* e) |
2198 | { |
2199 | switch (e->type()) { |
2200 | case QEvent::MouseButtonPress: |
2201 | case QEvent::MouseButtonRelease: |
2202 | case QEvent::MouseButtonDblClick: |
2203 | case QEvent::MouseMove: |
2204 | case QEvent::Paint: |
2205 | #ifndef QT_NO_WHEELEVENT |
2206 | case QEvent::Wheel: |
2207 | #endif |
2208 | case QEvent::ContextMenu: |
2209 | case QEvent::DragEnter: |
2210 | case QEvent::DragMove: |
2211 | case QEvent::DragLeave: |
2212 | case QEvent::Drop: |
2213 | return QFrame::event(e); |
2214 | case QEvent::ChildPolished: { |
2215 | // we need to install an event filter on all children of the widget() to |
2216 | // be able to get correct stacking of children within the document. |
2217 | QObject *c = static_cast<QChildEvent *>(e)->child(); |
2218 | if (c->isWidgetType()) { |
2219 | QWidget *w = static_cast<QWidget *>(c); |
2220 | // don't install the event filter on toplevels |
2221 | if (!(w->windowFlags() & Qt::Window) && !(w->windowModality() & Qt::ApplicationModal)) { |
2222 | KHTMLWidget* k = dynamic_cast<KHTMLWidget*>(w); |
2223 | if (k && k->m_kwp->isRedirected()) { |
2224 | w->unsetCursor(); |
2225 | handleWidget(w, this); |
2226 | } |
2227 | } |
2228 | } |
2229 | break; |
2230 | } |
2231 | case QEvent::Move: { |
2232 | if (static_cast<QMoveEvent*>(e)->pos() != QPoint(0,0)) { |
2233 | widget()->move(0,0); |
2234 | updateScrollBars(); |
2235 | return true; |
2236 | } |
2237 | break; |
2238 | } |
2239 | default: |
2240 | break; |
2241 | } |
2242 | return false; |
2243 | } |
2244 | |
2245 | bool KHTMLView::hasLayoutPending() |
2246 | { |
2247 | return d->layoutTimerId && !d->firstLayoutPending; |
2248 | } |
2249 | |
2250 | DOM::NodeImpl *KHTMLView::nodeUnderMouse() const |
2251 | { |
2252 | return d->underMouse; |
2253 | } |
2254 | |
2255 | DOM::NodeImpl *KHTMLView::nonSharedNodeUnderMouse() const |
2256 | { |
2257 | return d->underMouseNonShared; |
2258 | } |
2259 | |
2260 | bool KHTMLView::scrollTo(const QRect &bounds) |
2261 | { |
2262 | d->scrollingSelf = true; // so scroll events get ignored |
2263 | |
2264 | int x, y, xe, ye; |
2265 | x = bounds.left(); |
2266 | y = bounds.top(); |
2267 | xe = bounds.right(); |
2268 | ye = bounds.bottom(); |
2269 | |
2270 | //kDebug(6000)<<"scrolling coords: x="<<x<<" y="<<y<<" width="<<xe-x<<" height="<<ye-y; |
2271 | |
2272 | int deltax; |
2273 | int deltay; |
2274 | |
2275 | int curHeight = visibleHeight(); |
2276 | int curWidth = visibleWidth(); |
2277 | |
2278 | if (ye-y>curHeight-d->borderY) |
2279 | ye = y + curHeight - d->borderY; |
2280 | |
2281 | if (xe-x>curWidth-d->borderX) |
2282 | xe = x + curWidth - d->borderX; |
2283 | |
2284 | // is xpos of target left of the view's border? |
2285 | if (x < contentsX() + d->borderX ) |
2286 | deltax = x - contentsX() - d->borderX; |
2287 | // is xpos of target right of the view's right border? |
2288 | else if (xe + d->borderX > contentsX() + curWidth) |
2289 | deltax = xe + d->borderX - ( contentsX() + curWidth ); |
2290 | else |
2291 | deltax = 0; |
2292 | |
2293 | // is ypos of target above upper border? |
2294 | if (y < contentsY() + d->borderY) |
2295 | deltay = y - contentsY() - d->borderY; |
2296 | // is ypos of target below lower border? |
2297 | else if (ye + d->borderY > contentsY() + curHeight) |
2298 | deltay = ye + d->borderY - ( contentsY() + curHeight ); |
2299 | else |
2300 | deltay = 0; |
2301 | |
2302 | int maxx = curWidth-d->borderX; |
2303 | int maxy = curHeight-d->borderY; |
2304 | |
2305 | int scrollX, scrollY; |
2306 | |
2307 | scrollX = deltax > 0 ? (deltax > maxx ? maxx : deltax) : deltax == 0 ? 0 : (deltax>-maxx ? deltax : -maxx); |
2308 | scrollY = deltay > 0 ? (deltay > maxy ? maxy : deltay) : deltay == 0 ? 0 : (deltay>-maxy ? deltay : -maxy); |
2309 | |
2310 | if (contentsX() + scrollX < 0) |
2311 | scrollX = -contentsX(); |
2312 | else if (contentsWidth() - visibleWidth() - contentsX() < scrollX) |
2313 | scrollX = contentsWidth() - visibleWidth() - contentsX(); |
2314 | |
2315 | if (contentsY() + scrollY < 0) |
2316 | scrollY = -contentsY(); |
2317 | else if (contentsHeight() - visibleHeight() - contentsY() < scrollY) |
2318 | scrollY = contentsHeight() - visibleHeight() - contentsY(); |
2319 | |
2320 | horizontalScrollBar()->setValue( horizontalScrollBar()->value()+scrollX ); |
2321 | verticalScrollBar()->setValue( verticalScrollBar()->value()+scrollY ); |
2322 | |
2323 | d->scrollingSelf = false; |
2324 | |
2325 | if ( (abs(deltax)<=maxx) && (abs(deltay)<=maxy) ) |
2326 | return true; |
2327 | else return false; |
2328 | |
2329 | } |
2330 | |
2331 | bool KHTMLView::focusNextPrevNode(bool next) |
2332 | { |
2333 | // Sets the focus node of the document to be the node after (or if |
2334 | // next is false, before) the current focus node. Only nodes that |
2335 | // are selectable (i.e. for which isFocusable() returns true) are |
2336 | // taken into account, and the order used is that specified in the |
2337 | // HTML spec (see DocumentImpl::nextFocusNode() and |
2338 | // DocumentImpl::previousFocusNode() for details). |
2339 | |
2340 | DocumentImpl *doc = m_part->xmlDocImpl(); |
2341 | NodeImpl *oldFocusNode = doc->focusNode(); |
2342 | |
2343 | // See whether we're in the middle of a detach, or hiding of the |
2344 | // widget. In this case, we will just clear focus, being careful not to emit events |
2345 | // or update rendering. Doing this also prevents the code below from going bonkers with |
2346 | // oldFocusNode not actually being focusable, etc. |
2347 | if (oldFocusNode) { |
2348 | if ((oldFocusNode->renderer() && !oldFocusNode->renderer()->parent()) |
2349 | || !oldFocusNode->isTabFocusable()) { |
2350 | doc->quietResetFocus(); |
2351 | return true; |
2352 | } |
2353 | } |
2354 | |
2355 | #if 1 |
2356 | // If the user has scrolled the document, then instead of picking |
2357 | // the next focusable node in the document, use the first one that |
2358 | // is within the visible area (if possible). |
2359 | if (d->scrollBarMoved) |
2360 | { |
2361 | NodeImpl *toFocus; |
2362 | if (next) |
2363 | toFocus = doc->nextFocusNode(oldFocusNode); |
2364 | else |
2365 | toFocus = doc->previousFocusNode(oldFocusNode); |
2366 | |
2367 | if (!toFocus && oldFocusNode) { |
2368 | if (next) |
2369 | toFocus = doc->nextFocusNode(NULL); |
2370 | else |
2371 | toFocus = doc->previousFocusNode(NULL); |
2372 | } |
2373 | |
2374 | while (toFocus && toFocus != oldFocusNode) |
2375 | { |
2376 | |
2377 | QRect focusNodeRect = toFocus->getRect(); |
2378 | if ((focusNodeRect.left() > contentsX()) && (focusNodeRect.right() < contentsX() + visibleWidth()) && |
2379 | (focusNodeRect.top() > contentsY()) && (focusNodeRect.bottom() < contentsY() + visibleHeight())) { |
2380 | { |
2381 | QRect r = toFocus->getRect(); |
2382 | ensureVisible( r.right(), r.bottom()); |
2383 | ensureVisible( r.left(), r.top()); |
2384 | d->scrollBarMoved = false; |
2385 | d->tabMovePending = false; |
2386 | d->lastTabbingDirection = next; |
2387 | d->pseudoFocusNode = KHTMLViewPrivate::PFNone; |
2388 | m_part->xmlDocImpl()->setFocusNode(toFocus); |
2389 | Node guard(toFocus); |
2390 | if (!toFocus->hasOneRef() ) |
2391 | { |
2392 | emit m_part->nodeActivated(Node(toFocus)); |
2393 | } |
2394 | return true; |
2395 | } |
2396 | } |
2397 | if (next) |
2398 | toFocus = doc->nextFocusNode(toFocus); |
2399 | else |
2400 | toFocus = doc->previousFocusNode(toFocus); |
2401 | |
2402 | if (!toFocus && oldFocusNode) |
2403 | { |
2404 | if (next) |
2405 | { |
2406 | toFocus = doc->nextFocusNode(NULL); |
2407 | } |
2408 | else |
2409 | { |
2410 | toFocus = doc->previousFocusNode(NULL); |
2411 | } |
2412 | } |
2413 | } |
2414 | |
2415 | d->scrollBarMoved = false; |
2416 | } |
2417 | #endif |
2418 | |
2419 | if (!oldFocusNode && d->pseudoFocusNode == KHTMLViewPrivate::PFNone) |
2420 | { |
2421 | ensureVisible(contentsX(), next?0:contentsHeight()); |
2422 | d->scrollBarMoved = false; |
2423 | d->pseudoFocusNode = next?KHTMLViewPrivate::PFTop:KHTMLViewPrivate::PFBottom; |
2424 | return true; |
2425 | } |
2426 | |
2427 | NodeImpl *newFocusNode = NULL; |
2428 | |
2429 | if (d->tabMovePending && next != d->lastTabbingDirection) |
2430 | { |
2431 | //kDebug ( 6000 ) << " tab move pending and tabbing direction changed!\n"; |
2432 | newFocusNode = oldFocusNode; |
2433 | } |
2434 | else if (next) |
2435 | { |
2436 | if (oldFocusNode || d->pseudoFocusNode == KHTMLViewPrivate::PFTop ) |
2437 | newFocusNode = doc->nextFocusNode(oldFocusNode); |
2438 | } |
2439 | else |
2440 | { |
2441 | if (oldFocusNode || d->pseudoFocusNode == KHTMLViewPrivate::PFBottom ) |
2442 | newFocusNode = doc->previousFocusNode(oldFocusNode); |
2443 | } |
2444 | |
2445 | bool targetVisible = false; |
2446 | if (!newFocusNode) |
2447 | { |
2448 | if ( next ) |
2449 | { |
2450 | targetVisible = scrollTo(QRect(contentsX()+visibleWidth()/2,contentsHeight()-d->borderY,0,0)); |
2451 | } |
2452 | else |
2453 | { |
2454 | targetVisible = scrollTo(QRect(contentsX()+visibleWidth()/2,d->borderY,0,0)); |
2455 | } |
2456 | } |
2457 | else |
2458 | { |
2459 | // if it's an editable element, activate the caret |
2460 | if (!m_part->isCaretMode() && newFocusNode->isContentEditable()) { |
2461 | kDebug(6200) << "show caret! fn: " << newFocusNode->nodeName().string() << endl; |
2462 | m_part->clearCaretRectIfNeeded(); |
2463 | m_part->d->editor_context.m_selection.moveTo(Position(newFocusNode, 0L)); |
2464 | m_part->setCaretVisible(true); |
2465 | } else { |
2466 | m_part->setCaretVisible(false); |
2467 | kDebug(6200) << "hide caret! fn: " << newFocusNode->nodeName().string() << endl; |
2468 | } |
2469 | m_part->notifySelectionChanged(); |
2470 | |
2471 | targetVisible = scrollTo(newFocusNode->getRect()); |
2472 | } |
2473 | |
2474 | if (targetVisible) |
2475 | { |
2476 | //kDebug ( 6000 ) << " target reached.\n"; |
2477 | d->tabMovePending = false; |
2478 | |
2479 | m_part->xmlDocImpl()->setFocusNode(newFocusNode); |
2480 | if (newFocusNode) |
2481 | { |
2482 | Node guard(newFocusNode); |
2483 | if (!newFocusNode->hasOneRef() ) |
2484 | { |
2485 | emit m_part->nodeActivated(Node(newFocusNode)); |
2486 | } |
2487 | return true; |
2488 | } |
2489 | else |
2490 | { |
2491 | d->pseudoFocusNode = next?KHTMLViewPrivate::PFBottom:KHTMLViewPrivate::PFTop; |
2492 | return false; |
2493 | } |
2494 | } |
2495 | else |
2496 | { |
2497 | if (!d->tabMovePending) |
2498 | d->lastTabbingDirection = next; |
2499 | d->tabMovePending = true; |
2500 | return true; |
2501 | } |
2502 | } |
2503 | |
2504 | void KHTMLView::displayAccessKeys() |
2505 | { |
2506 | QVector< QChar > taken; |
2507 | displayAccessKeys( NULL, this, taken, false ); |
2508 | displayAccessKeys( NULL, this, taken, true ); |
2509 | } |
2510 | |
2511 | void KHTMLView::displayAccessKeys( KHTMLView* caller, KHTMLView* origview, QVector< QChar >& taken, bool use_fallbacks ) |
2512 | { |
2513 | QMap< ElementImpl*, QChar > fallbacks; |
2514 | if( use_fallbacks ) |
2515 | fallbacks = buildFallbackAccessKeys(); |
2516 | for( NodeImpl* n = m_part->xmlDocImpl(); n != NULL; n = n->traverseNextNode()) { |
2517 | if( n->isElementNode()) { |
2518 | ElementImpl* en = static_cast< ElementImpl* >( n ); |
2519 | DOMString s = en->getAttribute( ATTR_ACCESSKEY ); |
2520 | QString accesskey; |
2521 | if( s.length() == 1 ) { |
2522 | QChar a = s.string()[ 0 ].toUpper(); |
2523 | if( qFind( taken.begin(), taken.end(), a ) == taken.end()) // !contains |
2524 | accesskey = a; |
2525 | } |
2526 | if( accesskey.isNull() && fallbacks.contains( en )) { |
2527 | QChar a = fallbacks[ en ].toUpper(); |
2528 | if( qFind( taken.begin(), taken.end(), a ) == taken.end()) // !contains |
2529 | accesskey = QString( "<qt><i>" ) + a + "</i></qt>" ; |
2530 | } |
2531 | if( !accesskey.isNull()) { |
2532 | QRect rec=en->getRect(); |
2533 | QLabel *lab=new QLabel(accesskey,widget()); |
2534 | lab->setAttribute(Qt::WA_DeleteOnClose); |
2535 | lab->setObjectName("KHTMLAccessKey" ); |
2536 | connect( origview, SIGNAL(hideAccessKeys()), lab, SLOT(close()) ); |
2537 | connect( this, SIGNAL(repaintAccessKeys()), lab, SLOT(repaint())); |
2538 | lab->setPalette(QToolTip::palette()); |
2539 | lab->setLineWidth(2); |
2540 | lab->setFrameStyle(QFrame::Box | QFrame::Plain); |
2541 | lab->setMargin(3); |
2542 | lab->adjustSize(); |
2543 | lab->setParent( widget() ); |
2544 | lab->setAutoFillBackground(true); |
2545 | lab->move( |
2546 | qMin(rec.left()+rec.width()/2 - contentsX(), contentsWidth() - lab->width()), |
2547 | qMin(rec.top()+rec.height()/2 - contentsY(), contentsHeight() - lab->height())); |
2548 | lab->show(); |
2549 | taken.append( accesskey[ 0 ] ); |
2550 | } |
2551 | } |
2552 | } |
2553 | if( use_fallbacks ) |
2554 | return; |
2555 | |
2556 | QList<KParts::ReadOnlyPart*> frames = m_part->frames(); |
2557 | foreach( KParts::ReadOnlyPart* cur, frames ) { |
2558 | if( !qobject_cast<KHTMLPart*>(cur) ) |
2559 | continue; |
2560 | KHTMLPart* part = static_cast< KHTMLPart* >( cur ); |
2561 | if( part->view() && part->view() != caller ) |
2562 | part->view()->displayAccessKeys( this, origview, taken, use_fallbacks ); |
2563 | } |
2564 | |
2565 | // pass up to the parent |
2566 | if (m_part->parentPart() && m_part->parentPart()->view() |
2567 | && m_part->parentPart()->view() != caller) |
2568 | m_part->parentPart()->view()->displayAccessKeys( this, origview, taken, use_fallbacks ); |
2569 | } |
2570 | |
2571 | bool KHTMLView::isScrollingFromMouseWheel() const |
2572 | { |
2573 | return d->scrollingFromWheel != QPoint(-1,-1); |
2574 | } |
2575 | |
2576 | void KHTMLView::accessKeysTimeout() |
2577 | { |
2578 | d->accessKeysActivated=false; |
2579 | d->accessKeysPreActivate = false; |
2580 | m_part->setStatusBarText(QString(), KHTMLPart::BarOverrideText); |
2581 | emit hideAccessKeys(); |
2582 | } |
2583 | |
2584 | // Handling of the HTML accesskey attribute. |
2585 | bool KHTMLView::handleAccessKey( const QKeyEvent* ev ) |
2586 | { |
2587 | // Qt interprets the keyevent also with the modifiers, and ev->text() matches that, |
2588 | // but this code must act as if the modifiers weren't pressed |
2589 | QChar c; |
2590 | if( ev->key() >= Qt::Key_A && ev->key() <= Qt::Key_Z ) |
2591 | c = 'A' + ev->key() - Qt::Key_A; |
2592 | else if( ev->key() >= Qt::Key_0 && ev->key() <= Qt::Key_9 ) |
2593 | c = '0' + ev->key() - Qt::Key_0; |
2594 | else { |
2595 | // TODO fake XKeyEvent and XLookupString ? |
2596 | // This below seems to work e.g. for eacute though. |
2597 | if( ev->text().length() == 1 ) |
2598 | c = ev->text()[ 0 ]; |
2599 | } |
2600 | if( c.isNull()) |
2601 | return false; |
2602 | return focusNodeWithAccessKey( c ); |
2603 | } |
2604 | |
2605 | bool KHTMLView::focusNodeWithAccessKey( QChar c, KHTMLView* caller ) |
2606 | { |
2607 | DocumentImpl *doc = m_part->xmlDocImpl(); |
2608 | if( !doc ) |
2609 | return false; |
2610 | ElementImpl* node = doc->findAccessKeyElement( c ); |
2611 | if( !node ) { |
2612 | QList<KParts::ReadOnlyPart*> frames = m_part->frames(); |
2613 | foreach( KParts::ReadOnlyPart* cur, frames ) { |
2614 | if( !qobject_cast<KHTMLPart*>(cur) ) |
2615 | continue; |
2616 | KHTMLPart* part = static_cast< KHTMLPart* >( cur ); |
2617 | if( part->view() && part->view() != caller |
2618 | && part->view()->focusNodeWithAccessKey( c, this )) |
2619 | return true; |
2620 | } |
2621 | // pass up to the parent |
2622 | if (m_part->parentPart() && m_part->parentPart()->view() |
2623 | && m_part->parentPart()->view() != caller |
2624 | && m_part->parentPart()->view()->focusNodeWithAccessKey( c, this )) |
2625 | return true; |
2626 | if( caller == NULL ) { // the active frame (where the accesskey was pressed) |
2627 | const QMap< ElementImpl*, QChar > fallbacks = buildFallbackAccessKeys(); |
2628 | for( QMap< ElementImpl*, QChar >::ConstIterator it = fallbacks.begin(); |
2629 | it != fallbacks.end(); |
2630 | ++it ) |
2631 | if( *it == c ) { |
2632 | node = it.key(); |
2633 | break; |
2634 | } |
2635 | } |
2636 | if( node == NULL ) |
2637 | return false; |
2638 | } |
2639 | |
2640 | // Scroll the view as necessary to ensure that the new focus node is visible |
2641 | |
2642 | QRect r = node->getRect(); |
2643 | ensureVisible( r.right(), r.bottom()); |
2644 | ensureVisible( r.left(), r.top()); |
2645 | |
2646 | Node guard( node ); |
2647 | if( node->isFocusable()) { |
2648 | if (node->id()==ID_LABEL) { |
2649 | // if Accesskey is a label, give focus to the label's referrer. |
2650 | node=static_cast<ElementImpl *>(static_cast< HTMLLabelElementImpl* >( node )->getFormElement()); |
2651 | if (!node) return true; |
2652 | guard = node; |
2653 | } |
2654 | // Set focus node on the document |
2655 | m_part->xmlDocImpl()->setFocusNode(node); |
2656 | |
2657 | if( node != NULL && node->hasOneRef()) // deleted, only held by guard |
2658 | return true; |
2659 | emit m_part->nodeActivated(Node(node)); |
2660 | if( node != NULL && node->hasOneRef()) |
2661 | return true; |
2662 | } |
2663 | |
2664 | switch( node->id()) { |
2665 | case ID_A: |
2666 | static_cast< HTMLAnchorElementImpl* >( node )->click(); |
2667 | break; |
2668 | case ID_INPUT: |
2669 | static_cast< HTMLInputElementImpl* >( node )->click(); |
2670 | break; |
2671 | case ID_BUTTON: |
2672 | static_cast< HTMLButtonElementImpl* >( node )->click(); |
2673 | break; |
2674 | case ID_AREA: |
2675 | static_cast< HTMLAreaElementImpl* >( node )->click(); |
2676 | break; |
2677 | case ID_TEXTAREA: |
2678 | break; // just focusing it is enough |
2679 | case ID_LEGEND: |
2680 | // TODO |
2681 | break; |
2682 | } |
2683 | return true; |
2684 | } |
2685 | |
2686 | static QString getElementText( NodeImpl* start, bool after ) |
2687 | { |
2688 | QString ret; // nextSibling(), to go after e.g. </select> |
2689 | for( NodeImpl* n = after ? start->nextSibling() : start->traversePreviousNode(); |
2690 | n != NULL; |
2691 | n = after ? n->traverseNextNode() : n->traversePreviousNode()) { |
2692 | if( n->isTextNode()) { |
2693 | if( after ) |
2694 | ret += static_cast< TextImpl* >( n )->toString().string(); |
2695 | else |
2696 | ret.prepend( static_cast< TextImpl* >( n )->toString().string()); |
2697 | } else { |
2698 | switch( n->id()) { |
2699 | case ID_A: |
2700 | case ID_FONT: |
2701 | case ID_TT: |
2702 | case ID_U: |
2703 | case ID_B: |
2704 | case ID_I: |
2705 | case ID_S: |
2706 | case ID_STRIKE: |
2707 | case ID_BIG: |
2708 | case ID_SMALL: |
2709 | case ID_EM: |
2710 | case ID_STRONG: |
2711 | case ID_DFN: |
2712 | case ID_CODE: |
2713 | case ID_SAMP: |
2714 | case ID_KBD: |
2715 | case ID_VAR: |
2716 | case ID_CITE: |
2717 | case ID_ABBR: |
2718 | case ID_ACRONYM: |
2719 | case ID_SUB: |
2720 | case ID_SUP: |
2721 | case ID_SPAN: |
2722 | case ID_NOBR: |
2723 | case ID_WBR: |
2724 | break; |
2725 | case ID_TD: |
2726 | if( ret.trimmed().isEmpty()) |
2727 | break; |
2728 | // fall through |
2729 | default: |
2730 | return ret.simplified(); |
2731 | } |
2732 | } |
2733 | } |
2734 | return ret.simplified(); |
2735 | } |
2736 | |
2737 | static QMap< NodeImpl*, QString > buildLabels( NodeImpl* start ) |
2738 | { |
2739 | QMap< NodeImpl*, QString > ret; |
2740 | for( NodeImpl* n = start; |
2741 | n != NULL; |
2742 | n = n->traverseNextNode()) { |
2743 | if( n->id() == ID_LABEL ) { |
2744 | HTMLLabelElementImpl* label = static_cast< HTMLLabelElementImpl* >( n ); |
2745 | NodeImpl* labelfor = label->getFormElement(); |
2746 | if( labelfor ) |
2747 | ret[ labelfor ] = label->innerText().string().simplified(); |
2748 | } |
2749 | } |
2750 | return ret; |
2751 | } |
2752 | |
2753 | namespace khtml { |
2754 | struct AccessKeyData { |
2755 | ElementImpl* element; |
2756 | QString text; |
2757 | QString url; |
2758 | int priority; // 10(highest) - 0(lowest) |
2759 | }; |
2760 | } |
2761 | |
2762 | QMap< ElementImpl*, QChar > KHTMLView::buildFallbackAccessKeys() const |
2763 | { |
2764 | // build a list of all possible candidate elements that could use an accesskey |
2765 | QLinkedList< AccessKeyData > data; // Note: this has to be a list type that keep iterators valid |
2766 | // when other entries are removed |
2767 | QMap< NodeImpl*, QString > labels = buildLabels( m_part->xmlDocImpl()); |
2768 | QMap< QString, QChar > hrefs; |
2769 | |
2770 | for( NodeImpl* n = m_part->xmlDocImpl(); |
2771 | n != NULL; |
2772 | n = n->traverseNextNode()) { |
2773 | if( n->isElementNode()) { |
2774 | ElementImpl* element = static_cast< ElementImpl* >( n ); |
2775 | if( element->renderer() == NULL ) |
2776 | continue; // not visible |
2777 | QString text; |
2778 | QString url; |
2779 | int priority = 0; |
2780 | bool ignore = false; |
2781 | bool text_after = false; |
2782 | bool text_before = false; |
2783 | switch( element->id()) { |
2784 | case ID_A: |
2785 | url = khtml::parseURL(element->getAttribute(ATTR_HREF)).string(); |
2786 | if( url.isEmpty()) // doesn't have href, it's only an anchor |
2787 | continue; |
2788 | text = static_cast< HTMLElementImpl* >( element )->innerText().string().simplified(); |
2789 | priority = 2; |
2790 | break; |
2791 | case ID_INPUT: { |
2792 | HTMLInputElementImpl* in = static_cast< HTMLInputElementImpl* >( element ); |
2793 | switch( in->inputType()) { |
2794 | case HTMLInputElementImpl::SUBMIT: |
2795 | text = in->value().string(); |
2796 | if( text.isEmpty()) |
2797 | text = i18n( "Submit" ); |
2798 | priority = 7; |
2799 | break; |
2800 | case HTMLInputElementImpl::IMAGE: |
2801 | text = in->altText().string(); |
2802 | priority = 7; |
2803 | break; |
2804 | case HTMLInputElementImpl::BUTTON: |
2805 | text = in->value().string(); |
2806 | priority = 5; |
2807 | break; |
2808 | case HTMLInputElementImpl::RESET: |
2809 | text = in->value().string(); |
2810 | if( text.isEmpty()) |
2811 | text = i18n( "Reset" ); |
2812 | priority = 5; |
2813 | break; |
2814 | case HTMLInputElementImpl::HIDDEN: |
2815 | ignore = true; |
2816 | break; |
2817 | case HTMLInputElementImpl::CHECKBOX: |
2818 | case HTMLInputElementImpl::RADIO: |
2819 | text_after = true; |
2820 | priority = 5; |
2821 | break; |
2822 | case HTMLInputElementImpl::TEXT: |
2823 | case HTMLInputElementImpl::PASSWORD: |
2824 | case HTMLInputElementImpl::FILE: |
2825 | text_before = true; |
2826 | priority = 5; |
2827 | break; |
2828 | default: |
2829 | priority = 5; |
2830 | break; |
2831 | } |
2832 | break; |
2833 | } |
2834 | case ID_BUTTON: |
2835 | text = static_cast< HTMLElementImpl* >( element )->innerText().string().simplified(); |
2836 | switch( static_cast< HTMLButtonElementImpl* >( element )->buttonType()) { |
2837 | case HTMLButtonElementImpl::SUBMIT: |
2838 | if( text.isEmpty()) |
2839 | text = i18n( "Submit" ); |
2840 | priority = 7; |
2841 | break; |
2842 | case HTMLButtonElementImpl::RESET: |
2843 | if( text.isEmpty()) |
2844 | text = i18n( "Reset" ); |
2845 | priority = 5; |
2846 | break; |
2847 | default: |
2848 | priority = 5; |
2849 | break; |
2850 | } |
2851 | break; |
2852 | case ID_SELECT: // these don't have accesskey attribute, but quick access may be handy |
2853 | text_before = true; |
2854 | text_after = true; |
2855 | priority = 5; |
2856 | break; |
2857 | case ID_FRAME: |
2858 | ignore = true; |
2859 | break; |
2860 | default: |
2861 | ignore = !element->isFocusable(); |
2862 | priority = 2; |
2863 | break; |
2864 | } |
2865 | if( ignore ) |
2866 | continue; |
2867 | |
2868 | // build map of manually assigned accesskeys and their targets |
2869 | DOMString akey = element->getAttribute( ATTR_ACCESSKEY ); |
2870 | if( akey.length() == 1 ) { |
2871 | hrefs[url] = akey.string()[ 0 ].toUpper(); |
2872 | continue; // has accesskey set, ignore |
2873 | } |
2874 | if( text.isNull() && labels.contains( element )) |
2875 | text = labels[ element ]; |
2876 | if( text.isNull() && text_before ) |
2877 | text = getElementText( element, false ); |
2878 | if( text.isNull() && text_after ) |
2879 | text = getElementText( element, true ); |
2880 | text = text.trimmed(); |
2881 | // increase priority of items which have explicitly specified accesskeys in the config |
2882 | const QList< QPair< QString, QChar > > priorities |
2883 | = m_part->settings()->fallbackAccessKeysAssignments(); |
2884 | for( QList< QPair< QString, QChar > >::ConstIterator it = priorities.begin(); |
2885 | it != priorities.end(); |
2886 | ++it ) { |
2887 | if( text == (*it).first ) |
2888 | priority = 10; |
2889 | } |
2890 | AccessKeyData tmp = { element, text, url, priority }; |
2891 | data.append( tmp ); |
2892 | } |
2893 | } |
2894 | |
2895 | QList< QChar > keys; |
2896 | for( char c = 'A'; c <= 'Z'; ++c ) |
2897 | keys << c; |
2898 | for( char c = '0'; c <= '9'; ++c ) |
2899 | keys << c; |
2900 | for( NodeImpl* n = m_part->xmlDocImpl(); |
2901 | n != NULL; |
2902 | n = n->traverseNextNode()) { |
2903 | if( n->isElementNode()) { |
2904 | ElementImpl* en = static_cast< ElementImpl* >( n ); |
2905 | DOMString s = en->getAttribute( ATTR_ACCESSKEY ); |
2906 | if( s.length() == 1 ) { |
2907 | QChar c = s.string()[ 0 ].toUpper(); |
2908 | keys.removeAll( c ); // remove manually assigned accesskeys |
2909 | } |
2910 | } |
2911 | } |
2912 | |
2913 | QMap< ElementImpl*, QChar > ret; |
2914 | for( int priority = 10; priority >= 0; --priority ) { |
2915 | for( QLinkedList< AccessKeyData >::Iterator it = data.begin(); |
2916 | it != data.end(); |
2917 | ) { |
2918 | if( (*it).priority != priority ) { |
2919 | ++it; |
2920 | continue; |
2921 | } |
2922 | if( keys.isEmpty()) |
2923 | break; |
2924 | QString text = (*it).text; |
2925 | QChar key; |
2926 | const QString url = (*it).url; |
2927 | // an identical link already has an accesskey assigned |
2928 | if( hrefs.contains( url ) ) { |
2929 | it = data.erase( it ); |
2930 | continue; |
2931 | } |
2932 | if( !text.isEmpty()) { |
2933 | const QList< QPair< QString, QChar > > priorities |
2934 | = m_part->settings()->fallbackAccessKeysAssignments(); |
2935 | for( QList< QPair< QString, QChar > >::ConstIterator it = priorities.begin(); |
2936 | it != priorities.end(); |
2937 | ++it ) |
2938 | if( text == (*it).first && keys.contains( (*it).second )) { |
2939 | key = (*it).second; |
2940 | break; |
2941 | } |
2942 | } |
2943 | // try first to select the first character as the accesskey, |
2944 | // then first character of the following words, |
2945 | // and then simply the first free character |
2946 | if( key.isNull() && !text.isEmpty()) { |
2947 | const QStringList words = text.split( ' ' ); |
2948 | for( QStringList::ConstIterator it = words.begin(); |
2949 | it != words.end(); |
2950 | ++it ) { |
2951 | if( keys.contains( (*it)[ 0 ].toUpper())) { |
2952 | key = (*it)[ 0 ].toUpper(); |
2953 | break; |
2954 | } |
2955 | } |
2956 | } |
2957 | if( key.isNull() && !text.isEmpty()) { |
2958 | for( int i = 0; i < text.length(); ++i ) { |
2959 | if( keys.contains( text[ i ].toUpper())) { |
2960 | key = text[ i ].toUpper(); |
2961 | break; |
2962 | } |
2963 | } |
2964 | } |
2965 | if( key.isNull()) |
2966 | key = keys.front(); |
2967 | ret[ (*it).element ] = key; |
2968 | keys.removeAll( key ); |
2969 | it = data.erase( it ); |
2970 | // assign the same accesskey also to other elements pointing to the same url |
2971 | if( !url.isEmpty() && !url.startsWith( "javascript:" , Qt::CaseInsensitive )) { |
2972 | for( QLinkedList< AccessKeyData >::Iterator it2 = data.begin(); |
2973 | it2 != data.end(); |
2974 | ) { |
2975 | if( (*it2).url == url ) { |
2976 | ret[ (*it2).element ] = key; |
2977 | if( it == it2 ) |
2978 | ++it; |
2979 | it2 = data.erase( it2 ); |
2980 | } else |
2981 | ++it2; |
2982 | } |
2983 | } |
2984 | } |
2985 | } |
2986 | return ret; |
2987 | } |
2988 | |
2989 | void KHTMLView::setMediaType( const QString &medium ) |
2990 | { |
2991 | m_medium = medium; |
2992 | } |
2993 | |
2994 | QString KHTMLView::mediaType() const |
2995 | { |
2996 | return m_medium; |
2997 | } |
2998 | |
2999 | bool KHTMLView::pagedMode() const |
3000 | { |
3001 | return d->paged; |
3002 | } |
3003 | |
3004 | void KHTMLView::setWidgetVisible(RenderWidget* w, bool vis) |
3005 | { |
3006 | if (vis) { |
3007 | d->visibleWidgets.insert(w, w->widget()); |
3008 | } |
3009 | else |
3010 | d->visibleWidgets.remove(w); |
3011 | } |
3012 | |
3013 | bool KHTMLView::needsFullRepaint() const |
3014 | { |
3015 | return d->needsFullRepaint; |
3016 | } |
3017 | |
3018 | namespace { |
3019 | class QPointerDeleter |
3020 | { |
3021 | public: |
3022 | explicit QPointerDeleter(QObject* o) : obj(o) {} |
3023 | ~QPointerDeleter() { delete obj; } |
3024 | private: |
3025 | const QPointer<QObject> obj; |
3026 | }; |
3027 | } |
3028 | |
3029 | void KHTMLView::print(bool quick) |
3030 | { |
3031 | if(!m_part->xmlDocImpl()) return; |
3032 | khtml::RenderCanvas *root = static_cast<khtml::RenderCanvas *>(m_part->xmlDocImpl()->renderer()); |
3033 | if(!root) return; |
3034 | |
3035 | QPointer<KHTMLPrintSettings> printSettings(new KHTMLPrintSettings); //XXX: doesn't save settings between prints like this |
3036 | const QPointerDeleter settingsDeleter(printSettings); //the printdialog takes ownership of the settings widget, thus this workaround to avoid double deletion |
3037 | QPrinter printer; |
3038 | QPointer<QPrintDialog> dialog = KdePrint::createPrintDialog(&printer, KdePrint::SystemSelectsPages, QList<QWidget*>() << printSettings.data(), this); |
3039 | |
3040 | const QPointerDeleter dialogDeleter(dialog); |
3041 | |
3042 | QString docname = m_part->xmlDocImpl()->URL().prettyUrl(); |
3043 | if ( !docname.isEmpty() ) |
3044 | docname = KStringHandler::csqueeze(docname, 80); |
3045 | |
3046 | if(quick || (dialog->exec() && dialog)) { /*'this' and thus dialog might have been deleted while exec()!*/ |
3047 | viewport()->setCursor( Qt::WaitCursor ); // only viewport(), no QApplication::, otherwise we get the busy cursor in kdeprint's dialogs |
3048 | // set up KPrinter |
3049 | printer.setFullPage(false); |
3050 | printer.setCreator(QString("KDE %1.%2.%3 HTML Library" ).arg(KDE_VERSION_MAJOR).arg(KDE_VERSION_MINOR).arg(KDE_VERSION_RELEASE)); |
3051 | printer.setDocName(docname); |
3052 | |
3053 | QPainter *p = new QPainter; |
3054 | p->begin( &printer ); |
3055 | khtml::setPrintPainter( p ); |
3056 | |
3057 | m_part->xmlDocImpl()->setPaintDevice( &printer ); |
3058 | QString oldMediaType = mediaType(); |
3059 | setMediaType( "print" ); |
3060 | // We ignore margin settings for html and body when printing |
3061 | // and use the default margins from the print-system |
3062 | // (In Qt 3.0.x the default margins are hardcoded in Qt) |
3063 | m_part->xmlDocImpl()->setPrintStyleSheet( printSettings->printFriendly() ? |
3064 | "* { background-image: none !important;" |
3065 | " background-color: white !important;" |
3066 | " color: black !important; }" |
3067 | "body { margin: 0px !important; }" |
3068 | "html { margin: 0px !important; }" : |
3069 | "body { margin: 0px !important; }" |
3070 | "html { margin: 0px !important; }" |
3071 | ); |
3072 | |
3073 | kDebug(6000) << "printing: physical page width = " << printer.width() |
3074 | << " height = " << printer.height() << endl; |
3075 | root->setStaticMode(true); |
3076 | root->setPagedMode(true); |
3077 | root->setWidth(printer.width()); |
3078 | // root->setHeight(printer.height()); |
3079 | root->setPageTop(0); |
3080 | root->setPageBottom(0); |
3081 | d->paged = true; |
3082 | |
3083 | m_part->xmlDocImpl()->styleSelector()->computeFontSizes(printer.logicalDpiY(), 100); |
3084 | m_part->xmlDocImpl()->updateStyleSelector(); |
3085 | root->setPrintImages(printSettings->printImages()); |
3086 | root->makePageBreakAvoidBlocks(); |
3087 | |
3088 | root->setNeedsLayoutAndMinMaxRecalc(); |
3089 | root->layout(); |
3090 | |
3091 | // check sizes ask for action.. (scale or clip) |
3092 | |
3093 | bool = printSettings->printHeader(); |
3094 | |
3095 | int = 0; |
3096 | QFont ("Sans Serif" , 8); |
3097 | |
3098 | QString = KGlobal::locale()->formatDate(QDate::currentDate(),KLocale::ShortDate); |
3099 | QString = docname; |
3100 | QString ; |
3101 | |
3102 | if (printHeader) |
3103 | { |
3104 | p->setFont(headerFont); |
3105 | headerHeight = (p->fontMetrics().lineSpacing() * 3) / 2; |
3106 | } |
3107 | |
3108 | // ok. now print the pages. |
3109 | kDebug(6000) << "printing: html page width = " << root->docWidth() |
3110 | << " height = " << root->docHeight() << endl; |
3111 | kDebug(6000) << "printing: margins left = " << printer.pageRect().left() - printer.paperRect().left() |
3112 | << " top = " << printer.pageRect().top() - printer.paperRect().top() << endl; |
3113 | kDebug(6000) << "printing: paper width = " << printer.width() |
3114 | << " height = " << printer.height() << endl; |
3115 | // if the width is too large to fit on the paper we just scale |
3116 | // the whole thing. |
3117 | int pageWidth = printer.width(); |
3118 | int pageHeight = printer.height(); |
3119 | p->setClipRect(0,0, pageWidth, pageHeight); |
3120 | |
3121 | pageHeight -= headerHeight; |
3122 | |
3123 | #ifndef QT_NO_TRANSFORMATIONS |
3124 | bool scalePage = false; |
3125 | double scale = 0.0; |
3126 | if(root->docWidth() > printer.width()) { |
3127 | scalePage = true; |
3128 | scale = ((double) printer.width())/((double) root->docWidth()); |
3129 | pageHeight = (int) (pageHeight/scale); |
3130 | pageWidth = (int) (pageWidth/scale); |
3131 | headerHeight = (int) (headerHeight/scale); |
3132 | } |
3133 | #endif |
3134 | kDebug(6000) << "printing: scaled html width = " << pageWidth |
3135 | << " height = " << pageHeight << endl; |
3136 | |
3137 | root->setHeight(pageHeight); |
3138 | root->setPageBottom(pageHeight); |
3139 | root->setNeedsLayout(true); |
3140 | root->layoutIfNeeded(); |
3141 | // m_part->slotDebugRenderTree(); |
3142 | |
3143 | // Squeeze header to make it it on the page. |
3144 | if (printHeader) |
3145 | { |
3146 | int available_width = printer.width() - 10 - |
3147 | 2 * qMax(p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerLeft).width(), |
3148 | p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerRight).width()); |
3149 | if (available_width < 150) |
3150 | available_width = 150; |
3151 | int mid_width; |
3152 | int squeeze = 120; |
3153 | do { |
3154 | headerMid = KStringHandler::csqueeze(docname, squeeze); |
3155 | mid_width = p->boundingRect(0, 0, printer.width(), p->fontMetrics().lineSpacing(), Qt::AlignLeft, headerMid).width(); |
3156 | squeeze -= 10; |
3157 | } while (mid_width > available_width); |
3158 | } |
3159 | |
3160 | int top = 0; |
3161 | int bottom = 0; |
3162 | int page = 1; |
3163 | while(top < root->docHeight()) { |
3164 | if(top > 0) printer.newPage(); |
3165 | #ifndef QT_NO_TRANSFORMATIONS |
3166 | if (scalePage) |
3167 | p->scale(scale, scale); |
3168 | #endif |
3169 | p->save(); |
3170 | p->setClipRect(0, 0, pageWidth, headerHeight); |
3171 | if (printHeader) |
3172 | { |
3173 | int dy = p->fontMetrics().lineSpacing(); |
3174 | p->setPen(Qt::black); |
3175 | p->setFont(headerFont); |
3176 | |
3177 | headerRight = QString("#%1" ).arg(page); |
3178 | |
3179 | p->drawText(0, 0, printer.width(), dy, Qt::AlignLeft, headerLeft); |
3180 | p->drawText(0, 0, printer.width(), dy, Qt::AlignHCenter, headerMid); |
3181 | p->drawText(0, 0, printer.width(), dy, Qt::AlignRight, headerRight); |
3182 | } |
3183 | |
3184 | p->restore(); |
3185 | p->translate(0, headerHeight-top); |
3186 | |
3187 | bottom = top+pageHeight; |
3188 | |
3189 | root->setPageTop(top); |
3190 | root->setPageBottom(bottom); |
3191 | root->setPageNumber(page); |
3192 | |
3193 | root->layer()->paint(p, QRect(0, top, pageWidth, pageHeight)); |
3194 | kDebug(6000) << "printed: page " << page <<" bottom At = " << bottom; |
3195 | |
3196 | top = bottom; |
3197 | p->resetTransform(); |
3198 | page++; |
3199 | } |
3200 | |
3201 | p->end(); |
3202 | delete p; |
3203 | |
3204 | // and now reset the layout to the usual one... |
3205 | root->setPagedMode(false); |
3206 | root->setStaticMode(false); |
3207 | d->paged = false; |
3208 | khtml::setPrintPainter( 0 ); |
3209 | setMediaType( oldMediaType ); |
3210 | m_part->xmlDocImpl()->setPaintDevice( this ); |
3211 | m_part->xmlDocImpl()->styleSelector()->computeFontSizes(m_part->xmlDocImpl()->logicalDpiY(), m_part->fontScaleFactor()); |
3212 | m_part->xmlDocImpl()->updateStyleSelector(); |
3213 | viewport()->unsetCursor(); |
3214 | } |
3215 | } |
3216 | |
3217 | void KHTMLView::slotPaletteChanged() |
3218 | { |
3219 | if(!m_part->xmlDocImpl()) return; |
3220 | DOM::DocumentImpl *document = m_part->xmlDocImpl(); |
3221 | if (!document->isHTMLDocument()) return; |
3222 | khtml::RenderCanvas *root = static_cast<khtml::RenderCanvas *>(document->renderer()); |
3223 | if(!root) return; |
3224 | root->style()->resetPalette(); |
3225 | NodeImpl *body = static_cast<HTMLDocumentImpl*>(document)->body(); |
3226 | if(!body) return; |
3227 | body->setChanged(true); |
3228 | body->recalcStyle( NodeImpl::Force ); |
3229 | } |
3230 | |
3231 | void KHTMLView::paint(QPainter *p, const QRect &rc, int yOff, bool *more) |
3232 | { |
3233 | if(!m_part->xmlDocImpl()) return; |
3234 | khtml::RenderCanvas *root = static_cast<khtml::RenderCanvas *>(m_part->xmlDocImpl()->renderer()); |
3235 | if(!root) return; |
3236 | #ifdef SPEED_DEBUG |
3237 | d->firstRepaintPending = false; |
3238 | #endif |
3239 | |
3240 | QPaintDevice* opd = m_part->xmlDocImpl()->paintDevice(); |
3241 | m_part->xmlDocImpl()->setPaintDevice(p->device()); |
3242 | root->setPagedMode(true); |
3243 | root->setStaticMode(true); |
3244 | root->setWidth(rc.width()); |
3245 | |
3246 | // save() |
3247 | QRegion creg = p->clipRegion(); |
3248 | QTransform t = p->worldTransform(); |
3249 | QRect w = p->window(); |
3250 | QRect v = p->viewport(); |
3251 | bool vte = p->viewTransformEnabled(); |
3252 | bool wme = p->worldMatrixEnabled(); |
3253 | |
3254 | p->setClipRect(rc); |
3255 | p->translate(rc.left(), rc.top()); |
3256 | double scale = ((double) rc.width()/(double) root->docWidth()); |
3257 | int height = (int) ((double) rc.height() / scale); |
3258 | #ifndef QT_NO_TRANSFORMATIONS |
3259 | p->scale(scale, scale); |
3260 | #endif |
3261 | root->setPageTop(yOff); |
3262 | root->setPageBottom(yOff+height); |
3263 | |
3264 | root->layer()->paint(p, QRect(0, yOff, root->docWidth(), height)); |
3265 | if (more) |
3266 | *more = yOff + height < root->docHeight(); |
3267 | |
3268 | // restore() |
3269 | p->setWorldTransform(t); |
3270 | p->setWindow(w); |
3271 | p->setViewport(v); |
3272 | p->setViewTransformEnabled( vte ); |
3273 | p->setWorldMatrixEnabled( wme ); |
3274 | if (!creg.isEmpty()) |
3275 | p->setClipRegion( creg ); |
3276 | else |
3277 | p->setClipRegion(QRegion(), Qt::NoClip); |
3278 | |
3279 | root->setPagedMode(false); |
3280 | root->setStaticMode(false); |
3281 | m_part->xmlDocImpl()->setPaintDevice( opd ); |
3282 | } |
3283 | |
3284 | void KHTMLView::render(QPainter* p, const QRect& r, const QPoint& off) |
3285 | { |
3286 | #ifdef SPEED_DEBUG |
3287 | d->firstRepaintPending = false; |
3288 | #endif |
3289 | QRect clip(off.x()+r.x(), off.y()+r.y(),r.width(),r.height()); |
3290 | if(!m_part || !m_part->xmlDocImpl() || !m_part->xmlDocImpl()->renderer()) { |
3291 | p->fillRect(clip, palette().brush(QPalette::Active, QPalette::Base)); |
3292 | return; |
3293 | } |
3294 | QPaintDevice* opd = m_part->xmlDocImpl()->paintDevice(); |
3295 | m_part->xmlDocImpl()->setPaintDevice(p->device()); |
3296 | |
3297 | // save() |
3298 | QRegion creg = p->clipRegion(); |
3299 | QTransform t = p->worldTransform(); |
3300 | QRect w = p->window(); |
3301 | QRect v = p->viewport(); |
3302 | bool vte = p->viewTransformEnabled(); |
3303 | bool wme = p->worldMatrixEnabled(); |
3304 | |
3305 | p->setClipRect(clip); |
3306 | QRect rect = r.translated(contentsX(),contentsY()); |
3307 | p->translate(off.x()-contentsX(), off.y()-contentsY()); |
3308 | |
3309 | m_part->xmlDocImpl()->renderer()->layer()->paint(p, rect); |
3310 | |
3311 | // restore() |
3312 | p->setWorldTransform(t); |
3313 | p->setWindow(w); |
3314 | p->setViewport(v); |
3315 | p->setViewTransformEnabled( vte ); |
3316 | p->setWorldMatrixEnabled( wme ); |
3317 | if (!creg.isEmpty()) |
3318 | p->setClipRegion( creg ); |
3319 | else |
3320 | p->setClipRegion(QRegion(), Qt::NoClip); |
3321 | |
3322 | m_part->xmlDocImpl()->setPaintDevice( opd ); |
3323 | } |
3324 | |
3325 | void KHTMLView::setHasStaticBackground(bool partial) |
3326 | { |
3327 | // full static iframe is irreversible for now |
3328 | if (d->staticWidget == KHTMLViewPrivate::SBFull && m_kwp->isRedirected()) |
3329 | return; |
3330 | |
3331 | d->staticWidget = partial ? |
3332 | KHTMLViewPrivate::SBPartial : KHTMLViewPrivate::SBFull; |
3333 | } |
3334 | |
3335 | void KHTMLView::setHasNormalBackground() |
3336 | { |
3337 | // full static iframe is irreversible for now |
3338 | if (d->staticWidget == KHTMLViewPrivate::SBFull && m_kwp->isRedirected()) |
3339 | return; |
3340 | |
3341 | d->staticWidget = KHTMLViewPrivate::SBNone; |
3342 | } |
3343 | |
3344 | void KHTMLView::addStaticObject(bool fixed) |
3345 | { |
3346 | if (fixed) |
3347 | d->fixedObjectsCount++; |
3348 | else |
3349 | d->staticObjectsCount++; |
3350 | |
3351 | setHasStaticBackground( true /*partial*/ ); |
3352 | } |
3353 | |
3354 | void KHTMLView::removeStaticObject(bool fixed) |
3355 | { |
3356 | if (fixed) |
3357 | d->fixedObjectsCount--; |
3358 | else |
3359 | d->staticObjectsCount--; |
3360 | |
3361 | assert( d->fixedObjectsCount >= 0 && d->staticObjectsCount >= 0 ); |
3362 | |
3363 | if (!d->staticObjectsCount && !d->fixedObjectsCount) |
3364 | setHasNormalBackground(); |
3365 | else |
3366 | setHasStaticBackground( true /*partial*/ ); |
3367 | } |
3368 | |
3369 | void KHTMLView::setVerticalScrollBarPolicy( Qt::ScrollBarPolicy policy ) |
3370 | { |
3371 | #ifndef KHTML_NO_SCROLLBARS |
3372 | d->vpolicy = policy; |
3373 | QScrollArea::setVerticalScrollBarPolicy(policy); |
3374 | #else |
3375 | Q_UNUSED( policy ); |
3376 | #endif |
3377 | } |
3378 | |
3379 | void KHTMLView::setHorizontalScrollBarPolicy( Qt::ScrollBarPolicy policy ) |
3380 | { |
3381 | #ifndef KHTML_NO_SCROLLBARS |
3382 | d->hpolicy = policy; |
3383 | QScrollArea::setHorizontalScrollBarPolicy(policy); |
3384 | #else |
3385 | Q_UNUSED( policy ); |
3386 | #endif |
3387 | } |
3388 | |
3389 | void KHTMLView::restoreScrollBar() |
3390 | { |
3391 | int ow = visibleWidth(); |
3392 | QScrollArea::setVerticalScrollBarPolicy(d->vpolicy); |
3393 | if (visibleWidth() != ow) |
3394 | layout(); |
3395 | d->prevScrollbarVisible = verticalScrollBar()->isVisible(); |
3396 | } |
3397 | |
3398 | QStringList KHTMLView::formCompletionItems(const QString &name) const |
3399 | { |
3400 | if (!m_part->settings()->isFormCompletionEnabled()) |
3401 | return QStringList(); |
3402 | if (!d->formCompletions) |
3403 | d->formCompletions = new KConfig(KStandardDirs::locateLocal("data" , "khtml/formcompletions" )); |
3404 | return d->formCompletions->group("" ).readEntry(name, QStringList()); |
3405 | } |
3406 | |
3407 | void KHTMLView::clearCompletionHistory(const QString& name) |
3408 | { |
3409 | if (!d->formCompletions) |
3410 | { |
3411 | d->formCompletions = new KConfig(KStandardDirs::locateLocal("data" , "khtml/formcompletions" )); |
3412 | } |
3413 | d->formCompletions->group("" ).writeEntry(name, "" ); |
3414 | d->formCompletions->sync(); |
3415 | } |
3416 | |
3417 | void KHTMLView::addFormCompletionItem(const QString &name, const QString &value) |
3418 | { |
3419 | if (!m_part->settings()->isFormCompletionEnabled()) |
3420 | return; |
3421 | // don't store values that are all numbers or just numbers with |
3422 | // dashes or spaces as those are likely credit card numbers or |
3423 | // something similar |
3424 | bool cc_number(true); |
3425 | for ( int i = 0; i < value.length(); ++i) |
3426 | { |
3427 | QChar c(value[i]); |
3428 | if (!c.isNumber() && c != '-' && !c.isSpace()) |
3429 | { |
3430 | cc_number = false; |
3431 | break; |
3432 | } |
3433 | } |
3434 | if (cc_number) |
3435 | return; |
3436 | QStringList items = formCompletionItems(name); |
3437 | if (!items.contains(value)) |
3438 | items.prepend(value); |
3439 | while ((int)items.count() > m_part->settings()->maxFormCompletionItems()) |
3440 | items.erase(items.isEmpty() ? items.end() : --items.end()); |
3441 | d->formCompletions->group("" ).writeEntry(name, items); |
3442 | } |
3443 | |
3444 | void KHTMLView::addNonPasswordStorableSite(const QString& host) |
3445 | { |
3446 | if (!d->formCompletions) { |
3447 | d->formCompletions = new KConfig(KStandardDirs::locateLocal("data" , "khtml/formcompletions" )); |
3448 | } |
3449 | |
3450 | KConfigGroup cg( d->formCompletions, "NonPasswordStorableSites" ); |
3451 | QStringList sites = cg.readEntry("Sites" , QStringList()); |
3452 | sites.append(host); |
3453 | cg.writeEntry("Sites" , sites); |
3454 | cg.sync(); |
3455 | } |
3456 | |
3457 | |
3458 | void KHTMLView::delNonPasswordStorableSite(const QString& host) |
3459 | { |
3460 | if (!d->formCompletions) { |
3461 | d->formCompletions = new KConfig(KStandardDirs::locateLocal("data" , "khtml/formcompletions" )); |
3462 | } |
3463 | |
3464 | KConfigGroup cg( d->formCompletions, "NonPasswordStorableSites" ); |
3465 | QStringList sites = cg.readEntry("Sites" , QStringList()); |
3466 | sites.removeOne(host); |
3467 | cg.writeEntry("Sites" , sites); |
3468 | cg.sync(); |
3469 | } |
3470 | |
3471 | bool KHTMLView::nonPasswordStorableSite(const QString& host) const |
3472 | { |
3473 | if (!d->formCompletions) { |
3474 | d->formCompletions = new KConfig(KStandardDirs::locateLocal("data" , "khtml/formcompletions" )); |
3475 | } |
3476 | QStringList sites = d->formCompletions->group( "NonPasswordStorableSites" ).readEntry("Sites" , QStringList()); |
3477 | return (sites.indexOf(host) != -1); |
3478 | } |
3479 | |
3480 | // returns true if event should be swallowed |
3481 | bool KHTMLView::dispatchMouseEvent(int eventId, DOM::NodeImpl *targetNode, |
3482 | DOM::NodeImpl *targetNodeNonShared, bool cancelable, |
3483 | int detail,QMouseEvent *_mouse, bool setUnder, |
3484 | int mouseEventType, int orient) |
3485 | { |
3486 | // if the target node is a text node, dispatch on the parent node - rdar://4196646 (and #76948) |
3487 | if (targetNode && targetNode->isTextNode()) |
3488 | targetNode = targetNode->parentNode(); |
3489 | |
3490 | if (d->underMouse) |
3491 | d->underMouse->deref(); |
3492 | d->underMouse = targetNode; |
3493 | if (d->underMouse) |
3494 | d->underMouse->ref(); |
3495 | |
3496 | if (d->underMouseNonShared) |
3497 | d->underMouseNonShared->deref(); |
3498 | d->underMouseNonShared = targetNodeNonShared; |
3499 | if (d->underMouseNonShared) |
3500 | d->underMouseNonShared->ref(); |
3501 | |
3502 | bool isWheelEvent = (mouseEventType == DOM::NodeImpl::MouseWheel); |
3503 | |
3504 | int exceptioncode = 0; |
3505 | int pageX = _mouse->x(); |
3506 | int pageY = _mouse->y(); |
3507 | revertTransforms(pageX, pageY); |
3508 | int clientX = pageX - contentsX(); |
3509 | int clientY = pageY - contentsY(); |
3510 | int screenX = _mouse->globalX(); |
3511 | int screenY = _mouse->globalY(); |
3512 | int button = -1; |
3513 | switch (_mouse->button()) { |
3514 | case Qt::LeftButton: |
3515 | button = 0; |
3516 | break; |
3517 | case Qt::MidButton: |
3518 | button = 1; |
3519 | break; |
3520 | case Qt::RightButton: |
3521 | button = 2; |
3522 | break; |
3523 | default: |
3524 | break; |
3525 | } |
3526 | if (d->accessKeysEnabled && d->accessKeysPreActivate && button!=-1) |
3527 | d->accessKeysPreActivate=false; |
3528 | |
3529 | bool ctrlKey = (_mouse->modifiers() & Qt::ControlModifier); |
3530 | bool altKey = (_mouse->modifiers() & Qt::AltModifier); |
3531 | bool shiftKey = (_mouse->modifiers() & Qt::ShiftModifier); |
3532 | bool metaKey = (_mouse->modifiers() & Qt::MetaModifier); |
3533 | |
3534 | // mouseout/mouseover |
3535 | if (setUnder && d->oldUnderMouse != targetNode) { |
3536 | if (d->oldUnderMouse && d->oldUnderMouse->document() != m_part->xmlDocImpl()) { |
3537 | d->oldUnderMouse->deref(); |
3538 | d->oldUnderMouse = 0; |
3539 | } |
3540 | // send mouseout event to the old node |
3541 | if (d->oldUnderMouse) { |
3542 | // send mouseout event to the old node |
3543 | MouseEventImpl *me = new MouseEventImpl(EventImpl::MOUSEOUT_EVENT, |
3544 | true,true,m_part->xmlDocImpl()->defaultView(), |
3545 | 0,screenX,screenY,clientX,clientY,pageX, pageY, |
3546 | ctrlKey,altKey,shiftKey,metaKey, |
3547 | button,targetNode); |
3548 | me->ref(); |
3549 | d->oldUnderMouse->dispatchEvent(me,exceptioncode,true); |
3550 | me->deref(); |
3551 | } |
3552 | // send mouseover event to the new node |
3553 | if (targetNode) { |
3554 | MouseEventImpl *me = new MouseEventImpl(EventImpl::MOUSEOVER_EVENT, |
3555 | true,true,m_part->xmlDocImpl()->defaultView(), |
3556 | 0,screenX,screenY,clientX,clientY,pageX, pageY, |
3557 | ctrlKey,altKey,shiftKey,metaKey, |
3558 | button,d->oldUnderMouse); |
3559 | |
3560 | me->ref(); |
3561 | targetNode->dispatchEvent(me,exceptioncode,true); |
3562 | me->deref(); |
3563 | } |
3564 | if (d->oldUnderMouse) |
3565 | d->oldUnderMouse->deref(); |
3566 | d->oldUnderMouse = targetNode; |
3567 | if (d->oldUnderMouse) |
3568 | d->oldUnderMouse->ref(); |
3569 | } |
3570 | |
3571 | bool swallowEvent = false; |
3572 | |
3573 | if (targetNode) { |
3574 | // if the target node is a disabled widget, we don't want any full-blown mouse events |
3575 | if (targetNode->isGenericFormElement() |
3576 | && static_cast<HTMLGenericFormElementImpl*>(targetNode)->disabled()) |
3577 | return true; |
3578 | |
3579 | // send the actual event |
3580 | bool dblclick = ( eventId == EventImpl::CLICK_EVENT && |
3581 | _mouse->type() == QEvent::MouseButtonDblClick ); |
3582 | MouseEventImpl *me = new MouseEventImpl(static_cast<EventImpl::EventId>(eventId), |
3583 | true,cancelable,m_part->xmlDocImpl()->defaultView(), |
3584 | detail,screenX,screenY,clientX,clientY,pageX, pageY, |
3585 | ctrlKey,altKey,shiftKey,metaKey, |
3586 | button,0, isWheelEvent ? 0 : _mouse, dblclick, |
3587 | isWheelEvent ? static_cast<MouseEventImpl::Orientation>(orient) : MouseEventImpl::ONone ); |
3588 | me->ref(); |
3589 | if ( !d->m_mouseEventsTarget && RenderLayer::gScrollBar && eventId == EventImpl::MOUSEDOWN_EVENT ) |
3590 | // button is pressed inside a layer scrollbar, so make it the target for future mousemove events until released |
3591 | d->m_mouseEventsTarget = RenderLayer::gScrollBar; |
3592 | if ( d->m_mouseEventsTarget && qobject_cast<QScrollBar*>(d->m_mouseEventsTarget) && |
3593 | dynamic_cast<KHTMLWidget*>(static_cast<QWidget*>(d->m_mouseEventsTarget)) ) { |
3594 | // we have a sticky mouse event target and it is a layer's scrollbar. Forward events manually. |
3595 | // ### should use the dom |
3596 | KHTMLWidget*w = dynamic_cast<KHTMLWidget*>(static_cast<QWidget*>(d->m_mouseEventsTarget)); |
3597 | QPoint p = w->m_kwp->absolutePos(); |
3598 | QMouseEvent fw(_mouse->type(), QPoint(pageX, pageY)-p, _mouse->button(), _mouse->buttons(), _mouse->modifiers()); |
3599 | static_cast<RenderWidget::EventPropagator *>(static_cast<QWidget*>(d->m_mouseEventsTarget))->sendEvent(&fw); |
3600 | if (_mouse->type() == QMouseEvent::MouseButtonPress && _mouse->button() == Qt::RightButton) { |
3601 | QContextMenuEvent cme(QContextMenuEvent::Mouse, p); |
3602 | static_cast<RenderWidget::EventPropagator *>(static_cast<QWidget*>(d->m_mouseEventsTarget))->sendEvent(&cme); |
3603 | d->m_mouseEventsTarget = 0; |
3604 | } |
3605 | swallowEvent = true; |
3606 | } else { |
3607 | targetNode->dispatchEvent(me,exceptioncode,true); |
3608 | bool defaultHandled = me->defaultHandled(); |
3609 | if (defaultHandled || me->defaultPrevented()) |
3610 | swallowEvent = true; |
3611 | } |
3612 | if (eventId == EventImpl::MOUSEDOWN_EVENT && !me->defaultPrevented()) { |
3613 | // Focus should be shifted on mouse down, not on a click. -dwh |
3614 | // Blur current focus node when a link/button is clicked; this |
3615 | // is expected by some sites that rely on onChange handlers running |
3616 | // from form fields before the button click is processed. |
3617 | DOM::NodeImpl* nodeImpl = targetNode; |
3618 | for ( ; nodeImpl && !nodeImpl->isFocusable(); nodeImpl = nodeImpl->parentNode()) |
3619 | {} |
3620 | if (nodeImpl && nodeImpl->isMouseFocusable()) |
3621 | m_part->xmlDocImpl()->setFocusNode(nodeImpl); |
3622 | else if (!nodeImpl || !nodeImpl->focused()) |
3623 | m_part->xmlDocImpl()->setFocusNode(0); |
3624 | } |
3625 | me->deref(); |
3626 | } |
3627 | |
3628 | return swallowEvent; |
3629 | } |
3630 | |
3631 | void KHTMLView::setIgnoreWheelEvents( bool e ) |
3632 | { |
3633 | d->ignoreWheelEvents = e; |
3634 | } |
3635 | |
3636 | #ifndef QT_NO_WHEELEVENT |
3637 | |
3638 | void KHTMLView::wheelEvent(QWheelEvent* e) |
3639 | { |
3640 | // check if we should reset the state of the indicator describing if |
3641 | // we are currently scrolling the view as a result of wheel events |
3642 | if (d->scrollingFromWheel != QPoint(-1,-1) && d->scrollingFromWheel != QCursor::pos()) |
3643 | d->scrollingFromWheel = d->scrollingFromWheelTimerId ? QCursor::pos() : QPoint(-1,-1); |
3644 | |
3645 | if (d->accessKeysEnabled && d->accessKeysPreActivate) d->accessKeysPreActivate=false; |
3646 | |
3647 | if ( ( e->modifiers() & Qt::ControlModifier) == Qt::ControlModifier ) |
3648 | { |
3649 | emit zoomView( - e->delta() ); |
3650 | e->accept(); |
3651 | } |
3652 | else if (d->firstLayoutPending) |
3653 | { |
3654 | e->accept(); |
3655 | } |
3656 | else if( !m_kwp->isRedirected() && |
3657 | ( (e->orientation() == Qt::Vertical && |
3658 | ((d->ignoreWheelEvents && !verticalScrollBar()->isVisible()) |
3659 | || (e->delta() > 0 && contentsY() <= 0) |
3660 | || (e->delta() < 0 && contentsY() >= contentsHeight() - visibleHeight()))) |
3661 | || |
3662 | (e->orientation() == Qt::Horizontal && |
3663 | ((d->ignoreWheelEvents && !horizontalScrollBar()->isVisible()) |
3664 | || (e->delta() > 0 && contentsX() <=0) |
3665 | || (e->delta() < 0 && contentsX() >= contentsWidth() - visibleWidth())))) |
3666 | && m_part->parentPart()) |
3667 | { |
3668 | if ( m_part->parentPart()->view() ) |
3669 | m_part->parentPart()->view()->wheelEvent( e ); |
3670 | e->ignore(); |
3671 | } |
3672 | else |
3673 | { |
3674 | int xm = e->x(); |
3675 | int ym = e->y(); |
3676 | revertTransforms(xm, ym); |
3677 | |
3678 | DOM::NodeImpl::MouseEvent mev( e->buttons(), DOM::NodeImpl::MouseWheel ); |
3679 | m_part->xmlDocImpl()->prepareMouseEvent( false, xm, ym, &mev ); |
3680 | |
3681 | MouseEventImpl::Orientation o = MouseEventImpl::OVertical; |
3682 | if (e->orientation() == Qt::Horizontal) |
3683 | o = MouseEventImpl::OHorizontal; |
3684 | |
3685 | QMouseEvent _mouse(QEvent::MouseMove, e->pos(), Qt::NoButton, e->buttons(), e->modifiers()); |
3686 | bool swallow = dispatchMouseEvent(EventImpl::KHTML_MOUSEWHEEL_EVENT,mev.innerNode.handle(),mev.innerNonSharedNode.handle(), |
3687 | true,-e->delta()/40,&_mouse,true,DOM::NodeImpl::MouseWheel,o); |
3688 | |
3689 | if (swallow) |
3690 | return; |
3691 | |
3692 | d->scrollBarMoved = true; |
3693 | d->scrollingFromWheel = QCursor::pos(); |
3694 | if (d->smoothScrollMode != SSMDisabled) |
3695 | d->shouldSmoothScroll = true; |
3696 | if (d->scrollingFromWheelTimerId) |
3697 | killTimer(d->scrollingFromWheelTimerId); |
3698 | d->scrollingFromWheelTimerId = startTimer(400); |
3699 | |
3700 | if (m_part->parentPart()) { |
3701 | // don't propagate if we are a sub-frame and our scrollbars are already at end of range |
3702 | bool h = (static_cast<QWheelEvent*>(e)->orientation() == Qt::Horizontal); |
3703 | bool d = (static_cast<QWheelEvent*>(e)->delta() < 0); |
3704 | QScrollBar* hsb = horizontalScrollBar(); |
3705 | QScrollBar* vsb = verticalScrollBar(); |
3706 | if ( (h && ((d && hsb->value() == hsb->maximum()) || (!d && hsb->value() == hsb->minimum()))) || |
3707 | (!h && ((d && vsb->value() == vsb->maximum()) || (!d && vsb->value() == vsb->minimum()))) ) { |
3708 | e->accept(); |
3709 | return; |
3710 | } |
3711 | } |
3712 | QScrollArea::wheelEvent( e ); |
3713 | } |
3714 | |
3715 | } |
3716 | #endif |
3717 | |
3718 | void KHTMLView::dragEnterEvent( QDragEnterEvent* ev ) |
3719 | { |
3720 | // Still overridden for BC reasons only... |
3721 | QScrollArea::dragEnterEvent( ev ); |
3722 | } |
3723 | |
3724 | void KHTMLView::dropEvent( QDropEvent *ev ) |
3725 | { |
3726 | // Still overridden for BC reasons only... |
3727 | QScrollArea::dropEvent( ev ); |
3728 | } |
3729 | |
3730 | void KHTMLView::focusInEvent( QFocusEvent *e ) |
3731 | { |
3732 | DOM::NodeImpl* fn = m_part->xmlDocImpl() ? m_part->xmlDocImpl()->focusNode() : 0; |
3733 | if (fn && fn->renderer() && fn->renderer()->isWidget() && |
3734 | (e->reason() != Qt::MouseFocusReason) && |
3735 | static_cast<khtml::RenderWidget*>(fn->renderer())->widget()) |
3736 | static_cast<khtml::RenderWidget*>(fn->renderer())->widget()->setFocus(); |
3737 | m_part->setSelectionVisible(); |
3738 | QScrollArea::focusInEvent( e ); |
3739 | } |
3740 | |
3741 | void KHTMLView::focusOutEvent( QFocusEvent *e ) |
3742 | { |
3743 | if (m_part) { |
3744 | m_part->stopAutoScroll(); |
3745 | m_part->setSelectionVisible(false); |
3746 | } |
3747 | |
3748 | if ( d->cursorIconWidget ) |
3749 | d->cursorIconWidget->hide(); |
3750 | |
3751 | QScrollArea::focusOutEvent( e ); |
3752 | } |
3753 | |
3754 | void KHTMLView::scrollContentsBy( int dx, int dy ) |
3755 | { |
3756 | if (!dx && !dy) return; |
3757 | |
3758 | if ( !d->firstLayoutPending && !d->complete && m_part->xmlDocImpl() && |
3759 | d->layoutSchedulingEnabled) { |
3760 | // contents scroll while we are not complete: we need to check our layout *now* |
3761 | khtml::RenderCanvas* root = static_cast<khtml::RenderCanvas *>( m_part->xmlDocImpl()->renderer() ); |
3762 | if (root && root->needsLayout()) { |
3763 | unscheduleRelayout(); |
3764 | layout(); |
3765 | } |
3766 | } |
3767 | |
3768 | if ( d->shouldSmoothScroll && d->smoothScrollMode != SSMDisabled && m_part->xmlDocImpl() && |
3769 | m_part->xmlDocImpl()->renderer() && (d->smoothScrollMode != SSMWhenEfficient || d->smoothScrollMissedDeadlines != sWayTooMany)) { |
3770 | |
3771 | bool doSmoothScroll = (!d->staticWidget || d->smoothScrollMode == SSMEnabled); |
3772 | |
3773 | int numStaticPixels = 0; |
3774 | QRegion r = static_cast<RenderCanvas*>(m_part->xmlDocImpl()->renderer())->staticRegion(); |
3775 | |
3776 | // only do smooth scrolling if static region is relatively small |
3777 | if (!doSmoothScroll && d->staticWidget == KHTMLViewPrivate::SBPartial && r.rects().size() <= 10) { |
3778 | foreach(const QRect &rr, r.rects()) |
3779 | numStaticPixels += rr.width()*rr.height(); |
3780 | if ((numStaticPixels < sSmoothScrollMinStaticPixels) || (numStaticPixels*8 < visibleWidth()*visibleHeight())) |
3781 | doSmoothScroll = true; |
3782 | } |
3783 | if (doSmoothScroll) { |
3784 | setupSmoothScrolling(dx, dy); |
3785 | return; |
3786 | } |
3787 | } |
3788 | |
3789 | if ( underMouse() && QToolTip::isVisible() ) |
3790 | QToolTip::hideText(); |
3791 | |
3792 | if (!d->scrollingSelf) { |
3793 | d->scrollBarMoved = true; |
3794 | d->contentsMoving = true; |
3795 | // ensure quick reset of contentsMoving flag |
3796 | scheduleRepaint(0, 0, 0, 0); |
3797 | } |
3798 | |
3799 | if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->documentElement()) { |
3800 | m_part->xmlDocImpl()->documentElement()->dispatchHTMLEvent(EventImpl::SCROLL_EVENT, true, false); |
3801 | } |
3802 | |
3803 | if (QApplication::isRightToLeft()) |
3804 | dx = -dx; |
3805 | |
3806 | if (!d->smoothScrolling) { |
3807 | d->updateContentsXY(); |
3808 | } else { |
3809 | d->contentsX -= dx; |
3810 | d->contentsY -= dy; |
3811 | } |
3812 | if (widget()->pos() != QPoint(0,0)) { |
3813 | kDebug(6000) << "Static widget wasn't positioned at (0,0). This should NOT happen. Please report this event to developers." ; |
3814 | kDebug(6000) << kBacktrace(); |
3815 | widget()->move(0,0); |
3816 | } |
3817 | |
3818 | QWidget *w = widget(); |
3819 | QPoint off; |
3820 | if (m_kwp->isRedirected()) { |
3821 | // This is a redirected sub frame. Translate to root view context |
3822 | KHTMLView* v = m_kwp->rootViewPos( off ); |
3823 | if (v) |
3824 | w = v->widget(); |
3825 | off = viewport()->mapTo(this, off); |
3826 | } |
3827 | |
3828 | if ( d->staticWidget ) { |
3829 | |
3830 | // now remove from view the external widgets that must have completely |
3831 | // disappeared after dx/dy scroll delta is effective |
3832 | if (!d->visibleWidgets.isEmpty()) |
3833 | checkExternalWidgetsPosition(); |
3834 | |
3835 | if ( d->staticWidget == KHTMLViewPrivate::SBPartial |
3836 | && m_part->xmlDocImpl() && m_part->xmlDocImpl()->renderer() ) { |
3837 | // static objects might be selectively repainted, like stones in flowing water |
3838 | QRegion r = static_cast<RenderCanvas*>(m_part->xmlDocImpl()->renderer())->staticRegion(); |
3839 | r.translate( -contentsX(), -contentsY()); |
3840 | QVector<QRect> ar = r.rects(); |
3841 | |
3842 | for (int i = 0; i < ar.size() ; ++i) { |
3843 | widget()->update( ar[i] ); |
3844 | } |
3845 | r = QRegion(QRect(0, 0, visibleWidth(), visibleHeight())) - r; |
3846 | ar = r.rects(); |
3847 | for (int i = 0; i < ar.size() ; ++i) { |
3848 | w->scroll( dx, dy, ar[i].translated(off) ); |
3849 | } |
3850 | d->scrollExternalWidgets(dx, dy); |
3851 | } else { |
3852 | // we can't avoid a full update |
3853 | widget()->update(); |
3854 | } |
3855 | if (d->accessKeysActivated) |
3856 | d->scrollAccessKeys(dx, dy); |
3857 | |
3858 | return; |
3859 | } |
3860 | |
3861 | if (m_kwp->isRedirected()) { |
3862 | const QRect rect(off.x(), off.y(), visibleWidth() * d->zoomLevel / 100, visibleHeight() * d->zoomLevel / 100); |
3863 | w->scroll(dx, dy, rect); |
3864 | if (d->zoomLevel != 100) { |
3865 | w->update(rect); // without this update we are getting bad rendering when an iframe is zoomed in |
3866 | } |
3867 | } else { |
3868 | widget()->scroll(dx, dy, widget()->rect() & viewport()->rect()); |
3869 | } |
3870 | |
3871 | d->scrollExternalWidgets(dx, dy); |
3872 | if (d->accessKeysActivated) |
3873 | d->scrollAccessKeys(dx, dy); |
3874 | } |
3875 | |
3876 | void KHTMLView::setupSmoothScrolling(int dx, int dy) |
3877 | { |
3878 | // old or minimum speed |
3879 | int ddx = qMax(d->steps ? abs(d->dx)/d->steps : 0,3); |
3880 | int ddy = qMax(d->steps ? abs(d->dy)/d->steps : 0,3); |
3881 | |
3882 | // full scroll is remaining scroll plus new scroll |
3883 | d->dx = d->dx + dx; |
3884 | d->dy = d->dy + dy; |
3885 | |
3886 | if (d->dx == 0 && d->dy == 0) { |
3887 | d->stopScrolling(); |
3888 | return; |
3889 | } |
3890 | |
3891 | d->steps = (sSmoothScrollTime-1)/sSmoothScrollTick + 1; |
3892 | |
3893 | if (qMax(abs(d->dx), abs(d->dy)) / d->steps < qMax(ddx,ddy)) { |
3894 | // Don't move slower than average 4px/step in minimum one direction |
3895 | // This means fewer than normal steps |
3896 | d->steps = qMax((abs(d->dx)+ddx-1)/ddx, (abs(d->dy)+ddy-1)/ddy); |
3897 | if (d->steps < 1) d->steps = 1; |
3898 | } |
3899 | |
3900 | d->smoothScrollStopwatch.start(); |
3901 | if (!d->smoothScrolling) { |
3902 | d->startScrolling(); |
3903 | scrollTick(); |
3904 | } |
3905 | } |
3906 | |
3907 | void KHTMLView::scrollTick() { |
3908 | if (d->dx == 0 && d->dy == 0) { |
3909 | d->stopScrolling(); |
3910 | return; |
3911 | } |
3912 | |
3913 | if (d->steps < 1) d->steps = 1; |
3914 | int takesteps = d->smoothScrollStopwatch.restart() / sSmoothScrollTick; |
3915 | int scroll_x = 0; |
3916 | int scroll_y = 0; |
3917 | if (takesteps < 1) takesteps = 1; |
3918 | if (takesteps > d->steps) takesteps = d->steps; |
3919 | for(int i = 0; i < takesteps; i++) { |
3920 | int ddx = (d->dx / (d->steps+1)) * 2; |
3921 | int ddy = (d->dy / (d->steps+1)) * 2; |
3922 | |
3923 | // limit step to requested scrolling distance |
3924 | if (abs(ddx) > abs(d->dx)) ddx = d->dx; |
3925 | if (abs(ddy) > abs(d->dy)) ddy = d->dy; |
3926 | |
3927 | // update remaining scroll |
3928 | d->dx -= ddx; |
3929 | d->dy -= ddy; |
3930 | scroll_x += ddx; |
3931 | scroll_y += ddy; |
3932 | d->steps--; |
3933 | } |
3934 | |
3935 | d->shouldSmoothScroll = false; |
3936 | scrollContentsBy(scroll_x, scroll_y); |
3937 | |
3938 | if (takesteps < 2) { |
3939 | d->smoothScrollMissedDeadlines = 0; |
3940 | } else { |
3941 | if (d->smoothScrollMissedDeadlines != sWayTooMany && |
3942 | (!m_part->xmlDocImpl() || !m_part->xmlDocImpl()->parsing())) { |
3943 | d->smoothScrollMissedDeadlines++; |
3944 | if (d->smoothScrollMissedDeadlines >= sMaxMissedDeadlines) { |
3945 | // we missed many deadlines in a row! |
3946 | // time to signal we had enough.. |
3947 | d->smoothScrollMissedDeadlines = sWayTooMany; |
3948 | } |
3949 | } |
3950 | } |
3951 | } |
3952 | |
3953 | |
3954 | void KHTMLView::addChild(QWidget * child, int x, int y) |
3955 | { |
3956 | if (!child) |
3957 | return; |
3958 | |
3959 | if (child->parent() != widget()) |
3960 | child->setParent( widget() ); |
3961 | |
3962 | // ### handle pseudo-zooming of non-redirected widgets (e.g. just resize'em) |
3963 | |
3964 | child->move(x-contentsX(), y-contentsY()); |
3965 | } |
3966 | |
3967 | void KHTMLView::timerEvent ( QTimerEvent *e ) |
3968 | { |
3969 | // kDebug() << "timer event " << e->timerId(); |
3970 | if ( e->timerId() == d->scrollTimerId ) { |
3971 | if( d->scrollSuspended ) |
3972 | return; |
3973 | switch (d->scrollDirection) { |
3974 | case KHTMLViewPrivate::ScrollDown: |
3975 | if (contentsY() + visibleHeight () >= contentsHeight()) |
3976 | d->newScrollTimer(this, 0); |
3977 | else |
3978 | verticalScrollBar()->setValue( verticalScrollBar()->value() +d->scrollBy ); |
3979 | break; |
3980 | case KHTMLViewPrivate::ScrollUp: |
3981 | if (contentsY() <= 0) |
3982 | d->newScrollTimer(this, 0); |
3983 | else |
3984 | verticalScrollBar()->setValue( verticalScrollBar()->value() -d->scrollBy ); |
3985 | break; |
3986 | case KHTMLViewPrivate::ScrollRight: |
3987 | if (contentsX() + visibleWidth () >= contentsWidth()) |
3988 | d->newScrollTimer(this, 0); |
3989 | else |
3990 | horizontalScrollBar()->setValue( horizontalScrollBar()->value() +d->scrollBy ); |
3991 | break; |
3992 | case KHTMLViewPrivate::ScrollLeft: |
3993 | if (contentsX() <= 0) |
3994 | d->newScrollTimer(this, 0); |
3995 | else |
3996 | horizontalScrollBar()->setValue( horizontalScrollBar()->value() -d->scrollBy ); |
3997 | break; |
3998 | } |
3999 | return; |
4000 | } |
4001 | else if ( e->timerId() == d->scrollingFromWheelTimerId ) { |
4002 | killTimer( d->scrollingFromWheelTimerId ); |
4003 | d->scrollingFromWheelTimerId = 0; |
4004 | } else if ( e->timerId() == d->layoutTimerId ) { |
4005 | if (d->firstLayoutPending && d->layoutAttemptCounter < 4 |
4006 | && (!m_part->xmlDocImpl() || !m_part->xmlDocImpl()->readyForLayout())) { |
4007 | d->layoutAttemptCounter++; |
4008 | killTimer(d->layoutTimerId); |
4009 | d->layoutTimerId = 0; |
4010 | scheduleRelayout(); |
4011 | return; |
4012 | } |
4013 | layout(); |
4014 | d->scheduledLayoutCounter++; |
4015 | if (d->firstLayoutPending) { |
4016 | d->firstLayoutPending = false; |
4017 | verticalScrollBar()->setEnabled( true ); |
4018 | horizontalScrollBar()->setEnabled( true ); |
4019 | } |
4020 | } |
4021 | |
4022 | d->contentsMoving = false; |
4023 | if( m_part->xmlDocImpl() ) { |
4024 | DOM::DocumentImpl *document = m_part->xmlDocImpl(); |
4025 | khtml::RenderCanvas* root = static_cast<khtml::RenderCanvas *>(document->renderer()); |
4026 | |
4027 | if ( root && root->needsLayout() ) { |
4028 | if (d->repaintTimerId) |
4029 | killTimer(d->repaintTimerId); |
4030 | d->repaintTimerId = 0; |
4031 | scheduleRelayout(); |
4032 | return; |
4033 | } |
4034 | } |
4035 | |
4036 | if (d->repaintTimerId) |
4037 | killTimer(d->repaintTimerId); |
4038 | d->repaintTimerId = 0; |
4039 | |
4040 | QRect updateRegion; |
4041 | const QVector<QRect> rects = d->updateRegion.rects(); |
4042 | |
4043 | d->updateRegion = QRegion(); |
4044 | |
4045 | if ( rects.size() ) |
4046 | updateRegion = rects[0]; |
4047 | |
4048 | for ( int i = 1; i < rects.size(); ++i ) { |
4049 | QRect newRegion = updateRegion.unite(rects[i]); |
4050 | if (2*newRegion.height() > 3*updateRegion.height() ) |
4051 | { |
4052 | repaintContents( updateRegion ); |
4053 | updateRegion = rects[i]; |
4054 | } |
4055 | else |
4056 | updateRegion = newRegion; |
4057 | } |
4058 | |
4059 | if ( !updateRegion.isNull() ) |
4060 | repaintContents( updateRegion ); |
4061 | |
4062 | // As widgets can only be accurately positioned during painting, every layout might |
4063 | // dissociate a widget from its RenderWidget. E.g: if a RenderWidget was visible before layout, but the layout |
4064 | // pushed it out of the viewport, it will not be repainted, and consequently it's associated widget won't be repositioned. |
4065 | // Thus we need to check each supposedly 'visible' widget at the end of layout, and remove it in case it's no more in sight. |
4066 | |
4067 | if (d->dirtyLayout && !d->visibleWidgets.isEmpty()) |
4068 | checkExternalWidgetsPosition(); |
4069 | |
4070 | d->dirtyLayout = false; |
4071 | |
4072 | emit repaintAccessKeys(); |
4073 | if (d->emitCompletedAfterRepaint) { |
4074 | bool full = d->emitCompletedAfterRepaint == KHTMLViewPrivate::CSFull; |
4075 | d->emitCompletedAfterRepaint = KHTMLViewPrivate::CSNone; |
4076 | if ( full ) |
4077 | emit m_part->completed(); |
4078 | else |
4079 | emit m_part->completed(true); |
4080 | } |
4081 | } |
4082 | |
4083 | void KHTMLView::checkExternalWidgetsPosition() |
4084 | { |
4085 | QWidget* w; |
4086 | QRect visibleRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()); |
4087 | QList<RenderWidget*> toRemove; |
4088 | QHashIterator<void*, QWidget*> it(d->visibleWidgets); |
4089 | while (it.hasNext()) { |
4090 | int xp = 0, yp = 0; |
4091 | it.next(); |
4092 | RenderWidget* rw = static_cast<RenderWidget*>( it.key() ); |
4093 | if (!rw->absolutePosition(xp, yp) || |
4094 | !visibleRect.intersects(QRect(xp, yp, it.value()->width(), it.value()->height()))) |
4095 | toRemove.append(rw); |
4096 | } |
4097 | foreach (RenderWidget* r, toRemove) |
4098 | if ( (w = d->visibleWidgets.take(r) ) ) |
4099 | w->move( 0, -500000); |
4100 | } |
4101 | |
4102 | void KHTMLView::scheduleRelayout(khtml::RenderObject * /*clippedObj*/) |
4103 | { |
4104 | if (!d->layoutSchedulingEnabled || d->layoutTimerId) |
4105 | return; |
4106 | |
4107 | int time = 0; |
4108 | if (d->firstLayoutPending) { |
4109 | // Any repaint happening while we have no content blanks the viewport ("white flash"). |
4110 | // Hence the need to delay the first layout as much as we can. |
4111 | // Only if the document gets stuck for too long in incomplete state will we allow the blanking. |
4112 | time = d->layoutAttemptCounter ? |
4113 | sLayoutAttemptDelay + sLayoutAttemptIncrement*d->layoutAttemptCounter : sFirstLayoutDelay; |
4114 | } else if (m_part->xmlDocImpl() && m_part->xmlDocImpl()->parsing()) { |
4115 | // Delay between successive layouts in parsing mode. |
4116 | // Increment reflects the decaying importance of visual feedback over time. |
4117 | time = qMin(2000, sParsingLayoutsInterval + d->scheduledLayoutCounter*sParsingLayoutsIncrement); |
4118 | } |
4119 | d->layoutTimerId = startTimer( time ); |
4120 | } |
4121 | |
4122 | void KHTMLView::unscheduleRelayout() |
4123 | { |
4124 | if (!d->layoutTimerId) |
4125 | return; |
4126 | |
4127 | killTimer(d->layoutTimerId); |
4128 | d->layoutTimerId = 0; |
4129 | } |
4130 | |
4131 | void KHTMLView::unscheduleRepaint() |
4132 | { |
4133 | if (!d->repaintTimerId) |
4134 | return; |
4135 | |
4136 | killTimer(d->repaintTimerId); |
4137 | d->repaintTimerId = 0; |
4138 | } |
4139 | |
4140 | void KHTMLView::scheduleRepaint(int x, int y, int w, int h, bool asap) |
4141 | { |
4142 | bool parsing = !m_part->xmlDocImpl() || m_part->xmlDocImpl()->parsing(); |
4143 | |
4144 | // kDebug() << "parsing " << parsing; |
4145 | // kDebug() << "complete " << d->complete; |
4146 | |
4147 | int time = parsing && !d->firstLayoutPending ? 150 : (!asap ? ( !d->complete ? 80 : 20 ) : 0); |
4148 | |
4149 | #ifdef DEBUG_FLICKER |
4150 | QPainter p; |
4151 | p.begin( viewport() ); |
4152 | |
4153 | int vx, vy; |
4154 | contentsToViewport( x, y, vx, vy ); |
4155 | p.fillRect( vx, vy, w, h, Qt::red ); |
4156 | p.end(); |
4157 | #endif |
4158 | |
4159 | d->updateRegion = d->updateRegion.unite(QRect(x,y,w,h)); |
4160 | |
4161 | if (asap && !parsing) |
4162 | unscheduleRepaint(); |
4163 | |
4164 | if ( !d->repaintTimerId ) |
4165 | d->repaintTimerId = startTimer( time ); |
4166 | |
4167 | // kDebug() << "starting timer " << time; |
4168 | } |
4169 | |
4170 | void KHTMLView::complete( bool pendingAction ) |
4171 | { |
4172 | // kDebug() << "KHTMLView::complete()"; |
4173 | |
4174 | d->complete = true; |
4175 | |
4176 | // is there a relayout pending? |
4177 | if (d->layoutTimerId) |
4178 | { |
4179 | // kDebug() << "requesting relayout now"; |
4180 | // do it now |
4181 | killTimer(d->layoutTimerId); |
4182 | d->layoutTimerId = startTimer( 0 ); |
4183 | d->emitCompletedAfterRepaint = pendingAction ? |
4184 | KHTMLViewPrivate::CSActionPending : KHTMLViewPrivate::CSFull; |
4185 | } |
4186 | |
4187 | // is there a repaint pending? |
4188 | if (d->repaintTimerId) |
4189 | { |
4190 | // kDebug() << "requesting repaint now"; |
4191 | // do it now |
4192 | killTimer(d->repaintTimerId); |
4193 | d->repaintTimerId = startTimer( 0 ); |
4194 | d->emitCompletedAfterRepaint = pendingAction ? |
4195 | KHTMLViewPrivate::CSActionPending : KHTMLViewPrivate::CSFull; |
4196 | } |
4197 | |
4198 | if (!d->emitCompletedAfterRepaint) |
4199 | { |
4200 | if (!pendingAction) |
4201 | emit m_part->completed(); |
4202 | else |
4203 | emit m_part->completed(true); |
4204 | } |
4205 | |
4206 | } |
4207 | |
4208 | void KHTMLView::updateScrollBars() |
4209 | { |
4210 | const QWidget *view = widget(); |
4211 | if (!view) |
4212 | return; |
4213 | |
4214 | QSize p = viewport()->size(); |
4215 | QSize m = maximumViewportSize(); |
4216 | |
4217 | if (m.expandedTo(view->size()) == m) |
4218 | p = m; // no scroll bars needed |
4219 | |
4220 | QSize v = view->size(); |
4221 | horizontalScrollBar()->setRange(0, v.width() - p.width()); |
4222 | horizontalScrollBar()->setPageStep(p.width()); |
4223 | verticalScrollBar()->setRange(0, v.height() - p.height()); |
4224 | verticalScrollBar()->setPageStep(p.height()); |
4225 | if (!d->smoothScrolling) { |
4226 | d->updateContentsXY(); |
4227 | } |
4228 | } |
4229 | |
4230 | void KHTMLView::slotMouseScrollTimer() |
4231 | { |
4232 | horizontalScrollBar()->setValue( horizontalScrollBar()->value() +d->m_mouseScroll_byX ); |
4233 | verticalScrollBar()->setValue( verticalScrollBar()->value() +d->m_mouseScroll_byY); |
4234 | } |
4235 | |
4236 | |
4237 | static DOM::Position positionOfLineBoundary(const DOM::Position &pos, bool toEnd) |
4238 | { |
4239 | Selection sel = pos; |
4240 | sel.expandUsingGranularity(Selection::LINE); |
4241 | return toEnd ? sel.end() : sel.start(); |
4242 | } |
4243 | |
4244 | inline static DOM::Position positionOfLineBegin(const DOM::Position &pos) |
4245 | { |
4246 | return positionOfLineBoundary(pos, false); |
4247 | } |
4248 | |
4249 | inline static DOM::Position positionOfLineEnd(const DOM::Position &pos) |
4250 | { |
4251 | return positionOfLineBoundary(pos, true); |
4252 | } |
4253 | |
4254 | bool KHTMLView::caretKeyPressEvent(QKeyEvent *_ke) |
4255 | { |
4256 | EditorContext *ec = &m_part->d->editor_context; |
4257 | Selection &caret = ec->m_selection; |
4258 | Position old_pos = caret.caretPos(); |
4259 | Position pos = old_pos; |
4260 | bool recalcXPos = true; |
4261 | bool handled = true; |
4262 | |
4263 | bool ctrl = _ke->modifiers() & Qt::ControlModifier; |
4264 | bool shift = _ke->modifiers() & Qt::ShiftModifier; |
4265 | |
4266 | switch(_ke->key()) { |
4267 | |
4268 | // -- Navigational keys |
4269 | case Qt::Key_Down: |
4270 | pos = old_pos.nextLinePosition(caret.xPosForVerticalArrowNavigation(Selection::EXTENT)); |
4271 | recalcXPos = false; |
4272 | break; |
4273 | |
4274 | case Qt::Key_Up: |
4275 | pos = old_pos.previousLinePosition(caret.xPosForVerticalArrowNavigation(Selection::EXTENT)); |
4276 | recalcXPos = false; |
4277 | break; |
4278 | |
4279 | case Qt::Key_Left: |
4280 | pos = ctrl ? old_pos.previousWordPosition() : old_pos.previousCharacterPosition(); |
4281 | break; |
4282 | |
4283 | case Qt::Key_Right: |
4284 | pos = ctrl ? old_pos.nextWordPosition() : old_pos.nextCharacterPosition(); |
4285 | break; |
4286 | |
4287 | case Qt::Key_PageDown: |
4288 | // moveCaretNextPage(); ### |
4289 | break; |
4290 | |
4291 | case Qt::Key_PageUp: |
4292 | // moveCaretPrevPage(); ### |
4293 | break; |
4294 | |
4295 | case Qt::Key_Home: |
4296 | if (ctrl) |
4297 | /*moveCaretToDocumentBoundary(false)*/; // ### |
4298 | else |
4299 | pos = positionOfLineBegin(old_pos); |
4300 | break; |
4301 | |
4302 | case Qt::Key_End: |
4303 | if (ctrl) |
4304 | /*moveCaretToDocumentBoundary(true)*/; // ### |
4305 | else |
4306 | pos = positionOfLineEnd(old_pos); |
4307 | break; |
4308 | |
4309 | default: |
4310 | handled = false; |
4311 | |
4312 | }/*end switch*/ |
4313 | |
4314 | if (pos != old_pos) { |
4315 | m_part->clearCaretRectIfNeeded(); |
4316 | |
4317 | caret.moveTo(shift ? caret.nonCaretPos() : pos, pos); |
4318 | int old_x = caret.xPosForVerticalArrowNavigation(Selection::CARETPOS); |
4319 | |
4320 | m_part->selectionLayoutChanged(); |
4321 | |
4322 | // restore old x-position to prevent recalculation |
4323 | if (!recalcXPos) |
4324 | m_part->d->editor_context.m_xPosForVerticalArrowNavigation = old_x; |
4325 | |
4326 | m_part->emitCaretPositionChanged(pos); |
4327 | // ### check when to emit it |
4328 | m_part->notifySelectionChanged(); |
4329 | |
4330 | } |
4331 | |
4332 | if (handled) _ke->accept(); |
4333 | return handled; |
4334 | } |
4335 | |
4336 | #undef DEBUG_CARETMODE |
4337 | |