2 * Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 #import "KWQKHTMLPart.h"
28 #import "DOMInternal.h"
30 #import "KWQClipboard.h"
31 #import "KWQDOMNode.h"
32 #import "KWQDummyView.h"
33 #import "KWQEditCommand.h"
34 #import "KWQExceptions.h"
35 #import "KWQFormData.h"
36 #import "KWQFoundationExtras.h"
37 #import "KWQKJobClasses.h"
38 #import "KWQLogging.h"
39 #import "KWQNSViewExtras.h"
40 #import "KWQPageState.h"
41 #import "KWQPrinter.h"
43 #import "KWQScrollBar.h"
44 #import "KWQWindowWidget.h"
46 #import "WebCoreBridge.h"
47 #import "WebCoreGraphicsBridge.h"
48 #import "WebCoreViewFactory.h"
49 #import "WebDashboardRegion.h"
51 #import "css_computedstyle.h"
53 #import "dom2_eventsimpl.h"
54 #import "dom2_rangeimpl.h"
55 #import "dom_position.h"
56 #import "dom_textimpl.h"
57 #import "html_document.h"
58 #import "html_documentimpl.h"
59 #import "html_formimpl.h"
61 #import "html_tableimpl.h"
63 #import "htmltokenizer.h"
64 #import "khtmlpart_p.h"
66 #import "kjs_binding.h"
67 #import "kjs_window.h"
68 #import "render_canvas.h"
69 #import "render_frames.h"
70 #import "render_image.h"
71 #import "render_list.h"
72 #import "render_style.h"
73 #import "render_table.h"
74 #import "render_text.h"
76 #import "visible_position.h"
77 #import "visible_text.h"
78 #import "visible_units.h"
80 #import <JavaScriptCore/identifier.h>
81 #import <JavaScriptCore/property_map.h>
82 #import <JavaScriptCore/runtime.h>
83 #import <JavaScriptCore/runtime_root.h>
84 #import <JavaScriptCore/WebScriptObjectPrivate.h>
87 #import "KWQAccObjectCache.h"
92 using DOM::AtomicString;
93 using DOM::ClipboardEventImpl;
94 using DOM::DocumentFragmentImpl;
95 using DOM::DocumentImpl;
96 using DOM::DocumentMarker;
98 using DOM::ElementImpl;
100 using DOM::HTMLDocumentImpl;
101 using DOM::HTMLElementImpl;
102 using DOM::HTMLFormElementImpl;
103 using DOM::HTMLFrameElementImpl;
104 using DOM::HTMLGenericFormElementImpl;
105 using DOM::HTMLTableCellElementImpl;
110 using DOM::RangeImpl;
114 using khtml::CharacterIterator;
115 using khtml::ChildFrame;
116 using khtml::Decoder;
117 using khtml::DashboardRegionValue;
118 using khtml::EditCommandPtr;
119 using khtml::endOfWord;
120 using khtml::findPlainText;
121 using khtml::InlineTextBox;
122 using khtml::LeftWordIfOnBoundary;
123 using khtml::MouseDoubleClickEvent;
124 using khtml::MouseMoveEvent;
125 using khtml::MousePressEvent;
126 using khtml::MouseReleaseEvent;
127 using khtml::parseURL;
129 using khtml::RenderCanvas;
130 using khtml::RenderImage;
131 using khtml::RenderLayer;
132 using khtml::RenderListItem;
133 using khtml::RenderObject;
134 using khtml::RenderPart;
135 using khtml::RenderStyle;
136 using khtml::RenderTableCell;
137 using khtml::RenderText;
138 using khtml::RenderWidget;
139 using khtml::RightWordIfOnBoundary;
140 using khtml::Selection;
142 using khtml::setStart;
143 using khtml::ShadowData;
144 using khtml::startOfWord;
145 using khtml::startVisiblePosition;
146 using khtml::StyleDashboardRegion;
147 using khtml::TextIterator;
148 using khtml::DOWNSTREAM;
149 using khtml::VISIBLE;
150 using khtml::VisiblePosition;
151 using khtml::WordAwareIterator;
155 using KJS::Interpreter;
157 using KJS::SavedBuiltins;
158 using KJS::SavedProperties;
159 using KJS::ScheduledAction;
162 using KJS::Bindings::Instance;
164 using KParts::ReadOnlyPart;
165 using KParts::URLArgs;
167 NSEvent *KWQKHTMLPart::_currentEvent = nil;
169 void KHTMLPart::completed()
171 KWQ(this)->_completed.call();
174 void KHTMLPart::completed(bool arg)
176 KWQ(this)->_completed.call(arg);
179 #if !KHTML_NO_CPLUSPLUS_DOM
181 void KHTMLPart::nodeActivated(const Node &)
187 bool KHTMLPart::openURL(const KURL &URL)
189 ASSERT_NOT_REACHED();
193 void KHTMLPart::onURL(const QString &)
197 void KHTMLPart::setStatusBarText(const QString &status)
199 KWQ(this)->setStatusBarText(status);
202 void KHTMLPart::started(Job *j)
204 KWQ(this)->_started.call(j);
207 bool KHTMLView::isKHTMLView() const
212 static void redirectionTimerMonitor(void *context)
214 KWQKHTMLPart *kwq = static_cast<KWQKHTMLPart *>(context);
215 kwq->redirectionTimerStartedOrStopped();
218 KWQKHTMLPart::KWQKHTMLPart()
219 : _started(this, SIGNAL(started(KIO::Job *)))
220 , _completed(this, SIGNAL(completed()))
221 , _completedWithBool(this, SIGNAL(completed(bool)))
222 , _mouseDownView(nil)
223 , _sendingEventToSubview(false)
224 , _mouseDownMayStartDrag(false)
225 , _mouseDownMayStartSelect(false)
226 , _activationEventNumber(0)
227 , _formValuesAboutToBeSubmitted(nil)
228 , _formAboutToBeSubmitted(nil)
229 , _windowWidget(NULL)
230 , _drawSelectionOnly(false)
232 , _windowScriptObject(0)
233 , _windowScriptNPObject(0)
235 , m_markedTextUsesUnderlines(false)
236 , m_windowHasFocus(false)
238 // Must init the cache before connecting to any signals
241 // The widget is made outside this class in our case.
242 KHTMLPart::init( 0, DefaultGUI );
244 mutableInstances().prepend(this);
245 d->m_redirectionTimer.setMonitor(redirectionTimerMonitor, this);
248 KWQKHTMLPart::~KWQKHTMLPart()
250 cleanupPluginRootObjects();
252 mutableInstances().remove(this);
257 // these are all basic Foundation classes and our own classes - we
258 // know they will not raise in dealloc, so no need to block
260 KWQRelease(_formValuesAboutToBeSubmitted);
261 KWQRelease(_formAboutToBeSubmitted);
263 KWQRelease(_windowScriptObject);
265 delete _windowWidget;
268 void KWQKHTMLPart::freeClipboard()
270 if (_dragClipboard) {
271 _dragClipboard->setAccessPolicy(KWQClipboard::Numb);
272 _dragClipboard->deref();
277 void KWQKHTMLPart::setSettings (KHTMLSettings *settings)
279 d->m_settings = settings;
282 QString KWQKHTMLPart::generateFrameName()
284 KWQ_BLOCK_EXCEPTIONS;
285 return QString::fromNSString([_bridge generateFrameName]);
286 KWQ_UNBLOCK_EXCEPTIONS;
291 void KWQKHTMLPart::provisionalLoadStarted()
293 // we don't want to wait until we get an actual http response back
294 // to cancel pending redirects, otherwise they might fire before
296 cancelRedirection(true);
299 bool KWQKHTMLPart::openURL(const KURL &url)
301 KWQ_BLOCK_EXCEPTIONS;
303 bool userGesture = true;
305 if (jScript() && jScript()->interpreter()) {
306 KHTMLPart *rootPart = this;
307 while (rootPart->parentPart() != 0)
308 rootPart = rootPart->parentPart();
309 KJS::ScriptInterpreter *interpreter = static_cast<KJS::ScriptInterpreter *>(KJSProxy::proxy(rootPart)->interpreter());
310 userGesture = interpreter->wasRunByUserGesture();
313 // FIXME: The lack of args here to get the reload flag from
314 // indicates a problem in how we use KHTMLPart::processObjectRequest,
315 // where we are opening the URL before the args are set up.
316 [_bridge loadURL:url.getNSURL()
317 referrer:[_bridge referrer]
319 userGesture:userGesture
325 KWQ_UNBLOCK_EXCEPTIONS;
330 void KWQKHTMLPart::openURLRequest(const KURL &url, const URLArgs &args)
332 KWQ_BLOCK_EXCEPTIONS;
335 QString argsReferrer = args.metaData()["referrer"];
336 if (!argsReferrer.isEmpty()) {
337 referrer = argsReferrer.getNSString();
339 referrer = [_bridge referrer];
342 [_bridge loadURL:url.getNSURL()
346 target:args.frameName.getNSString()
351 KWQ_UNBLOCK_EXCEPTIONS;
354 void KWQKHTMLPart::didNotOpenURL(const KURL &URL)
356 if (_submittedFormURL == URL) {
357 _submittedFormURL = KURL();
361 // Scans logically forward from "start", including any child frames
362 static HTMLFormElementImpl *scanForForm(NodeImpl *start)
365 for (n = start; n; n = n->traverseNextNode()) {
366 NodeImpl::Id nodeID = idFromNode(n);
367 if (nodeID == ID_FORM) {
368 return static_cast<HTMLFormElementImpl *>(n);
369 } else if (n->isHTMLElement()
370 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()) {
371 return static_cast<HTMLGenericFormElementImpl *>(n)->form();
372 } else if (nodeID == ID_FRAME || nodeID == ID_IFRAME) {
373 NodeImpl *childDoc = static_cast<HTMLFrameElementImpl *>(n)->contentDocument();
374 HTMLFormElementImpl *frameResult = scanForForm(childDoc);
383 // We look for either the form containing the current focus, or for one immediately after it
384 HTMLFormElementImpl *KWQKHTMLPart::currentForm() const
386 // start looking either at the active (first responder) node, or where the selection is
387 NodeImpl *start = activeNode().handle();
389 start = selectionStart();
392 // try walking up the node tree to find a form element
394 for (n = start; n; n = n->parentNode()) {
395 if (idFromNode(n) == ID_FORM) {
396 return static_cast<HTMLFormElementImpl *>(n);
397 } else if (n->isHTMLElement()
398 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()) {
399 return static_cast<HTMLGenericFormElementImpl *>(n)->form();
403 // try walking forward in the node tree to find a form element
404 return start ? scanForForm(start) : 0;
407 // Either get cached regexp or build one that matches any of the labels.
408 // The regexp we build is of the form: (STR1|STR2|STRN)
409 QRegExp *regExpForLabels(NSArray *labels)
411 // All the ObjC calls in this method are simple array and string
412 // calls which we can assume do not raise exceptions
415 // Parallel arrays that we use to cache regExps. In practice the number of expressions
416 // that the app will use is equal to the number of locales is used in searching.
417 static const unsigned int regExpCacheSize = 4;
418 static NSMutableArray *regExpLabels = nil;
419 static QPtrList <QRegExp> regExps;
420 static QRegExp wordRegExp = QRegExp("\\w");
424 regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize];
426 unsigned int cacheHit = [regExpLabels indexOfObject:labels];
427 if (cacheHit != NSNotFound) {
428 result = regExps.at(cacheHit);
430 QString pattern("(");
431 unsigned int numLabels = [labels count];
433 for (i = 0; i < numLabels; i++) {
434 QString label = QString::fromNSString([labels objectAtIndex:i]);
436 bool startsWithWordChar = false;
437 bool endsWithWordChar = false;
438 if (label.length() != 0) {
439 startsWithWordChar = wordRegExp.search(label.at(0)) >= 0;
440 endsWithWordChar = wordRegExp.search(label.at(label.length() - 1)) >= 0;
446 // Search for word boundaries only if label starts/ends with "word characters".
447 // If we always searched for word boundaries, this wouldn't work for languages
449 if (startsWithWordChar) {
450 pattern.append("\\b");
452 pattern.append(label);
453 if (endsWithWordChar) {
454 pattern.append("\\b");
458 result = new QRegExp(pattern, false);
461 // add regexp to the cache, making sure it is at the front for LRU ordering
463 if (cacheHit != NSNotFound) {
464 // remove from old spot
465 [regExpLabels removeObjectAtIndex:cacheHit];
466 regExps.remove(cacheHit);
469 [regExpLabels insertObject:labels atIndex:0];
470 regExps.insert(0, result);
472 if ([regExpLabels count] > regExpCacheSize) {
473 [regExpLabels removeObjectAtIndex:regExpCacheSize];
474 QRegExp *last = regExps.last();
475 regExps.removeLast();
482 NSString *KWQKHTMLPart::searchForLabelsAboveCell(QRegExp *regExp, HTMLTableCellElementImpl *cell)
484 RenderTableCell *cellRenderer = static_cast<RenderTableCell *>(cell->renderer());
485 RenderTableCell *cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer);
487 if (cellAboveRenderer) {
488 HTMLTableCellElementImpl *aboveCell =
489 static_cast<HTMLTableCellElementImpl *>(cellAboveRenderer->element());
492 // search within the above cell we found for a match
493 for (NodeImpl *n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) {
494 if (idFromNode(n) == ID_TEXT
495 && n->renderer() && n->renderer()->style()->visibility() == VISIBLE)
497 // For each text chunk, run the regexp
498 QString nodeString = n->nodeValue().string();
499 int pos = regExp->searchRev(nodeString);
501 return nodeString.mid(pos, regExp->matchedLength()).getNSString();
507 // Any reason in practice to search all cells in that are above cell?
511 NSString *KWQKHTMLPart::searchForLabelsBeforeElement(NSArray *labels, ElementImpl *element)
513 QRegExp *regExp = regExpForLabels(labels);
514 // We stop searching after we've seen this many chars
515 const unsigned int charsSearchedThreshold = 500;
516 // This is the absolute max we search. We allow a little more slop than
517 // charsSearchedThreshold, to make it more likely that we'll search whole nodes.
518 const unsigned int maxCharsSearched = 600;
519 // If the starting element is within a table, the cell that contains it
520 HTMLTableCellElementImpl *startingTableCell = 0;
521 bool searchedCellAbove = false;
523 // walk backwards in the node tree, until another element, or form, or end of tree
524 int unsigned lengthSearched = 0;
526 for (n = element->traversePreviousNode();
527 n && lengthSearched < charsSearchedThreshold;
528 n = n->traversePreviousNode())
530 NodeImpl::Id nodeID = idFromNode(n);
531 if (nodeID == ID_FORM
532 || (n->isHTMLElement()
533 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()))
535 // We hit another form element or the start of the form - bail out
537 } else if (nodeID == ID_TD && !startingTableCell) {
538 startingTableCell = static_cast<HTMLTableCellElementImpl *>(n);
539 } else if (nodeID == ID_TR && startingTableCell) {
540 NSString *result = searchForLabelsAboveCell(regExp, startingTableCell);
544 searchedCellAbove = true;
545 } else if (nodeID == ID_TEXT
546 && n->renderer() && n->renderer()->style()->visibility() == VISIBLE)
548 // For each text chunk, run the regexp
549 QString nodeString = n->nodeValue().string();
550 // add 100 for slop, to make it more likely that we'll search whole nodes
551 if (lengthSearched + nodeString.length() > maxCharsSearched) {
552 nodeString = nodeString.right(charsSearchedThreshold - lengthSearched);
554 int pos = regExp->searchRev(nodeString);
556 return nodeString.mid(pos, regExp->matchedLength()).getNSString();
558 lengthSearched += nodeString.length();
563 // If we started in a cell, but bailed because we found the start of the form or the
564 // previous element, we still might need to search the row above us for a label.
565 if (startingTableCell && !searchedCellAbove) {
566 return searchForLabelsAboveCell(regExp, startingTableCell);
572 NSString *KWQKHTMLPart::matchLabelsAgainstElement(NSArray *labels, ElementImpl *element)
574 QString name = element->getAttribute(ATTR_NAME).string();
575 // Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
576 name.replace(QRegExp("[[:digit:]]"), " ");
577 name.replace('_', ' ');
579 QRegExp *regExp = regExpForLabels(labels);
580 // Use the largest match we can find in the whole name string
587 pos = regExp->search(name, start);
589 length = regExp->matchedLength();
590 if (length >= bestLength) {
599 return name.mid(bestPos, bestLength).getNSString();
604 // Search from the end of the currently selected location if we are first responder, or from
605 // the beginning of the document if nothing is selected or we're not first responder.
606 bool KWQKHTMLPart::findString(NSString *string, bool forward, bool caseFlag, bool wrapFlag)
608 QString target = QString::fromNSString(string);
609 if (target.isEmpty()) {
613 // Start on the correct edge of the selection, search to edge of document.
614 Range searchRange(xmlDocImpl());
615 searchRange.selectNodeContents(xmlDocImpl());
616 if (selectionStart()) {
618 setStart(searchRange, VisiblePosition(selection().end(), selection().endAffinity()));
620 setEnd(searchRange, VisiblePosition(selection().start(), selection().startAffinity()));
624 // Do the search once, then do it a second time to handle wrapped search.
625 // Searches some or all of document twice in the failure case, but that's probably OK.
626 Range resultRange = findPlainText(searchRange, target, forward, caseFlag);
627 if (resultRange.collapsed() && wrapFlag) {
628 searchRange.selectNodeContents(xmlDocImpl());
629 resultRange = findPlainText(searchRange, target, forward, caseFlag);
630 // If we got back to the same place we started, that doesn't count as success.
631 if (resultRange == selection().toRange()) {
636 if (resultRange.collapsed()) {
640 setSelection(Selection(resultRange, DOWNSTREAM, khtml::SEL_PREFER_UPSTREAM_AFFINITY));
645 void KWQKHTMLPart::clearRecordedFormValues()
647 // It's safe to assume that our own classes and Foundation data
648 // structures won't raise exceptions in dealloc
650 KWQRelease(_formValuesAboutToBeSubmitted);
651 _formValuesAboutToBeSubmitted = nil;
652 KWQRelease(_formAboutToBeSubmitted);
653 _formAboutToBeSubmitted = nil;
656 void KWQKHTMLPart::recordFormValue(const QString &name, const QString &value, HTMLFormElementImpl *element)
658 // It's safe to assume that our own classes and basic Foundation
659 // data structures won't raise exceptions
661 if (!_formValuesAboutToBeSubmitted) {
662 _formValuesAboutToBeSubmitted = KWQRetainNSRelease([[NSMutableDictionary alloc] init]);
663 ASSERT(!_formAboutToBeSubmitted);
664 _formAboutToBeSubmitted = KWQRetain([DOMElement _elementWithImpl:element]);
666 ASSERT([_formAboutToBeSubmitted _elementImpl] == element);
668 [_formValuesAboutToBeSubmitted setObject:value.getNSString() forKey:name.getNSString()];
671 void KWQKHTMLPart::submitForm(const KURL &url, const URLArgs &args)
673 KWQ_BLOCK_EXCEPTIONS;
675 // FIXME: We'd like to remove this altogether and fix the multiple form submission issue another way.
676 // We do not want to submit more than one form from the same page,
677 // nor do we want to submit a single form more than once.
678 // This flag prevents these from happening; not sure how other browsers prevent this.
679 // The flag is reset in each time we start handle a new mouse or key down event, and
680 // also in setView since this part may get reused for a page from the back/forward cache.
681 // The form multi-submit logic here is only needed when we are submitting a form that affects this frame.
682 // FIXME: Frame targeting is only one of the ways the submission could end up doing something other
683 // than replacing this frame's content, so this check is flawed. On the other hand, the check is hardly
684 // needed any more now that we reset _submittedFormURL on each mouse or key down event.
685 WebCoreBridge *target = args.frameName.isEmpty() ? _bridge : [_bridge findFrameNamed:args.frameName.getNSString()];
686 KHTMLPart *targetPart = [target part];
687 bool willReplaceThisFrame = false;
688 for (KHTMLPart *p = this; p; p = p->parentPart()) {
689 if (p == targetPart) {
690 willReplaceThisFrame = true;
694 if (willReplaceThisFrame) {
695 if (_submittedFormURL == url) {
698 _submittedFormURL = url;
701 if (!args.doPost()) {
702 [_bridge loadURL:url.getNSURL()
703 referrer:[_bridge referrer]
706 target:args.frameName.getNSString()
707 triggeringEvent:_currentEvent
708 form:_formAboutToBeSubmitted
709 formValues:_formValuesAboutToBeSubmitted];
711 ASSERT(args.contentType().startsWith("Content-Type: "));
712 [_bridge postWithURL:url.getNSURL()
713 referrer:[_bridge referrer]
714 target:args.frameName.getNSString()
715 data:arrayFromFormData(args.postData)
716 contentType:args.contentType().mid(14).getNSString()
717 triggeringEvent:_currentEvent
718 form:_formAboutToBeSubmitted
719 formValues:_formValuesAboutToBeSubmitted];
721 clearRecordedFormValues();
723 KWQ_UNBLOCK_EXCEPTIONS;
726 void KWQKHTMLPart::setEncoding(const QString &name, bool userChosen)
728 if (!d->m_workingURL.isEmpty()) {
731 d->m_encoding = name;
732 d->m_haveEncoding = userChosen;
735 void KWQKHTMLPart::addData(const char *bytes, int length)
737 ASSERT(d->m_workingURL.isEmpty());
739 ASSERT(d->m_doc->parsing());
740 write(bytes, length);
743 void KHTMLPart::frameDetached()
745 // FIXME: This should be a virtual function, with the first part in KWQKHTMLPart, and the second
746 // part in KHTMLPart, so it works for KHTML too.
748 KWQ_BLOCK_EXCEPTIONS;
749 [KWQ(this)->bridge() frameDetached];
750 KWQ_UNBLOCK_EXCEPTIONS;
752 KHTMLPart *parent = parentPart();
754 FrameList& parentFrames = parent->d->m_frames;
755 FrameIt end = parentFrames.end();
756 for (FrameIt it = parentFrames.begin(); it != end; ++it) {
757 ChildFrame &child = *it;
758 if (child.m_part == this) {
759 parent->disconnectChild(&child);
760 parentFrames.remove(it);
768 void KWQKHTMLPart::urlSelected(const KURL &url, int button, int state, const URLArgs &args)
770 KWQ_BLOCK_EXCEPTIONS;
773 QString argsReferrer = args.metaData()["referrer"];
774 if (!argsReferrer.isEmpty()) {
775 referrer = argsReferrer.getNSString();
777 referrer = [_bridge referrer];
780 [_bridge loadURL:url.getNSURL()
784 target:args.frameName.getNSString()
785 triggeringEvent:_currentEvent
789 KWQ_UNBLOCK_EXCEPTIONS;
792 class KWQPluginPart : public ReadOnlyPart
794 virtual bool openURL(const KURL &) { return true; }
795 virtual bool closeURL() { return true; }
798 ReadOnlyPart *KWQKHTMLPart::createPart(const ChildFrame &child, const KURL &url, const QString &mimeType)
800 KWQ_BLOCK_EXCEPTIONS;
803 ObjectElementType objectType = ObjectElementFrame;
804 if (child.m_type == ChildFrame::Object)
805 objectType = [_bridge determineObjectFromMIMEType:mimeType.getNSString() URL:url.getNSURL()];
807 if (objectType == ObjectElementNone) {
808 if (child.m_hasFallbackContent)
810 objectType = ObjectElementPlugin; // Since no fallback content exists, we'll make a plugin and show the error dialog.
813 if (objectType == ObjectElementPlugin) {
814 KWQPluginPart *newPart = new KWQPluginPart;
815 newPart->setWidget(new QWidget([_bridge viewForPluginWithURL:url.getNSURL()
816 attributeNames:child.m_paramNames.getNSArray()
817 attributeValues:child.m_paramValues.getNSArray()
818 MIMEType:child.m_args.serviceType.getNSString()]));
821 LOG(Frames, "name %s", child.m_name.ascii());
822 BOOL allowsScrolling = YES;
823 int marginWidth = -1;
824 int marginHeight = -1;
825 if (child.m_type != ChildFrame::Object) {
826 HTMLFrameElementImpl *o = static_cast<HTMLFrameElementImpl *>(child.m_frame->element());
827 allowsScrolling = o->scrollingMode() != QScrollView::AlwaysOff;
828 marginWidth = o->getMarginWidth();
829 marginHeight = o->getMarginHeight();
831 WebCoreBridge *childBridge = [_bridge createChildFrameNamed:child.m_name.getNSString()
832 withURL:url.getNSURL()
833 referrer:child.m_args.metaData()["referrer"].getNSString()
834 renderPart:child.m_frame
835 allowsScrolling:allowsScrolling
836 marginWidth:marginWidth
837 marginHeight:marginHeight];
838 // This call needs to return an object with a ref, since the caller will expect to own it.
839 // childBridge owns the only ref so far.
840 part = [childBridge part];
847 KWQ_UNBLOCK_EXCEPTIONS;
852 void KWQKHTMLPart::setView(KHTMLView *view)
854 // Detach the document now, so any onUnload handlers get run - if
855 // we wait until the view is destroyed, then things won't be
856 // hooked up enough for some JavaScript calls to work.
857 if (d->m_doc && view == NULL) {
870 // Only one form submission is allowed per view of a part.
871 // Since this part may be getting reused as a result of being
872 // pulled from the back/forward cache, reset this flag.
873 _submittedFormURL = KURL();
876 KHTMLView *KWQKHTMLPart::view() const
881 void KWQKHTMLPart::setTitle(const DOMString &title)
883 QString text = title.string();
884 text.replace(QChar('\\'), backslashAsCurrencySymbol());
886 KWQ_BLOCK_EXCEPTIONS;
887 [_bridge setTitle:text.getNSString()];
888 KWQ_UNBLOCK_EXCEPTIONS;
891 void KWQKHTMLPart::setStatusBarText(const QString &status)
893 QString text = status;
894 text.replace(QChar('\\'), backslashAsCurrencySymbol());
896 KWQ_BLOCK_EXCEPTIONS;
897 [_bridge setStatusText:text.getNSString()];
898 KWQ_UNBLOCK_EXCEPTIONS;
901 void KWQKHTMLPart::scheduleClose()
903 KWQ_BLOCK_EXCEPTIONS;
904 [_bridge closeWindowSoon];
905 KWQ_UNBLOCK_EXCEPTIONS;
908 void KWQKHTMLPart::unfocusWindow()
910 KWQ_BLOCK_EXCEPTIONS;
911 [_bridge unfocusWindow];
912 KWQ_UNBLOCK_EXCEPTIONS;
915 void KWQKHTMLPart::jumpToSelection()
917 if (d->m_selection.start().isNotNull()) {
918 KWQ_BLOCK_EXCEPTIONS;
919 [d->m_view->getDocumentView() _KWQ_scrollRectToVisible:NSRect(selectionRect()) forceCentering:NO];
920 KWQ_UNBLOCK_EXCEPTIONS;
924 QString KWQKHTMLPart::advanceToNextMisspelling(bool startBeforeSelection)
926 // The basic approach is to search in two phases - from the selection end to the end of the doc, and
927 // then we wrap and search from the doc start to (approximately) where we started.
929 // Start at the end of the selection, search to edge of document. Starting at the selection end makes
930 // repeated "check spelling" commands work.
931 Range searchRange(xmlDocImpl());
932 searchRange.selectNodeContents(xmlDocImpl());
933 bool startedWithSelection = false;
934 if (selectionStart()) {
935 startedWithSelection = true;
936 if (startBeforeSelection) {
937 VisiblePosition start(selection().start(), selection().startAffinity());
938 // We match AppKit's rule: Start 1 character before the selection.
939 VisiblePosition oneBeforeStart = start.previous();
940 setStart(searchRange, oneBeforeStart.isNotNull() ? oneBeforeStart : start);
942 setStart(searchRange, VisiblePosition(selection().end(), selection().endAffinity()));
946 // If we're not in an editable node, try to find one, make that our range to work in
947 NodeImpl *editableNodeImpl = searchRange.startContainer().handle();
948 if (!editableNodeImpl->isContentEditable()) {
949 editableNodeImpl = editableNodeImpl->nextEditable();
950 if (!editableNodeImpl) {
953 searchRange.setStartBefore(editableNodeImpl);
954 startedWithSelection = false; // won't need to wrap
957 // topNode defines the whole range we want to operate on
958 NodeImpl *topNode = editableNodeImpl->rootEditableElement();
959 searchRange.setEndAfter(topNode);
961 // Make sure start of searchRange is not in the middle of a word. Jumping back a char and then
962 // forward by a word happens to do the trick.
963 if (startedWithSelection) {
964 VisiblePosition oneBeforeStart = startVisiblePosition(searchRange, DOWNSTREAM).previous();
965 if (oneBeforeStart.isNotNull()) {
966 setStart(searchRange, endOfWord(oneBeforeStart));
967 } // else we were already at the start of the editable node
970 if (searchRange.collapsed()) {
971 return QString(); // nothing to search in
974 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
975 WordAwareIterator it(searchRange);
976 bool wrapped = false;
978 // We go to the end of our first range instead of the start of it, just to be sure
979 // we don't get foiled by any word boundary problems at the start. It means we might
980 // do a tiny bit more searching.
981 NodeImpl *searchEndAfterWrapNode = it.range().endContainer().handle();
982 long searchEndAfterWrapOffset = it.range().endOffset();
985 if (!it.atEnd()) { // we may be starting at the end of the doc, and already by atEnd
986 const QChar *chars = it.characters();
987 long len = it.length();
988 if (len > 1 || !chars[0].isSpace()) {
989 NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:(unichar *)chars length:len freeWhenDone:NO];
990 NSRange misspelling = [checker checkSpellingOfString:chunk startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:[_bridge spellCheckerDocumentTag] wordCount:NULL];
992 if (misspelling.length > 0) {
993 // Build up result range and string. Note the misspelling may span many text nodes,
994 // but the CharIterator insulates us from this complexity
995 Range misspellingRange(xmlDocImpl());
996 CharacterIterator chars(it.range());
997 chars.advance(misspelling.location);
998 misspellingRange.setStart(chars.range().startContainer(), chars.range().startOffset());
999 QString result = chars.string(misspelling.length);
1000 misspellingRange.setEnd(chars.range().startContainer(), chars.range().startOffset());
1002 setSelection(Selection(misspellingRange, DOWNSTREAM, khtml::SEL_PREFER_UPSTREAM_AFFINITY));
1004 // Mark misspelling in document.
1005 xmlDocImpl()->addMarker(misspellingRange, DocumentMarker::Spelling);
1013 if (wrapped || !startedWithSelection) {
1014 break; // finished the second range, or we did the whole doc with the first range
1016 // we've gone from the selection to the end of doc, now wrap around
1018 searchRange.setStartBefore(topNode);
1019 // going until the end of the very first chunk we tested is far enough
1020 searchRange.setEnd(searchEndAfterWrapNode, searchEndAfterWrapOffset);
1021 it = WordAwareIterator(searchRange);
1029 bool KWQKHTMLPart::scrollOverflow(KWQScrollDirection direction, KWQScrollGranularity granularity)
1031 if (!xmlDocImpl()) {
1035 NodeImpl *node = xmlDocImpl()->focusNode();
1037 node = d->m_mousePressNode.get();
1041 RenderObject *r = node->renderer();
1043 return r->scroll(direction, granularity);
1050 bool KWQKHTMLPart::wheelEvent(NSEvent *event)
1052 KHTMLView *v = d->m_view;
1055 QWheelEvent qEvent(event);
1056 v->viewportWheelEvent(&qEvent);
1057 if (qEvent.isAccepted())
1061 // FIXME: The scrolling done here should be done in the default handlers
1062 // of the elements rather than here in the part.
1064 KWQScrollDirection direction;
1066 float deltaX = [event deltaX];
1067 float deltaY = [event deltaY];
1069 direction = KWQScrollRight;
1070 multiplier = -deltaX;
1071 } else if (deltaX > 0) {
1072 direction = KWQScrollLeft;
1073 multiplier = deltaX;
1074 } else if (deltaY < 0) {
1075 direction = KWQScrollDown;
1076 multiplier = -deltaY;
1077 } else if (deltaY > 0) {
1078 direction = KWQScrollUp;
1079 multiplier = deltaY;
1084 RenderObject *r = renderer();
1089 NSPoint point = [d->m_view->getDocumentView() convertPoint:[event locationInWindow] fromView:nil];
1090 RenderObject::NodeInfo nodeInfo(true, true);
1091 r->layer()->hitTest(nodeInfo, (int)point.x, (int)point.y);
1093 NodeImpl *node = nodeInfo.innerNode();
1098 r = node->renderer();
1103 return r->scroll(direction, KWQScrollWheel, multiplier);
1106 void KWQKHTMLPart::redirectionTimerStartedOrStopped()
1108 // Don't report history navigations, just actual redirection.
1109 if (d->m_scheduledRedirection == historyNavigationScheduled) {
1113 KWQ_BLOCK_EXCEPTIONS;
1114 if (d->m_redirectionTimer.isActive()) {
1115 [_bridge reportClientRedirectToURL:KURL(d->m_redirectURL).getNSURL()
1116 delay:d->m_delayRedirect
1117 fireDate:[d->m_redirectionTimer.getNSTimer() fireDate]
1118 lockHistory:d->m_redirectLockHistory
1119 isJavaScriptFormAction:d->m_executingJavaScriptFormAction];
1121 [_bridge reportClientRedirectCancelled:d->m_cancelWithLoadInProgress];
1123 KWQ_UNBLOCK_EXCEPTIONS;
1126 void KWQKHTMLPart::paint(QPainter *p, const QRect &rect)
1130 if (p->device()->devType() == QInternal::Printer)
1131 fillWithRed = false; // Printing, don't fill with red (can't remember why).
1132 else if (!xmlDocImpl() || xmlDocImpl()->ownerElement())
1133 fillWithRed = false; // Subframe, don't fill with red.
1134 else if (view() && view()->isTransparent())
1135 fillWithRed = false; // Transparent, don't fill with red.
1136 else if (_drawSelectionOnly)
1137 fillWithRed = false; // Selections are transparent, don't fill with red.
1138 else if (_elementToDraw.notNull())
1139 fillWithRed = false; // Element images are transparent, don't fill with red.
1144 p->fillRect(rect.x(), rect.y(), rect.width(), rect.height(), QColor(0xFF, 0, 0));
1149 // _elementToDraw is used to draw only one element
1150 RenderObject *eltRenderer = _elementToDraw.notNull() ? _elementToDraw->renderer() : 0;
1151 renderer()->layer()->paint(p, rect, _drawSelectionOnly, eltRenderer);
1154 // Regions may have changed as a result of the visibility/z-index of element changing.
1155 if (renderer()->document()->dashboardRegionsDirty()){
1156 renderer()->canvas()->view()->updateDashboardRegions();
1160 ERROR("called KWQKHTMLPart::paint with nil renderer");
1164 void KWQKHTMLPart::adjustPageHeight(float *newBottom, float oldTop, float oldBottom, float bottomLimit)
1166 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
1168 // Use a printer device, with painting disabled for the pagination phase
1169 QPainter painter(true);
1170 painter.setPaintingDisabled(true);
1172 root->setTruncatedAt((int)floor(oldBottom));
1173 QRect dirtyRect(0, (int)floor(oldTop),
1174 root->docWidth(), (int)ceil(oldBottom-oldTop));
1175 root->layer()->paint(&painter, dirtyRect);
1176 *newBottom = root->bestTruncatedAt();
1177 if (*newBottom == 0) {
1178 *newBottom = oldBottom;
1181 *newBottom = oldBottom;
1185 RenderObject *KWQKHTMLPart::renderer() const
1187 DocumentImpl *doc = xmlDocImpl();
1188 return doc ? doc->renderer() : 0;
1191 QString KWQKHTMLPart::userAgent() const
1193 KWQ_BLOCK_EXCEPTIONS;
1194 return QString::fromNSString([_bridge userAgentForURL:m_url.getNSURL()]);
1195 KWQ_UNBLOCK_EXCEPTIONS;
1200 QString KWQKHTMLPart::mimeTypeForFileName(const QString &fileName) const
1202 NSString *ns = fileName.getNSString();
1204 KWQ_BLOCK_EXCEPTIONS;
1205 return QString::fromNSString([_bridge MIMETypeForPath:ns]);
1206 KWQ_UNBLOCK_EXCEPTIONS;
1211 NSView *KWQKHTMLPart::nextKeyViewInFrame(NodeImpl *node, KWQSelectionDirection direction)
1213 DocumentImpl *doc = xmlDocImpl();
1218 node = direction == KWQSelectingNext
1219 ? doc->nextFocusNode(node) : doc->previousFocusNode(node);
1223 RenderWidget *renderWidget = dynamic_cast<RenderWidget *>(node->renderer());
1225 QWidget *widget = renderWidget->widget();
1226 KHTMLView *childFrameWidget = dynamic_cast<KHTMLView *>(widget);
1228 if (childFrameWidget) {
1229 view = KWQ(childFrameWidget->part())->nextKeyViewInFrame(0, direction);
1230 } else if (widget) {
1231 view = widget->getView();
1238 doc->setFocusNode(node);
1239 if (view() && node->renderer() && !node->renderer()->isRoot()) {
1240 view()->ensureRectVisibleCentered(node->getRect());
1242 [_bridge makeFirstResponder:[_bridge documentView]];
1243 return [_bridge documentView];
1248 NSView *KWQKHTMLPart::nextKeyViewInFrameHierarchy(NodeImpl *node, KWQSelectionDirection direction)
1250 NSView *next = nextKeyViewInFrame(node, direction);
1252 KWQKHTMLPart *parent = KWQ(parentPart());
1254 next = parent->nextKeyViewInFrameHierarchy(parent->childFrame(this)->m_frame->element(), direction);
1258 // remove focus from currently focused node if we're giving focus to another view
1259 if (next && (next != [_bridge documentView])) {
1260 DocumentImpl *doc = xmlDocImpl();
1262 doc->setFocusNode(0);
1269 NSView *KWQKHTMLPart::nextKeyView(NodeImpl *node, KWQSelectionDirection direction)
1271 KWQ_BLOCK_EXCEPTIONS;
1273 NSView * next = nextKeyViewInFrameHierarchy(node, direction);
1278 // Look at views from the top level part up, looking for a next key view that we can use.
1280 next = direction == KWQSelectingNext
1281 ? [_bridge nextKeyViewOutsideWebFrameViews]
1282 : [_bridge previousKeyViewOutsideWebFrameViews];
1288 KWQ_UNBLOCK_EXCEPTIONS;
1290 // If all else fails, make a loop by starting from 0.
1291 return nextKeyViewInFrameHierarchy(0, direction);
1294 NSView *KWQKHTMLPart::nextKeyViewForWidget(QWidget *startingWidget, KWQSelectionDirection direction)
1296 // Use the event filter object to figure out which RenderWidget owns this QWidget and get to the DOM.
1297 // Then get the next key view in the order determined by the DOM.
1298 NodeImpl *node = nodeForWidget(startingWidget);
1300 return partForNode(node)->nextKeyView(node, direction);
1303 bool KWQKHTMLPart::currentEventIsMouseDownInWidget(QWidget *candidate)
1305 KWQ_BLOCK_EXCEPTIONS;
1306 switch ([[NSApp currentEvent] type]) {
1307 case NSLeftMouseDown:
1308 case NSRightMouseDown:
1309 case NSOtherMouseDown:
1314 KWQ_UNBLOCK_EXCEPTIONS;
1316 NodeImpl *node = nodeForWidget(candidate);
1318 return partForNode(node)->nodeUnderMouse() == node;
1321 bool KWQKHTMLPart::currentEventIsKeyboardOptionTab()
1323 KWQ_BLOCK_EXCEPTIONS;
1324 NSEvent *evt = [NSApp currentEvent];
1325 if ([evt type] != NSKeyDown) {
1329 if (([evt modifierFlags] & NSAlternateKeyMask) == 0) {
1333 NSString *chars = [evt charactersIgnoringModifiers];
1334 if ([chars length] != 1)
1337 const unichar tabKey = 0x0009;
1338 const unichar shiftTabKey = 0x0019;
1339 unichar c = [chars characterAtIndex:0];
1340 if (c != tabKey && c != shiftTabKey)
1343 KWQ_UNBLOCK_EXCEPTIONS;
1347 bool KWQKHTMLPart::handleKeyboardOptionTabInView(NSView *view)
1349 if (KWQKHTMLPart::currentEventIsKeyboardOptionTab()) {
1350 if (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) != 0) {
1351 [[view window] selectKeyViewPrecedingView:view];
1353 [[view window] selectKeyViewFollowingView:view];
1361 bool KWQKHTMLPart::tabsToLinks() const
1363 if ([_bridge keyboardUIMode] & WebCoreKeyboardAccessTabsToLinks)
1364 return !KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1366 return KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1369 bool KWQKHTMLPart::tabsToAllControls() const
1371 WebCoreKeyboardUIMode keyboardUIMode = [_bridge keyboardUIMode];
1372 BOOL handlingOptionTab = KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1374 // If tab-to-links is off, option-tab always highlights all controls
1375 if ((keyboardUIMode & WebCoreKeyboardAccessTabsToLinks) == 0 && handlingOptionTab) {
1379 // If system preferences say to include all controls, we always include all controls
1380 if (keyboardUIMode & WebCoreKeyboardAccessFull) {
1384 // Otherwise tab-to-links includes all controls, unless the sense is flipped via option-tab.
1385 if (keyboardUIMode & WebCoreKeyboardAccessTabsToLinks) {
1386 return !handlingOptionTab;
1389 return handlingOptionTab;
1392 KJS::Bindings::RootObject *KWQKHTMLPart::executionContextForDOM()
1394 return bindingRootObject();
1397 KJS::Bindings::RootObject *KWQKHTMLPart::bindingRootObject()
1399 if (!_bindingRoot) {
1400 _bindingRoot = new KJS::Bindings::RootObject(0); // The root gets deleted by JavaScriptCore.
1401 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1402 _bindingRoot->setRootObjectImp (win);
1403 _bindingRoot->setInterpreter (KJSProxy::proxy(this)->interpreter());
1404 addPluginRootObject (_bindingRoot);
1406 return _bindingRoot;
1409 WebScriptObject *KWQKHTMLPart::windowScriptObject()
1411 if (!_windowScriptObject) {
1412 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1413 _windowScriptObject = KWQRetainNSRelease([[WebScriptObject alloc] _initWithObjectImp:win originExecutionContext:bindingRootObject() executionContext:bindingRootObject()]);
1416 return _windowScriptObject;
1419 NPObject *KWQKHTMLPart::windowScriptNPObject()
1421 if (!_windowScriptNPObject) {
1422 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1423 _windowScriptNPObject = _NPN_CreateScriptObject (0, win, bindingRootObject(), bindingRootObject());
1426 return _windowScriptNPObject;
1429 void KWQKHTMLPart::partClearedInBegin()
1431 [_bridge windowObjectCleared];
1434 QMap<int, ScheduledAction*> *KWQKHTMLPart::pauseActions(const void *key)
1436 if (d->m_doc && d->m_jscript) {
1437 Window *w = Window::retrieveWindow(this);
1438 if (w && w->hasTimeouts()) {
1439 return w->pauseTimeouts(key);
1445 void KWQKHTMLPart::resumeActions(QMap<int, ScheduledAction*> *actions, const void *key)
1447 if (d->m_doc && d->m_jscript && d->m_bJScriptEnabled) {
1448 Window *w = Window::retrieveWindow(this);
1450 w->resumeTimeouts(actions, key);
1455 bool KWQKHTMLPart::canCachePage()
1457 // Only save page state if:
1458 // 1. We're not a frame or frameset.
1459 // 2. The page has no unload handler.
1460 // 3. The page has no password fields.
1461 // 4. The URL for the page is not https.
1462 // 5. The page has no applets.
1463 if (d->m_frames.count() ||
1465 m_url.protocol().startsWith("https") ||
1466 (d->m_doc && (htmlDocument().applets().length() != 0 ||
1467 d->m_doc->hasWindowEventListener(EventImpl::UNLOAD_EVENT) ||
1468 d->m_doc->hasPasswordField()))) {
1474 void KWQKHTMLPart::saveWindowProperties(SavedProperties *windowProperties)
1476 Window *window = Window::retrieveWindow(this);
1478 window->saveProperties(*windowProperties);
1481 void KWQKHTMLPart::saveLocationProperties(SavedProperties *locationProperties)
1483 Window *window = Window::retrieveWindow(this);
1485 Interpreter::lock();
1486 Location *location = window->location();
1487 Interpreter::unlock();
1488 location->saveProperties(*locationProperties);
1492 void KWQKHTMLPart::restoreWindowProperties(SavedProperties *windowProperties)
1494 Window *window = Window::retrieveWindow(this);
1496 window->restoreProperties(*windowProperties);
1499 void KWQKHTMLPart::restoreLocationProperties(SavedProperties *locationProperties)
1501 Window *window = Window::retrieveWindow(this);
1503 Interpreter::lock();
1504 Location *location = window->location();
1505 Interpreter::unlock();
1506 location->restoreProperties(*locationProperties);
1510 void KWQKHTMLPart::saveInterpreterBuiltins(SavedBuiltins &interpreterBuiltins)
1512 if (jScript() && jScript()->interpreter()) {
1513 jScript()->interpreter()->saveBuiltins(interpreterBuiltins);
1517 void KWQKHTMLPart::restoreInterpreterBuiltins(const SavedBuiltins &interpreterBuiltins)
1519 if (jScript() && jScript()->interpreter()) {
1520 jScript()->interpreter()->restoreBuiltins(interpreterBuiltins);
1524 void KWQKHTMLPart::openURLFromPageCache(KWQPageState *state)
1526 // It's safe to assume none of the KWQPageState methods will raise
1527 // exceptions, since KWQPageState is implemented by WebCore and
1530 DocumentImpl *doc = [state document];
1531 NodeImpl *mousePressNode = [state mousePressNode];
1532 KURL *url = [state URL];
1533 SavedProperties *windowProperties = [state windowProperties];
1534 SavedProperties *locationProperties = [state locationProperties];
1535 SavedBuiltins *interpreterBuiltins = [state interpreterBuiltins];
1536 QMap<int, ScheduledAction*> *actions = [state pausedActions];
1538 cancelRedirection();
1540 // We still have to close the previous part page.
1541 if (!d->m_restored){
1545 d->m_bComplete = false;
1547 // Don't re-emit the load event.
1548 d->m_bLoadEventEmitted = true;
1550 // delete old status bar msg's from kjs (if it _was_ activated on last URL)
1551 if( d->m_bJScriptEnabled )
1553 d->m_kjsStatusBarText = QString::null;
1554 d->m_kjsDefaultStatusBarText = QString::null;
1561 // initializing m_url to the new url breaks relative links when opening such a link after this call and _before_ begin() is called (when the first
1562 // data arrives) (Simon)
1563 if(m_url.protocol().startsWith( "http" ) && !m_url.host().isEmpty() && m_url.path().isEmpty()) {
1565 emit d->m_extension->setLocationBarURL( m_url.prettyURL() );
1568 // copy to m_workingURL after fixing m_url above
1569 d->m_workingURL = m_url;
1573 // -----------begin-----------
1576 doc->setInPageCache(NO);
1578 d->m_bCleared = false;
1580 d->m_bComplete = false;
1581 d->m_bLoadEventEmitted = false;
1582 d->m_referrer = m_url.url();
1584 setView(doc->view());
1589 d->m_mousePressNode.reset(mousePressNode);
1591 Decoder *decoder = doc->decoder();
1596 d->m_decoder->deref();
1598 d->m_decoder = decoder;
1600 doc->setParseMode ([state parseMode]);
1602 updatePolicyBaseURL();
1604 restoreWindowProperties (windowProperties);
1605 restoreLocationProperties (locationProperties);
1606 restoreInterpreterBuiltins (*interpreterBuiltins);
1609 resumeActions (actions, state);
1614 KWQKHTMLPart *KWQKHTMLPart::partForWidget(const QWidget *widget)
1616 ASSERT_ARG(widget, widget);
1618 NodeImpl *node = nodeForWidget(widget);
1620 return partForNode(node);
1623 // Assume all widgets are either form controls, or KHTMLViews.
1624 const KHTMLView *view = dynamic_cast<const KHTMLView *>(widget);
1626 return KWQ(view->part());
1629 WebCoreBridge *KWQKHTMLPart::bridgeForWidget(const QWidget *widget)
1631 ASSERT_ARG(widget, widget);
1633 KWQKHTMLPart *part = partForWidget(widget);
1635 return part->bridge();
1638 KWQKHTMLPart *KWQKHTMLPart::partForNode(NodeImpl *node)
1640 ASSERT_ARG(node, node);
1641 return KWQ(node->getDocument()->part());
1644 NSView *KWQKHTMLPart::documentViewForNode(DOM::NodeImpl *node)
1646 WebCoreBridge *bridge = partForNode(node)->bridge();
1647 return [bridge documentView];
1650 NodeImpl *KWQKHTMLPart::nodeForWidget(const QWidget *widget)
1652 ASSERT_ARG(widget, widget);
1653 const QObject *o = widget->eventFilterObject();
1654 return o ? static_cast<const RenderWidget *>(o)->element() : 0;
1657 void KWQKHTMLPart::setDocumentFocus(QWidget *widget)
1659 NodeImpl *node = nodeForWidget(widget);
1661 node->getDocument()->setFocusNode(node);
1663 ERROR("unable to clear focus because widget had no corresponding node");
1667 void KWQKHTMLPart::clearDocumentFocus(QWidget *widget)
1669 NodeImpl *node = nodeForWidget(widget);
1671 node->getDocument()->setFocusNode(0);
1673 ERROR("unable to clear focus because widget had no corresponding node");
1677 void KWQKHTMLPart::saveDocumentState()
1679 // Do not save doc state if the page has a password field and a form that would be submitted
1681 if (!(d->m_doc && d->m_doc->hasPasswordField() && d->m_doc->hasSecureForm())) {
1682 KWQ_BLOCK_EXCEPTIONS;
1683 [_bridge saveDocumentState];
1684 KWQ_UNBLOCK_EXCEPTIONS;
1688 void KWQKHTMLPart::restoreDocumentState()
1690 KWQ_BLOCK_EXCEPTIONS;
1691 [_bridge restoreDocumentState];
1692 KWQ_UNBLOCK_EXCEPTIONS;
1695 QPtrList<KWQKHTMLPart> &KWQKHTMLPart::mutableInstances()
1697 static QPtrList<KWQKHTMLPart> instancesList;
1698 return instancesList;
1701 void KWQKHTMLPart::updatePolicyBaseURL()
1703 if (parentPart() && parentPart()->xmlDocImpl()) {
1704 setPolicyBaseURL(parentPart()->xmlDocImpl()->policyBaseURL());
1706 setPolicyBaseURL(m_url.url());
1710 void KWQKHTMLPart::setPolicyBaseURL(const DOMString &s)
1713 xmlDocImpl()->setPolicyBaseURL(s);
1714 ConstFrameIt end = d->m_frames.end();
1715 for (ConstFrameIt it = d->m_frames.begin(); it != end; ++it) {
1716 ReadOnlyPart *subpart = (*it).m_part;
1717 static_cast<KWQKHTMLPart *>(subpart)->setPolicyBaseURL(s);
1721 QString KWQKHTMLPart::requestedURLString() const
1723 KWQ_BLOCK_EXCEPTIONS;
1724 return QString::fromNSString([_bridge requestedURLString]);
1725 KWQ_UNBLOCK_EXCEPTIONS;
1730 QString KWQKHTMLPart::incomingReferrer() const
1732 KWQ_BLOCK_EXCEPTIONS;
1733 return QString::fromNSString([_bridge incomingReferrer]);
1734 KWQ_UNBLOCK_EXCEPTIONS;
1739 void KWQKHTMLPart::forceLayout()
1741 KHTMLView *v = d->m_view;
1744 // We cannot unschedule a pending relayout, since the force can be called with
1745 // a tiny rectangle from a drawRect update. By unscheduling we in effect
1746 // "validate" and stop the necessary full repaint from occurring. Basically any basic
1747 // append/remove DHTML is broken by this call. For now, I have removed the optimization
1748 // until we have a better invalidation stategy. -dwh
1749 //v->unscheduleRelayout();
1753 void KWQKHTMLPart::forceLayoutWithPageWidthRange(float minPageWidth, float maxPageWidth)
1755 // Dumping externalRepresentation(_part->renderer()).ascii() is a good trick to see
1756 // the state of things before and after the layout
1757 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
1759 // This magic is basically copied from khtmlview::print
1760 int pageW = (int)ceil(minPageWidth);
1761 root->setWidth(pageW);
1762 root->setNeedsLayoutAndMinMaxRecalc();
1765 // If we don't fit in the minimum page width, we'll lay out again. If we don't fit in the
1766 // maximum page width, we will lay out to the maximum page width and clip extra content.
1767 // FIXME: We are assuming a shrink-to-fit printing implementation. A cropping
1768 // implementation should not do this!
1769 int rightmostPos = root->rightmostPosition();
1770 if (rightmostPos > minPageWidth) {
1771 pageW = kMin(rightmostPos, (int)ceil(maxPageWidth));
1772 root->setWidth(pageW);
1773 root->setNeedsLayoutAndMinMaxRecalc();
1779 void KWQKHTMLPart::sendResizeEvent()
1781 KHTMLView *v = d->m_view;
1788 void KWQKHTMLPart::sendScrollEvent()
1790 KHTMLView *v = d->m_view;
1792 DocumentImpl *doc = xmlDocImpl();
1795 doc->dispatchHTMLEvent(EventImpl::SCROLL_EVENT, true, false);
1799 void KWQKHTMLPart::runJavaScriptAlert(const QString &message)
1801 QString text = message;
1802 text.replace(QChar('\\'), backslashAsCurrencySymbol());
1803 KWQ_BLOCK_EXCEPTIONS;
1804 [_bridge runJavaScriptAlertPanelWithMessage:text.getNSString()];
1805 KWQ_UNBLOCK_EXCEPTIONS;
1808 bool KWQKHTMLPart::runJavaScriptConfirm(const QString &message)
1810 QString text = message;
1811 text.replace(QChar('\\'), backslashAsCurrencySymbol());
1813 KWQ_BLOCK_EXCEPTIONS;
1814 return [_bridge runJavaScriptConfirmPanelWithMessage:text.getNSString()];
1815 KWQ_UNBLOCK_EXCEPTIONS;
1820 bool KWQKHTMLPart::runJavaScriptPrompt(const QString &prompt, const QString &defaultValue, QString &result)
1822 QString promptText = prompt;
1823 promptText.replace(QChar('\\'), backslashAsCurrencySymbol());
1824 QString defaultValueText = defaultValue;
1825 defaultValueText.replace(QChar('\\'), backslashAsCurrencySymbol());
1827 KWQ_BLOCK_EXCEPTIONS;
1828 NSString *returnedText = nil;
1830 bool ok = [_bridge runJavaScriptTextInputPanelWithPrompt:prompt.getNSString()
1831 defaultText:defaultValue.getNSString() returningText:&returnedText];
1834 result = QString::fromNSString(returnedText);
1835 result.replace(backslashAsCurrencySymbol(), QChar('\\'));
1839 KWQ_UNBLOCK_EXCEPTIONS;
1844 bool KWQKHTMLPart::locationbarVisible()
1846 return [_bridge areToolbarsVisible];
1849 bool KWQKHTMLPart::menubarVisible()
1851 // The menubar is always on in Mac OS X UI
1855 bool KWQKHTMLPart::personalbarVisible()
1857 return [_bridge areToolbarsVisible];
1860 bool KWQKHTMLPart::scrollbarsVisible()
1865 if (view()->hScrollBarMode() == QScrollView::AlwaysOff || view()->vScrollBarMode() == QScrollView::AlwaysOff)
1871 bool KWQKHTMLPart::statusbarVisible()
1873 return [_bridge isStatusBarVisible];
1876 bool KWQKHTMLPart::toolbarVisible()
1878 return [_bridge areToolbarsVisible];
1881 void KWQKHTMLPart::addMessageToConsole(const QString &message, unsigned lineNumber, const QString &sourceURL)
1883 NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
1884 message.getNSString(), @"message",
1885 [NSNumber numberWithInt: lineNumber], @"lineNumber",
1886 sourceURL.getNSString(), @"sourceURL",
1888 [_bridge addMessageToConsole:dictionary];
1891 void KWQKHTMLPart::createEmptyDocument()
1893 // Although it's not completely clear from the name of this function,
1894 // it does nothing if we already have a document, and just creates an
1895 // empty one if we have no document at all.
1897 KWQ_BLOCK_EXCEPTIONS;
1898 [_bridge loadEmptyDocumentSynchronously];
1899 KWQ_UNBLOCK_EXCEPTIONS;
1901 if (parentPart() && (parentPart()->childFrame(this)->m_type == ChildFrame::IFrame ||
1902 parentPart()->childFrame(this)->m_type == ChildFrame::Object)) {
1903 d->m_doc->setBaseURL(parentPart()->d->m_doc->baseURL());
1908 void KWQKHTMLPart::addMetaData(const QString &key, const QString &value)
1910 d->m_job->addMetaData(key, value);
1913 bool KWQKHTMLPart::keyEvent(NSEvent *event)
1915 KWQ_BLOCK_EXCEPTIONS;
1917 ASSERT([event type] == NSKeyDown || [event type] == NSKeyUp);
1919 // Check for cases where we are too early for events -- possible unmatched key up
1920 // from pressing return in the location bar.
1921 DocumentImpl *doc = xmlDocImpl();
1925 NodeImpl *node = doc->focusNode();
1933 if ([event type] == NSKeyDown) {
1934 prepareForUserAction();
1937 NSEvent *oldCurrentEvent = _currentEvent;
1938 _currentEvent = KWQRetain(event);
1940 QKeyEvent qEvent(event);
1941 bool result = !node->dispatchKeyEvent(&qEvent);
1943 // We want to send both a down and a press for the initial key event.
1944 // To get KHTML to do this, we send a second KeyPress QKeyEvent with "is repeat" set to true,
1945 // which causes it to send a press to the DOM.
1946 // That's not a great hack; it would be good to do this in a better way.
1947 if ([event type] == NSKeyDown && ![event isARepeat]) {
1948 QKeyEvent repeatEvent(event, true);
1949 if (!node->dispatchKeyEvent(&repeatEvent)) {
1954 ASSERT(_currentEvent == event);
1956 _currentEvent = oldCurrentEvent;
1960 KWQ_UNBLOCK_EXCEPTIONS;
1965 // This does the same kind of work that KHTMLPart::openURL does, except it relies on the fact
1966 // that a higher level already checked that the URLs match and the scrolling is the right thing to do.
1967 void KWQKHTMLPart::scrollToAnchor(const KURL &URL)
1974 // It's important to model this as a load that starts and immediately finishes.
1975 // Otherwise, the parent frame may think we never finished loading.
1976 d->m_bComplete = false;
1980 bool KWQKHTMLPart::closeURL()
1982 saveDocumentState();
1983 return KHTMLPart::closeURL();
1986 void KWQKHTMLPart::khtmlMousePressEvent(MousePressEvent *event)
1988 bool singleClick = [_currentEvent clickCount] <= 1;
1990 // If we got the event back, that must mean it wasn't prevented,
1991 // so it's allowed to start a drag or selection.
1992 _mouseDownMayStartSelect = true;
1993 // Careful that the drag starting logic stays in sync with eventMayStartDrag()
1994 _mouseDownMayStartDrag = singleClick;
1996 d->m_mousePressNode.reset(event->innerNode());
1998 if (!passWidgetMouseDownEventToWidget(event)) {
1999 // We don't do this at the start of mouse down handling (before calling into WebCore),
2000 // because we don't want to do it until we know we didn't hit a widget.
2001 NSView *view = d->m_view->getDocumentView();
2004 KWQ_BLOCK_EXCEPTIONS;
2005 if ([_bridge firstResponder] != view) {
2006 [_bridge makeFirstResponder:view];
2008 KWQ_UNBLOCK_EXCEPTIONS;
2011 KHTMLPart::khtmlMousePressEvent(event);
2015 void KWQKHTMLPart::khtmlMouseDoubleClickEvent(MouseDoubleClickEvent *event)
2017 if (!passWidgetMouseDownEventToWidget(event)) {
2018 KHTMLPart::khtmlMouseDoubleClickEvent(event);
2022 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(khtml::MouseEvent *event)
2024 // Figure out which view to send the event to.
2025 RenderObject *target = event->innerNode() ? event->innerNode()->renderer() : 0;
2029 QWidget* widget = RenderLayer::gScrollBar;
2031 if (!target->isWidget())
2033 widget = static_cast<RenderWidget *>(target)->widget();
2036 // Doubleclick events don't exist in Cocoa. Since passWidgetMouseDownEventToWidget will
2037 // just pass _currentEvent down to the widget, we don't want to call it for events that
2038 // don't correspond to Cocoa events. The mousedown/ups will have already been passed on as
2039 // part of the pressed/released handling.
2040 if (!MouseDoubleClickEvent::test(event))
2041 return passWidgetMouseDownEventToWidget(widget);
2046 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(RenderWidget *renderWidget)
2048 return passWidgetMouseDownEventToWidget(renderWidget->widget());
2051 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(QWidget* widget)
2053 // FIXME: this method always returns true
2056 ERROR("hit a RenderWidget without a corresponding QWidget, means a frame is half-constructed");
2060 KWQ_BLOCK_EXCEPTIONS;
2062 NSView *nodeView = widget->getView();
2064 ASSERT([nodeView superview]);
2065 NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[_currentEvent locationInWindow] fromView:nil]];
2067 ERROR("KHTML says we hit a RenderWidget, but AppKit doesn't agree we hit the corresponding NSView");
2071 if ([_bridge firstResponder] == view) {
2072 // In the case where we just became first responder, we should send the mouseDown:
2073 // to the NSTextField, not the NSTextField's editor. This code makes sure that happens.
2074 // If we don't do this, we see a flash of selected text when clicking in a text field.
2075 if (![_bridge wasFirstResponderAtMouseDownTime:view] && [view isKindOfClass:[NSTextView class]]) {
2076 NSView *superview = view;
2077 while (superview != nodeView) {
2078 superview = [superview superview];
2080 if ([superview isKindOfClass:[NSControl class]]) {
2081 NSControl *control = superview;
2082 if ([control currentEditor] == view) {
2090 // Normally [NSWindow sendEvent:] handles setting the first responder.
2091 // But in our case, the event was sent to the view representing the entire web page.
2092 if ([_currentEvent clickCount] <= 1 && [view acceptsFirstResponder] && [view needsPanelToBecomeKey]) {
2093 [_bridge makeFirstResponder:view];
2097 // We need to "defer loading" and defer timers while we are tracking the mouse.
2098 // That's because we don't want the new page to load while the user is holding the mouse down.
2100 BOOL wasDeferringLoading = [_bridge defersLoading];
2101 if (!wasDeferringLoading) {
2102 [_bridge setDefersLoading:YES];
2104 BOOL wasDeferringTimers = QObject::defersTimers();
2105 if (!wasDeferringTimers) {
2106 QObject::setDefersTimers(true);
2109 ASSERT(!_sendingEventToSubview);
2110 _sendingEventToSubview = true;
2111 [view mouseDown:_currentEvent];
2112 _sendingEventToSubview = false;
2114 if (!wasDeferringTimers) {
2115 QObject::setDefersTimers(false);
2117 if (!wasDeferringLoading) {
2118 [_bridge setDefersLoading:NO];
2121 // Remember which view we sent the event to, so we can direct the release event properly.
2122 _mouseDownView = view;
2123 _mouseDownWasInSubframe = false;
2125 KWQ_UNBLOCK_EXCEPTIONS;
2130 bool KWQKHTMLPart::lastEventIsMouseUp() const
2132 // Many AK widgets run their own event loops and consume events while the mouse is down.
2133 // When they finish, currentEvent is the mouseUp that they exited on. We need to update
2134 // the khtml state with this mouseUp, which khtml never saw. This method lets us detect
2137 KWQ_BLOCK_EXCEPTIONS;
2138 NSEvent *currentEventAfterHandlingMouseDown = [NSApp currentEvent];
2139 if (_currentEvent != currentEventAfterHandlingMouseDown) {
2140 if ([currentEventAfterHandlingMouseDown type] == NSLeftMouseUp) {
2144 KWQ_UNBLOCK_EXCEPTIONS;
2149 // Note that this does the same kind of check as [target isDescendantOf:superview].
2150 // There are two differences: This is a lot slower because it has to walk the whole
2151 // tree, and this works in cases where the target has already been deallocated.
2152 static bool findViewInSubviews(NSView *superview, NSView *target)
2154 KWQ_BLOCK_EXCEPTIONS;
2155 NSEnumerator *e = [[superview subviews] objectEnumerator];
2157 while ((subview = [e nextObject])) {
2158 if (subview == target || findViewInSubviews(subview, target)) {
2162 KWQ_UNBLOCK_EXCEPTIONS;
2167 NSView *KWQKHTMLPart::mouseDownViewIfStillGood()
2169 // Since we have no way of tracking the lifetime of _mouseDownView, we have to assume that
2170 // it could be deallocated already. We search for it in our subview tree; if we don't find
2171 // it, we set it to nil.
2172 NSView *mouseDownView = _mouseDownView;
2173 if (!mouseDownView) {
2176 KHTMLView *topKHTMLView = d->m_view;
2177 NSView *topView = topKHTMLView ? topKHTMLView->getView() : nil;
2178 if (!topView || !findViewInSubviews(topView, mouseDownView)) {
2179 _mouseDownView = nil;
2182 return mouseDownView;
2185 // The link drag hysteresis is much larger than the others because there
2186 // needs to be enough space to cancel the link press without starting a link drag,
2187 // and because dragging links is rare.
2188 #define LinkDragHysteresis 40.0
2189 #define ImageDragHysteresis 5.0
2190 #define TextDragHysteresis 3.0
2191 #define GeneralDragHysteresis 3.0
2193 #define TextDragDelay 0.15
2195 bool KWQKHTMLPart::dragHysteresisExceeded(float dragLocationX, float dragLocationY) const
2198 d->m_view->viewportToContents((int)dragLocationX, (int)dragLocationY, dragX, dragY);
2199 float deltaX = QABS(dragX - _mouseDownX);
2200 float deltaY = QABS(dragY - _mouseDownY);
2202 float threshold = GeneralDragHysteresis;
2203 if (_dragSrcIsImage) {
2204 threshold = ImageDragHysteresis;
2205 } else if (_dragSrcIsLink) {
2206 threshold = LinkDragHysteresis;
2207 } else if (_dragSrcInSelection) {
2208 threshold = TextDragHysteresis;
2210 return deltaX >= threshold || deltaY >= threshold;
2213 // returns if we should continue "default processing", i.e., whether eventhandler canceled
2214 bool KWQKHTMLPart::dispatchDragSrcEvent(int eventId, const QPoint &loc) const
2216 bool noDefaultProc = d->m_view->dispatchDragEvent(eventId, _dragSrc.get(), loc, _dragClipboard);
2217 return !noDefaultProc;
2220 bool KWQKHTMLPart::eventMayStartDrag(NSEvent *event) const
2222 // This is a pre-flight check of whether the event might lead to a drag being started. Be careful
2223 // that its logic needs to stay in sync with khtmlMouseMoveEvent() and the way we set
2224 // _mouseDownMayStartDrag in khtmlMousePressEvent
2226 if ([event type] != NSLeftMouseDown || [event clickCount] != 1) {
2230 BOOL DHTMLFlag, UAFlag;
2231 [_bridge allowDHTMLDrag:&DHTMLFlag UADrag:&UAFlag];
2232 if (!DHTMLFlag && !UAFlag) {
2236 NSPoint loc = [event locationInWindow];
2237 int mouseDownX, mouseDownY;
2238 d->m_view->viewportToContents((int)loc.x, (int)loc.y, mouseDownX, mouseDownY);
2239 RenderObject::NodeInfo nodeInfo(true, false);
2240 renderer()->layer()->hitTest(nodeInfo, mouseDownX, mouseDownY);
2242 return nodeInfo.innerNode()->renderer()->draggableNode(DHTMLFlag, UAFlag, mouseDownX, mouseDownY, srcIsDHTML);
2245 void KWQKHTMLPart::khtmlMouseMoveEvent(MouseMoveEvent *event)
2247 KWQ_BLOCK_EXCEPTIONS;
2249 if ([_currentEvent type] == NSLeftMouseDragged) {
2250 NSView *view = mouseDownViewIfStillGood();
2253 _sendingEventToSubview = true;
2254 [view mouseDragged:_currentEvent];
2255 _sendingEventToSubview = false;
2259 // Careful that the drag starting logic stays in sync with eventMayStartDrag()
2261 if (_mouseDownMayStartDrag && _dragSrc.isNull()) {
2262 BOOL tempFlag1, tempFlag2;
2263 [_bridge allowDHTMLDrag:&tempFlag1 UADrag:&tempFlag2];
2264 _dragSrcMayBeDHTML = tempFlag1;
2265 _dragSrcMayBeUA = tempFlag2;
2266 if (!_dragSrcMayBeDHTML && !_dragSrcMayBeUA) {
2267 _mouseDownMayStartDrag = false; // no element is draggable
2271 if (_mouseDownMayStartDrag && _dragSrc.isNull()) {
2272 // try to find an element that wants to be dragged
2273 RenderObject::NodeInfo nodeInfo(true, false);
2274 renderer()->layer()->hitTest(nodeInfo, _mouseDownX, _mouseDownY);
2275 _dragSrc.reset(nodeInfo.innerNode()->renderer()->draggableNode(_dragSrcMayBeDHTML, _dragSrcMayBeUA, _mouseDownX, _mouseDownY, _dragSrcIsDHTML));
2276 if (_dragSrc.isNull()) {
2277 _mouseDownMayStartDrag = false; // no element is draggable
2279 // remember some facts about this source, while we have a NodeInfo handy
2280 NodeImpl *node = nodeInfo.URLElement();
2281 _dragSrcIsLink = node ? node->isLink() : false;
2283 node = nodeInfo.innerNonSharedNode();
2284 _dragSrcIsImage = (node && node->renderer() && node->renderer()->isImage());
2286 _dragSrcInSelection = isPointInsideSelection(_mouseDownX, _mouseDownY);
2290 // For drags starting in the selection, the user must wait between the mousedown and mousedrag,
2291 // or else we bail on the dragging stuff and allow selection to occur
2292 if (_mouseDownMayStartDrag && _dragSrcInSelection && [_currentEvent timestamp] - _mouseDownTimestamp < TextDragDelay) {
2293 _mouseDownMayStartDrag = false;
2294 // ...but if this was the first click in the window, we don't even want to start selection
2295 if (_activationEventNumber == [_currentEvent eventNumber]) {
2296 _mouseDownMayStartSelect = false;
2300 if (_mouseDownMayStartDrag) {
2301 // We are starting a text/image/url drag, so the cursor should be an arrow
2302 d->m_view->resetCursor();
2304 NSPoint dragLocation = [_currentEvent locationInWindow];
2305 if (dragHysteresisExceeded(dragLocation.x, dragLocation.y)) {
2307 // Once we're past the hysteresis point, we don't want to treat this gesture as a click
2308 d->m_view->invalidateClick();
2310 NSImage *dragImage = nil; // we use these values if WC is out of the loop
2311 NSPoint dragLoc = NSZeroPoint;
2312 NSDragOperation srcOp = NSDragOperationNone;
2313 BOOL wcWrotePasteboard = NO;
2314 if (_dragSrcMayBeDHTML) {
2315 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
2316 // Must be done before ondragstart adds types and data to the pboard,
2317 // also done for security, as it erases data from the last drag
2318 [pasteboard declareTypes:[NSArray array] owner:nil];
2320 freeClipboard(); // would only happen if we missed a dragEnd. Do it anyway, just
2321 // to make sure it gets numbified
2322 _dragClipboard = new KWQClipboard(true, pasteboard, KWQClipboard::Writable, this);
2323 _dragClipboard->ref();
2325 // If this is drag of an element, get set up to generate a default image. Otherwise
2326 // WebKit will generate the default, the element doesn't override.
2327 if (_dragSrcIsDHTML) {
2329 _dragSrc->renderer()->absolutePosition(srcX, srcY);
2330 _dragClipboard->setDragImageElement(_dragSrc.get(), QPoint(_mouseDownX - srcX, _mouseDownY - srcY));
2333 _mouseDownMayStartDrag = dispatchDragSrcEvent(EventImpl::DRAGSTART_EVENT, QPoint(_mouseDownWinX, _mouseDownWinY));
2334 // Invalidate clipboard here against anymore pasteboard writing for security. The drag
2335 // image can still be changed as we drag, but not the pasteboard data.
2336 _dragClipboard->setAccessPolicy(KWQClipboard::ImageWritable);
2338 if (_mouseDownMayStartDrag) {
2339 // gather values from DHTML element, if it set any
2340 _dragClipboard->sourceOperation(&srcOp);
2342 NSArray *types = [pasteboard types];
2343 wcWrotePasteboard = types && [types count] > 0;
2345 if (_dragSrcMayBeDHTML) {
2346 dragImage = _dragClipboard->dragNSImage(&dragLoc);
2349 // Yuck, dragSourceMovedTo() can be called as a result of kicking off the drag with
2350 // dragImage! Because of that dumb reentrancy, we may think we've not started the
2351 // drag when that happens. So we have to assume it's started before we kick it off.
2352 _dragClipboard->setDragHasStarted();
2356 if (_mouseDownMayStartDrag) {
2357 BOOL startedDrag = [_bridge startDraggingImage:dragImage at:dragLoc operation:srcOp event:_currentEvent sourceIsDHTML:_dragSrcIsDHTML DHTMLWroteData:wcWrotePasteboard];
2358 if (!startedDrag && _dragSrcMayBeDHTML) {
2359 // WebKit canned the drag at the last minute - we owe _dragSrc a DRAGEND event
2360 dispatchDragSrcEvent(EventImpl::DRAGEND_EVENT, QPoint(dragLocation));
2361 _mouseDownMayStartDrag = false;
2365 if (!_mouseDownMayStartDrag) {
2366 // something failed to start the drag, cleanup
2372 // No more default handling (like selection), whether we're past the hysteresis bounds or not
2375 if (!_mouseDownMayStartSelect) {
2379 // Don't allow dragging or click handling after we've started selecting.
2380 _mouseDownMayStartDrag = false;
2381 d->m_view->invalidateClick();
2383 // We use khtml's selection but our own autoscrolling.
2384 [_bridge handleAutoscrollForMouseDragged:_currentEvent];
2386 // If we allowed the other side of the bridge to handle a drag
2387 // last time, then m_bMousePressed might still be set. So we
2388 // clear it now to make sure the next move after a drag
2389 // doesn't look like a drag.
2390 d->m_bMousePressed = false;
2393 KHTMLPart::khtmlMouseMoveEvent(event);
2395 KWQ_UNBLOCK_EXCEPTIONS;
2398 void KWQKHTMLPart::dragSourceMovedTo(const QPoint &loc)
2400 if (!_dragSrc.isNull() && _dragSrcMayBeDHTML) {
2401 // for now we don't care if event handler cancels default behavior, since there is none
2402 dispatchDragSrcEvent(EventImpl::DRAG_EVENT, loc);
2406 void KWQKHTMLPart::dragSourceEndedAt(const QPoint &loc, NSDragOperation operation)
2408 if (!_dragSrc.isNull() && _dragSrcMayBeDHTML) {
2409 _dragClipboard->setDestinationOperation(operation);
2410 // for now we don't care if event handler cancels default behavior, since there is none
2411 dispatchDragSrcEvent(EventImpl::DRAGEND_EVENT, loc);
2417 // Returns whether caller should continue with "the default processing", which is the same as
2418 // the event handler NOT setting the return value to false
2419 bool KWQKHTMLPart::dispatchCPPEvent(int eventId, KWQClipboard::AccessPolicy policy)
2421 NodeImpl *target = d->m_selection.start().element();
2422 if (!target && xmlDocImpl()) {
2423 target = xmlDocImpl()->body();
2429 KWQClipboard *clipboard = new KWQClipboard(false, [NSPasteboard generalPasteboard], (KWQClipboard::AccessPolicy)policy);
2432 int exceptioncode = 0;
2433 EventImpl *evt = new ClipboardEventImpl(static_cast<EventImpl::EventId>(eventId), true, true, clipboard);
2435 target->dispatchEvent(evt, exceptioncode, true);
2436 bool noDefaultProcessing = evt->defaultPrevented();
2439 // invalidate clipboard here for security
2440 clipboard->setAccessPolicy(KWQClipboard::Numb);
2443 return !noDefaultProcessing;
2446 // WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They
2447 // also send onbeforecopy, apparently for symmetry, but it doesn't affect the menu items.
2448 // We need to use onbeforecopy as a real menu enabler because we allow elements that are not
2449 // normally selectable to implement copy/paste (like divs, or a document body).
2451 bool KWQKHTMLPart::mayCut()
2453 return !dispatchCPPEvent(EventImpl::BEFORECUT_EVENT, KWQClipboard::Numb);
2456 bool KWQKHTMLPart::mayCopy()
2458 return !dispatchCPPEvent(EventImpl::BEFORECOPY_EVENT, KWQClipboard::Numb);
2461 bool KWQKHTMLPart::mayPaste()
2463 return !dispatchCPPEvent(EventImpl::BEFOREPASTE_EVENT, KWQClipboard::Numb);
2466 bool KWQKHTMLPart::tryCut()
2468 // Must be done before oncut adds types and data to the pboard,
2469 // also done for security, as it erases data from the last copy/paste.
2470 [[NSPasteboard generalPasteboard] declareTypes:[NSArray array] owner:nil];
2472 return !dispatchCPPEvent(EventImpl::CUT_EVENT, KWQClipboard::Writable);
2475 bool KWQKHTMLPart::tryCopy()
2477 // Must be done before oncopy adds types and data to the pboard,
2478 // also done for security, as it erases data from the last copy/paste.
2479 [[NSPasteboard generalPasteboard] declareTypes:[NSArray array] owner:nil];
2481 return !dispatchCPPEvent(EventImpl::COPY_EVENT, KWQClipboard::Writable);
2484 bool KWQKHTMLPart::tryPaste()
2486 return !dispatchCPPEvent(EventImpl::PASTE_EVENT, KWQClipboard::Readable);
2489 void KWQKHTMLPart::khtmlMouseReleaseEvent(MouseReleaseEvent *event)
2491 NSView *view = mouseDownViewIfStillGood();
2493 // If this was the first click in the window, we don't even want to clear the selection.
2494 // This case occurs when the user clicks on a draggable element, since we have to process
2495 // the mouse down and drag events to see if we might start a drag. For other first clicks
2496 // in a window, we just don't acceptFirstMouse, and the whole down-drag-up sequence gets
2497 // ignored upstream of this layer.
2498 if (_activationEventNumber != [_currentEvent eventNumber]) {
2499 KHTMLPart::khtmlMouseReleaseEvent(event);
2504 _sendingEventToSubview = true;
2505 KWQ_BLOCK_EXCEPTIONS;
2506 [view mouseUp:_currentEvent];
2507 KWQ_UNBLOCK_EXCEPTIONS;
2508 _sendingEventToSubview = false;
2511 void KWQKHTMLPart::clearTimers(KHTMLView *view)
2514 view->unscheduleRelayout();
2516 DocumentImpl* document = view->part()->xmlDocImpl();
2517 if (document && document->renderer() && document->renderer()->layer())
2518 document->renderer()->layer()->suspendMarquees();
2523 void KWQKHTMLPart::clearTimers()
2525 clearTimers(d->m_view);
2528 bool KWQKHTMLPart::passSubframeEventToSubframe(NodeImpl::MouseEvent &event)
2530 KWQ_BLOCK_EXCEPTIONS;
2532 switch ([_currentEvent type]) {
2533 case NSLeftMouseDown: {
2534 NodeImpl *node = event.innerNode.get();
2538 RenderPart *renderPart = dynamic_cast<RenderPart *>(node->renderer());
2542 if (!passWidgetMouseDownEventToWidget(renderPart)) {
2545 _mouseDownWasInSubframe = true;
2548 case NSLeftMouseUp: {
2549 if (!_mouseDownWasInSubframe) {
2552 NSView *view = mouseDownViewIfStillGood();
2556 ASSERT(!_sendingEventToSubview);
2557 _sendingEventToSubview = true;
2558 [view mouseUp:_currentEvent];
2559 _sendingEventToSubview = false;
2562 case NSLeftMouseDragged: {
2563 if (!_mouseDownWasInSubframe) {
2566 NSView *view = mouseDownViewIfStillGood();
2570 ASSERT(!_sendingEventToSubview);
2571 _sendingEventToSubview = true;
2572 [view mouseDragged:_currentEvent];
2573 _sendingEventToSubview = false;
2579 KWQ_UNBLOCK_EXCEPTIONS;
2584 void KWQKHTMLPart::mouseDown(NSEvent *event)
2586 KHTMLView *v = d->m_view;
2587 if (!v || _sendingEventToSubview) {
2591 KWQ_BLOCK_EXCEPTIONS;
2593 prepareForUserAction();
2595 _mouseDownView = nil;
2598 NSEvent *oldCurrentEvent = _currentEvent;
2599 _currentEvent = KWQRetain(event);
2600 NSPoint loc = [event locationInWindow];
2601 _mouseDownWinX = (int)loc.x;
2602 _mouseDownWinY = (int)loc.y;
2603 d->m_view->viewportToContents(_mouseDownWinX, _mouseDownWinY, _mouseDownX, _mouseDownY);
2604 _mouseDownTimestamp = [event timestamp];
2606 _mouseDownMayStartDrag = false;
2607 _mouseDownMayStartSelect = false;
2609 QMouseEvent kEvent(QEvent::MouseButtonPress, event);
2610 v->viewportMousePressEvent(&kEvent);
2612 ASSERT(_currentEvent == event);
2614 _currentEvent = oldCurrentEvent;
2616 KWQ_UNBLOCK_EXCEPTIONS;
2619 void KWQKHTMLPart::mouseDragged(NSEvent *event)
2621 KHTMLView *v = d->m_view;
2622 if (!v || _sendingEventToSubview) {
2626 KWQ_BLOCK_EXCEPTIONS;
2628 NSEvent *oldCurrentEvent = _currentEvent;
2629 _currentEvent = KWQRetain(event);
2631 QMouseEvent kEvent(QEvent::MouseMove, event);
2632 v->viewportMouseMoveEvent(&kEvent);
2634 ASSERT(_currentEvent == event);
2636 _currentEvent = oldCurrentEvent;
2638 KWQ_UNBLOCK_EXCEPTIONS;
2641 void KWQKHTMLPart::mouseUp(NSEvent *event)
2643 KHTMLView *v = d->m_view;
2644 if (!v || _sendingEventToSubview) {
2648 KWQ_BLOCK_EXCEPTIONS;
2650 NSEvent *oldCurrentEvent = _currentEvent;
2651 _currentEvent = KWQRetain(event);
2653 // Our behavior here is a little different that Qt. Qt always sends
2654 // a mouse release event, even for a double click. To correct problems
2655 // in khtml's DOM click event handling we do not send a release here
2656 // for a double click. Instead we send that event from KHTMLView's
2657 // viewportMouseDoubleClickEvent. Note also that the third click of
2658 // a triple click is treated as a single click, but the fourth is then
2659 // treated as another double click. Hence the "% 2" below.
2660 int clickCount = [event clickCount];
2661 if (clickCount > 0 && clickCount % 2 == 0) {
2662 QMouseEvent doubleClickEvent(QEvent::MouseButtonDblClick, event);
2663 v->viewportMouseDoubleClickEvent(&doubleClickEvent);
2665 QMouseEvent releaseEvent(QEvent::MouseButtonRelease, event);
2666 v->viewportMouseReleaseEvent(&releaseEvent);
2669 ASSERT(_currentEvent == event);
2671 _currentEvent = oldCurrentEvent;
2673 _mouseDownView = nil;
2675 KWQ_UNBLOCK_EXCEPTIONS;
2679 A hack for the benefit of AK's PopUpButton, which uses the Carbon menu manager, which thus
2680 eats all subsequent events after it is starts its modal tracking loop. After the interaction
2681 is done, this routine is used to fix things up. When a mouse down started us tracking in
2682 the widget, we post a fake mouse up to balance the mouse down we started with. When a
2683 key down started us tracking in the widget, we post a fake key up to balance things out.
2684 In addition, we post a fake mouseMoved to get the cursor in sync with whatever we happen to
2685 be over after the tracking is done.
2687 void KWQKHTMLPart::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent)
2689 KWQ_BLOCK_EXCEPTIONS;
2691 _sendingEventToSubview = false;
2692 int eventType = [initiatingEvent type];
2693 ASSERT(eventType == NSLeftMouseDown || eventType == NSKeyDown);
2694 NSEvent *fakeEvent = nil;
2695 if (eventType == NSLeftMouseDown) {
2696 fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
2697 location:[initiatingEvent locationInWindow]
2698 modifierFlags:[initiatingEvent modifierFlags]
2699 timestamp:[initiatingEvent timestamp]
2700 windowNumber:[initiatingEvent windowNumber]
2701 context:[initiatingEvent context]
2702 eventNumber:[initiatingEvent eventNumber]
2703 clickCount:[initiatingEvent clickCount]
2704 pressure:[initiatingEvent pressure]];
2708 else { // eventType == NSKeyDown
2709 fakeEvent = [NSEvent keyEventWithType:NSKeyUp
2710 location:[initiatingEvent locationInWindow]
2711 modifierFlags:[initiatingEvent modifierFlags]
2712 timestamp:[initiatingEvent timestamp]
2713 windowNumber:[initiatingEvent windowNumber]
2714 context:[initiatingEvent context]
2715 characters:[initiatingEvent characters]
2716 charactersIgnoringModifiers:[initiatingEvent charactersIgnoringModifiers]
2717 isARepeat:[initiatingEvent isARepeat]
2718 keyCode:[initiatingEvent keyCode]];
2719 keyEvent(fakeEvent);
2721 // FIXME: We should really get the current modifierFlags here, but there's no way to poll
2722 // them in Cocoa, and because the event stream was stolen by the Carbon menu code we have
2723 // no up-to-date cache of them anywhere.
2724 fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
2725 location:[[_bridge window] convertScreenToBase:[NSEvent mouseLocation]]
2726 modifierFlags:[initiatingEvent modifierFlags]
2727 timestamp:[initiatingEvent timestamp]
2728 windowNumber:[initiatingEvent windowNumber]
2729 context:[initiatingEvent context]
2733 mouseMoved(fakeEvent);
2735 KWQ_UNBLOCK_EXCEPTIONS;
2738 void KWQKHTMLPart::mouseMoved(NSEvent *event)
2740 KHTMLView *v = d->m_view;
2741 // Reject a mouse moved if the button is down - screws up tracking during autoscroll
2742 // These happen because WebKit sometimes has to fake up moved events.
2743 if (!v || d->m_bMousePressed) {
2747 KWQ_BLOCK_EXCEPTIONS;
2749 NSEvent *oldCurrentEvent = _currentEvent;
2750 _currentEvent = KWQRetain(event);
2752 QMouseEvent kEvent(QEvent::MouseMove, event);
2753 v->viewportMouseMoveEvent(&kEvent);
2755 ASSERT(_currentEvent == event);
2757 _currentEvent = oldCurrentEvent;
2759 KWQ_UNBLOCK_EXCEPTIONS;
2762 // Called as we walk up the element chain for nodes with CSS property -khtml-user-drag == auto
2763 bool KWQKHTMLPart::shouldDragAutoNode(DOM::NodeImpl* node, int x, int y) const
2765 // We assume that WebKit only cares about dragging things that can be leaf nodes (text, images, urls).
2766 // This saves a bunch of expensive calls (creating WC and WK element dicts) as we walk farther up
2767 // the node hierarchy, and we also don't have to cook up a way to ask WK about non-leaf nodes
2768 // (since right now WK just hit-tests using a cached lastMouseDown).
2769 if (!node->hasChildNodes() && d->m_view) {
2770 int windowX, windowY;
2771 d->m_view->contentsToViewport(x, y, windowX, windowY);
2772 NSPoint eventLoc = {windowX, windowY};
2773 return [_bridge mayStartDragAtEventLocation:eventLoc];
2779 bool KWQKHTMLPart::sendContextMenuEvent(NSEvent *event)
2781 DocumentImpl *doc = d->m_doc;
2782 KHTMLView *v = d->m_view;
2787 KWQ_BLOCK_EXCEPTIONS;
2789 NSEvent *oldCurrentEvent = _currentEvent;
2790 _currentEvent = KWQRetain(event);
2792 QMouseEvent qev(QEvent::MouseButtonPress, event);
2795 v->viewportToContents(qev.x(), qev.y(), xm, ym);
2797 NodeImpl::MouseEvent mev(qev.stateAfter(), NodeImpl::MousePress);
2798 doc->prepareMouseEvent(false, xm, ym, &mev);
2800 bool swallowEvent = v->dispatchMouseEvent(EventImpl::CONTEXTMENU_EVENT,
2801 mev.innerNode.get(), true, 0, &qev, true, NodeImpl::MousePress);
2802 if (!swallowEvent && !isPointInsideSelection(xm, ym) &&
2803 ([_bridge selectWordBeforeMenuEvent] || [_bridge isEditable] || mev.innerNode->isContentEditable())) {
2804 selectClosestWordFromMouseEvent(&qev, mev.innerNode.get(), xm, ym);
2807 ASSERT(_currentEvent == event);
2809 _currentEvent = oldCurrentEvent;
2811 return swallowEvent;
2813 KWQ_UNBLOCK_EXCEPTIONS;
2818 struct ListItemInfo {
2823 NSFileWrapper *KWQKHTMLPart::fileWrapperForElement(ElementImpl *e)
2825 KWQ_BLOCK_EXCEPTIONS;
2827 NSFileWrapper *wrapper = nil;
2829 AtomicString attr = e->getAttribute(ATTR_SRC);
2830 if (!attr.isEmpty()) {
2831 NSURL *URL = completeURL(attr.string()).getNSURL();
2832 wrapper = [_bridge fileWrapperForURL:URL];
2835 RenderImage *renderer = static_cast<RenderImage *>(e->renderer());
2836 NSImage *image = renderer->pixmap().image();
2837 NSData *tiffData = [image TIFFRepresentationUsingCompression:NSTIFFCompressionLZW factor:0.0];
2838 wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:tiffData];
2839 [wrapper setPreferredFilename:@"image.tiff"];
2840 [wrapper autorelease];
2845 KWQ_UNBLOCK_EXCEPTIONS;
2850 static ElementImpl *listParent(ElementImpl *item)
2852 // Ick! Avoid use of item->id() which confuses ObjC++.
2853 unsigned short _id = item->identifier();
2855 while (_id != ID_UL && _id != ID_OL) {
2856 item = static_cast<ElementImpl *>(item->parentNode());
2859 _id = item->identifier();
2864 static NodeImpl* isTextFirstInListItem(NodeImpl *e)
2866 if (!e->isTextNode())
2868 NodeImpl* par = e->parentNode();
2870 if (par->firstChild() != e)
2872 if (par->identifier() == ID_LI)
2875 par = par->parentNode();
2880 // FIXME: This collosal function needs to be refactored into maintainable smaller bits.
2882 #define BULLET_CHAR 0x2022
2883 #define SQUARE_CHAR 0x25AA
2884 #define CIRCLE_CHAR 0x25E6
2886 NSAttributedString *KWQKHTMLPart::attributedString(NodeImpl *_start, int startOffset, NodeImpl *endNode, int endOffset)
2888 KWQ_BLOCK_EXCEPTIONS;
2890 NodeImpl * _startNode = _start;
2892 if (_startNode == nil) {
2896 NSMutableAttributedString *result = [[[NSMutableAttributedString alloc] init] autorelease];
2898 bool hasNewLine = true;
2899 bool addedSpace = true;
2900 NSAttributedString *pendingStyledSpace = nil;
2901 bool hasParagraphBreak = true;
2902 const ElementImpl *linkStartNode = 0;
2903 unsigned linkStartLocation = 0;
2904 QPtrList<ElementImpl> listItems;
2905 QValueList<ListItemInfo> listItemLocations;
2906 float maxMarkerWidth = 0;
2908 NodeImpl *n = _startNode;
2910 // If the first item is the entire text of a list item, use the list item node as the start of the
2911 // selection, not the text node. The user's intent was probably to select the list.
2912 if (n->isTextNode() && startOffset == 0) {
2913 NodeImpl *startListNode = isTextFirstInListItem(_startNode);
2915 _startNode = startListNode;
2921 RenderObject *renderer = n->renderer();
2923 RenderStyle *style = renderer->style();
2924 NSFont *font = style->font().getNSFont();
2925 bool needSpace = pendingStyledSpace != nil;
2926 if (n->isTextNode()) {
2930 [pendingStyledSpace release];
2931 pendingStyledSpace = nil;
2935 QString str = n->nodeValue().string();
2936 int start = (n == _startNode) ? startOffset : -1;
2937 int end = (n == endNode) ? endOffset : -1;
2938 if (renderer->isText()) {
2939 if (style->whiteSpace() == PRE) {
2940 if (needSpace && !addedSpace) {
2941 if (text.isEmpty() && linkStartLocation == [result length]) {
2942 ++linkStartLocation;
2944 [result appendAttributedString:pendingStyledSpace];
2946 int runStart = (start == -1) ? 0 : start;
2947 int runEnd = (end == -1) ? str.length() : end;
2948 text += str.mid(runStart, runEnd-runStart);
2949 [pendingStyledSpace release];
2950 pendingStyledSpace = nil;
2951 addedSpace = str[runEnd-1].direction() == QChar::DirWS;
2954 RenderText* textObj = static_cast<RenderText*>(renderer);
2955 if (!textObj->firstTextBox() && str.length() > 0 && !addedSpace) {
2956 // We have no runs, but we do have a length. This means we must be
2957 // whitespace that collapsed away at the end of a line.
2963 for (InlineTextBox* box = textObj->firstTextBox(); box; box = box->nextTextBox()) {
2964 int runStart = (start == -1) ? box->m_start : start;
2965 int runEnd = (end == -1) ? box->m_start + box->m_len : end;
2966 runEnd = kMin(runEnd, box->m_start + box->m_len);
2967 if (runStart >= box->m_start &&
2968 runStart < box->m_start + box->m_len) {
2969 if (box == textObj->firstTextBox() && box->m_start == runStart && runStart > 0) {
2970 needSpace = true; // collapsed space at the start
2972 if (needSpace && !addedSpace) {
2973 if (pendingStyledSpace != nil) {
2974 if (text.isEmpty() && linkStartLocation == [result length]) {
2975 ++linkStartLocation;
2977 [result appendAttributedString:pendingStyledSpace];
2982 QString runText = str.mid(runStart, runEnd - runStart);
2983 runText.replace('\n', ' ');
2985 int nextRunStart = box->nextTextBox() ? box->nextTextBox()->m_start : str.length(); // collapsed space between runs or at the end
2986 needSpace = nextRunStart > runEnd;
2987 [pendingStyledSpace release];
2988 pendingStyledSpace = nil;
2989 addedSpace = str[runEnd-1].direction() == QChar::DirWS;
2992 if (end != -1 && runEnd >= end)
2999 text.replace(QChar('\\'), renderer->backslashAsCurrencySymbol());
3001 if (text.length() > 0 || needSpace) {
3002 NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
3003 [attrs setObject:font forKey:NSFontAttributeName];
3004 if (style && style->color().isValid() && qAlpha(style->color().rgb()) != 0)
3005 [attrs setObject:style->color().getNSColor() forKey:NSForegroundColorAttributeName];
3006 if (style && style->backgroundColor().isValid() && qAlpha(style->backgroundColor().rgb()) != 0)
3007 [attrs setObject:style->backgroundColor().getNSColor() forKey:NSBackgroundColorAttributeName];
3009 if (text.length() > 0) {
3010 hasParagraphBreak = false;
3011 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString() attributes:attrs];
3012 [result appendAttributedString: partialString];
3013 [partialString release];
3017 [pendingStyledSpace release];
3018 pendingStyledSpace = [[NSAttributedString alloc] initWithString:@" " attributes:attrs];
3024 // This is our simple HTML -> ASCII transformation:
3026 NodeImpl::Id _id = n->identifier();
3029 // Note the start of the <a> element. We will add the NSLinkAttributeName
3030 // attribute to the attributed string when navigating to the next sibling
3032 linkStartLocation = [result length];
3033 linkStartNode = static_cast<ElementImpl*>(n);
3044 ElementImpl *itemParent = listParent(static_cast<ElementImpl *>(n));
3050 listItems.append(static_cast<ElementImpl*>(n));
3052 info.start = [result length];
3054 listItemLocations.append (info);
3057 if (itemParent && renderer->isListItem()) {
3058 RenderListItem *listRenderer = static_cast<RenderListItem*>(renderer);
3060 maxMarkerWidth = MAX([font pointSize], maxMarkerWidth);
3061 switch(style->listStyleType()) {
3063 listText += ((QChar)BULLET_CHAR);
3066 listText += ((QChar)CIRCLE_CHAR);
3069 listText += ((QChar)SQUARE_CHAR);
3074 QString marker = listRenderer->markerStringValue();
3076 // Use AppKit metrics. Will be rendered by AppKit.
3077 float markerWidth = [font widthOfString: marker.getNSString()];
3078 maxMarkerWidth = MAX(markerWidth, maxMarkerWidth);
3084 NSMutableDictionary *attrs;
3086 attrs = [[NSMutableDictionary alloc] init];
3087 [attrs setObject:font forKey:NSFontAttributeName];
3088 if (style && style->color().isValid())
3089 [attrs setObject:style->color().getNSColor() forKey:NSForegroundColorAttributeName];
3090 if (style && style->backgroundColor().isValid())
3091 [attrs setObject:style->backgroundColor().getNSColor() forKey:NSBackgroundColorAttributeName];
3093 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:listText.getNSString() attributes:attrs];
3095 [result appendAttributedString: partialString];
3096 [partialString release];
3132 // In certain cases, emit a paragraph break.
3133 int bottomMargin = renderer->collapsedMarginBottom();
3134 int fontSize = style->htmlFont().getFontDef().computedPixelSize();
3135 if (bottomMargin * 2 >= fontSize) {
3136 if (!hasParagraphBreak) {
3138 hasParagraphBreak = true;
3147 if (pendingStyledSpace != nil) {
3148 if (linkStartLocation == [result length]) {
3149 ++linkStartLocation;
3151 [result appendAttributedString:pendingStyledSpace];
3152 [pendingStyledSpace release];
3153 pendingStyledSpace = nil;
3155 NSFileWrapper *fileWrapper = fileWrapperForElement(static_cast<ElementImpl *>(n));
3156 NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper];
3157 NSAttributedString *iString = [NSAttributedString attributedStringWithAttachment:attachment];
3158 [result appendAttributedString: iString];
3159 [attachment release];
3162 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()];
3163 [result appendAttributedString: partialString];
3164 [partialString release];
3171 NodeImpl *next = n->firstChild();
3173 next = n->nextSibling();
3176 while (!next && n->parentNode()) {
3178 n = n->parentNode();
3181 next = n->nextSibling();
3183 NodeImpl::Id _id = n->identifier();
3186 // End of a <a> element. Create an attributed string NSLinkAttributeName attribute
3187 // for the range of the link. Note that we create the attributed string from the DOM, which
3188 // will have corrected any illegally nested <a> elements.
3189 if (linkStartNode && n == linkStartNode){
3190 DOMString href = parseURL(linkStartNode->getAttribute(ATTR_HREF));
3191 KURL kURL = KWQ(linkStartNode->getDocument()->part())->completeURL(href.string());
3193 NSURL *URL = kURL.getNSURL();
3194 [result addAttribute:NSLinkAttributeName value:URL range:NSMakeRange(linkStartLocation, [result length]-linkStartLocation)];
3208 int i, count = listItems.count();
3209 for (i = 0; i < count; i++){
3210 if (listItems.at(i) == n){
3211 listItemLocations[i].end = [result length];
3244 // An extra newline is needed at the start, not the end, of these types of tags,
3245 // so don't add another here.
3250 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()];
3251 [result appendAttributedString:partialString];
3252 [partialString release];
3258 [pendingStyledSpace release];
3260 // Apply paragraph styles from outside in. This ensures that nested lists correctly
3261 // override their parent's paragraph style.
3263 unsigned i, count = listItems.count();
3267 #ifdef POSITION_LIST
3268 NodeImpl *containingBlock;
3269 int containingBlockX, containingBlockY;
3271 // Determine the position of the outermost containing block. All paragraph
3272 // styles and tabs should be relative to this position. So, the horizontal position of
3273 // each item in the list (in the resulting attributed string) will be relative to position
3274 // of the outermost containing block.
3276 containingBlock = _startNode;
3277 while (containingBlock->renderer()->isInline()){
3278 containingBlock = containingBlock->parentNode();
3280 containingBlock->renderer()->absolutePosition(containingBlockX, containingBlockY);
3284 for (i = 0; i < count; i++){
3285 e = listItems.at(i);
3286 info = listItemLocations[i];
3288 if (info.end < info.start)
3289 info.end = [result length];
3291 RenderObject *r = e->renderer();
3292 RenderStyle *style = r->style();
3295 NSFont *font = style->font().getNSFont();
3296 float pointSize = [font pointSize];
3298 #ifdef POSITION_LIST
3300 r->absolutePosition(rx, ry);
3301 rx -= containingBlockX;
3303 // Ensure that the text is indented at least enough to allow for the markers.
3304 rx = MAX(rx, (int)maxMarkerWidth);
3306 rx = (int)MAX(maxMarkerWidth, pointSize);
3309 // The bullet text will be right aligned at the first tab marker, followed
3310 // by a space, followed by the list item text. The space is arbitrarily
3311 // picked as pointSize*2/3. The space on the first line of the text item
3312 // is established by a left aligned tab, on subsequent lines it's established
3313 // by the head indent.
3314 NSMutableParagraphStyle *mps = [[NSMutableParagraphStyle alloc] init];
3315 [mps setFirstLineHeadIndent: 0];
3316 [mps setHeadIndent: rx];
3317 [mps setTabStops:[NSArray arrayWithObjects:
3318 [[[NSTextTab alloc] initWithType:NSRightTabStopType location:rx-(pointSize*2/3)] autorelease],
3319 [[[NSTextTab alloc] initWithType:NSLeftTabStopType location:rx] autorelease],
3321 [result addAttribute:NSParagraphStyleAttributeName value:mps range:NSMakeRange(info.start, info.end-info.start)];
3328 KWQ_UNBLOCK_EXCEPTIONS;
3333 QRect KWQKHTMLPart::selectionRect() const
3339 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
3344 return root->selectionRect();
3347 // returns NSRect because going through QRect would truncate any floats
3348 NSRect KWQKHTMLPart::visibleSelectionRect() const
3353 NSView *documentView = d->m_view->getDocumentView();
3354 if (!documentView) {
3357 return NSIntersectionRect(selectionRect(), [documentView visibleRect]);
3360 void KWQKHTMLPart::centerSelectionInVisibleArea() const
3362 switch (selection().state()) {
3363 case Selection::NONE:
3365 case Selection::CARET: {
3367 // passing true forces centering even if selection is already exposed
3368 view()->ensureRectVisibleCentered(selection().caretRect(), true);
3371 case Selection::RANGE:
3373 // passing true forces centering even if selection is already exposed
3374 view()->ensureRectVisibleCentered(selectionRect(), true);
3379 NSImage *KWQKHTMLPart::imageFromRect(NSRect rect) const
3381 NSView *view = d->m_view->getDocumentView();
3386 KWQ_BLOCK_EXCEPTIONS;
3388 NSRect bounds = [view bounds];
3389 NSImage *resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease];
3391 if (rect.size.width != 0 && rect.size.height != 0) {
3392 [resultImage setFlipped:YES];
3393 [resultImage lockFocus];
3395 [NSGraphicsContext saveGraphicsState];
3396 NSPoint translation = { -(NSMinX(rect) - NSMinX(bounds)), -(NSMinY(rect) - NSMinY(bounds)) };
3397 CGContextTranslateCTM((CGContext *)[[NSGraphicsContext currentContext] graphicsPort], translation.x, translation.y);
3399 // We change the coord system at the CG level, out from under the AK focus machinery, because it doesn't
3400 // work to change the coord system of a focused view. However, WebImageRenderer uses the difference
3401 // between the focused view's coord system and the window's coord system to adjust the pattern phase, and
3402 // that calc ignores our translation. So we must tell it about this extra phase offset.
3404 // Window is not flipped, we are, so y coord must be inverted when describing phase, which is a
3405 // window level notion.
3406 translation.y = -translation.y;
3407 [[WebCoreGraphicsBridge sharedBridge] setAdditionalPatternPhase:translation];
3409 [view drawRect:rect];
3411 [[WebCoreGraphicsBridge sharedBridge] setAdditionalPatternPhase:NSZeroPoint];
3412 [NSGraphicsContext restoreGraphicsState];
3414 [resultImage unlockFocus];
3415 [resultImage setFlipped:NO];
3420 KWQ_UNBLOCK_EXCEPTIONS;
3425 NSImage *KWQKHTMLPart::selectionImage() const
3427 _drawSelectionOnly = true; // invoke special drawing mode
3428 NSImage *result = imageFromRect(visibleSelectionRect());
3429 _drawSelectionOnly = false;
3433 NSImage *KWQKHTMLPart::snapshotDragImage(DOM::NodeImpl *node, NSRect *imageRect, NSRect *elementRect) const
3435 RenderObject *renderer = node->renderer();
3440 renderer->updateDragState(true); // mark dragged nodes (so they pick up the right CSS)
3441 d->m_doc->updateLayout(); // forces style recalc - needed since changing the drag state might
3442 // imply new styles, plus JS could have changed other things
3444 NSRect paintingRect = renderer->paintingRootRect(topLevelRect);
3446 _elementToDraw.reset(node); // invoke special sub-tree drawing mode
3447 NSImage *result = imageFromRect(paintingRect);
3448 renderer->updateDragState(false);
3449 _elementToDraw.reset();
3452 *elementRect = topLevelRect;
3455 *imageRect = paintingRect;
3460 RenderStyle *KWQKHTMLPart::styleForSelectionStart(NodeImpl *&nodeToRemove) const