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 "KWQPageState.h"
40 #import "KWQPrinter.h"
42 #import "KWQScrollBar.h"
43 #import "KWQWindowWidget.h"
45 #import "WebCoreBridge.h"
46 #import "WebCoreGraphicsBridge.h"
47 #import "WebCoreViewFactory.h"
48 #import "WebDashboardRegion.h"
50 #import "css_computedstyle.h"
52 #import "dom2_eventsimpl.h"
53 #import "dom2_rangeimpl.h"
54 #import "dom_position.h"
55 #import "dom_textimpl.h"
56 #import "html_document.h"
57 #import "html_documentimpl.h"
58 #import "html_formimpl.h"
60 #import "html_tableimpl.h"
62 #import "htmltokenizer.h"
63 #import "khtmlpart_p.h"
65 #import "kjs_binding.h"
66 #import "kjs_window.h"
67 #import "render_canvas.h"
68 #import "render_frames.h"
69 #import "render_image.h"
70 #import "render_list.h"
71 #import "render_style.h"
72 #import "render_table.h"
73 #import "render_text.h"
75 #import "visible_position.h"
76 #import "visible_text.h"
77 #import "visible_units.h"
79 #import <JavaScriptCore/identifier.h>
80 #import <JavaScriptCore/property_map.h>
81 #import <JavaScriptCore/runtime.h>
82 #import <JavaScriptCore/runtime_root.h>
83 #import <JavaScriptCore/WebScriptObjectPrivate.h>
87 using DOM::AtomicString;
88 using DOM::ClipboardEventImpl;
89 using DOM::DocumentFragmentImpl;
90 using DOM::DocumentImpl;
91 using DOM::DocumentMarker;
93 using DOM::ElementImpl;
95 using DOM::HTMLDocumentImpl;
96 using DOM::HTMLElementImpl;
97 using DOM::HTMLFormElementImpl;
98 using DOM::HTMLFrameElementImpl;
99 using DOM::HTMLGenericFormElementImpl;
100 using DOM::HTMLTableCellElementImpl;
105 using DOM::RangeImpl;
109 using khtml::CharacterIterator;
110 using khtml::ChildFrame;
111 using khtml::Decoder;
112 using khtml::DashboardRegionValue;
113 using khtml::EditCommandPtr;
114 using khtml::endOfWord;
115 using khtml::findPlainText;
116 using khtml::InlineTextBox;
117 using khtml::LeftWordIfOnBoundary;
118 using khtml::MouseDoubleClickEvent;
119 using khtml::MouseMoveEvent;
120 using khtml::MousePressEvent;
121 using khtml::MouseReleaseEvent;
122 using khtml::parseURL;
124 using khtml::RenderCanvas;
125 using khtml::RenderImage;
126 using khtml::RenderLayer;
127 using khtml::RenderListItem;
128 using khtml::RenderObject;
129 using khtml::RenderPart;
130 using khtml::RenderStyle;
131 using khtml::RenderTableCell;
132 using khtml::RenderText;
133 using khtml::RenderWidget;
134 using khtml::RightWordIfOnBoundary;
135 using khtml::Selection;
137 using khtml::setStart;
138 using khtml::ShadowData;
139 using khtml::startOfWord;
140 using khtml::startVisiblePosition;
141 using khtml::StyleDashboardRegion;
142 using khtml::TextIterator;
143 using khtml::UPSTREAM;
144 using khtml::VISIBLE;
145 using khtml::VisiblePosition;
146 using khtml::WordAwareIterator;
150 using KJS::Interpreter;
152 using KJS::SavedBuiltins;
153 using KJS::SavedProperties;
154 using KJS::ScheduledAction;
157 using KJS::Bindings::Instance;
159 using KParts::ReadOnlyPart;
160 using KParts::URLArgs;
162 NSEvent *KWQKHTMLPart::_currentEvent = nil;
164 void KHTMLPart::completed()
166 KWQ(this)->_completed.call();
169 void KHTMLPart::completed(bool arg)
171 KWQ(this)->_completed.call(arg);
174 void KHTMLPart::nodeActivated(const Node &)
178 bool KHTMLPart::openURL(const KURL &URL)
180 ASSERT_NOT_REACHED();
184 void KHTMLPart::onURL(const QString &)
188 void KHTMLPart::setStatusBarText(const QString &status)
190 KWQ(this)->setStatusBarText(status);
193 void KHTMLPart::started(Job *j)
195 KWQ(this)->_started.call(j);
198 static void redirectionTimerMonitor(void *context)
200 KWQKHTMLPart *kwq = static_cast<KWQKHTMLPart *>(context);
201 kwq->redirectionTimerStartedOrStopped();
204 KWQKHTMLPart::KWQKHTMLPart()
205 : _started(this, SIGNAL(started(KIO::Job *)))
206 , _completed(this, SIGNAL(completed()))
207 , _completedWithBool(this, SIGNAL(completed(bool)))
208 , _mouseDownView(nil)
209 , _sendingEventToSubview(false)
210 , _mouseDownMayStartDrag(false)
211 , _mouseDownMayStartSelect(false)
212 , _activationEventNumber(0)
213 , _formValuesAboutToBeSubmitted(nil)
214 , _formAboutToBeSubmitted(nil)
215 , _windowWidget(NULL)
216 , _drawSelectionOnly(false)
218 , _windowScriptObject(0)
219 , _windowScriptNPObject(0)
224 // Must init the cache before connecting to any signals
227 // The widget is made outside this class in our case.
228 KHTMLPart::init( 0, DefaultGUI );
230 mutableInstances().prepend(this);
231 d->m_redirectionTimer.setMonitor(redirectionTimerMonitor, this);
234 KWQKHTMLPart::~KWQKHTMLPart()
236 cleanupPluginRootObjects();
238 mutableInstances().remove(this);
243 // these are all basic Foundation classes and our own classes - we
244 // know they will not raise in dealloc, so no need to block
246 KWQRelease(_formValuesAboutToBeSubmitted);
247 KWQRelease(_formAboutToBeSubmitted);
249 KWQRelease(_windowScriptObject);
251 delete _windowWidget;
254 void KWQKHTMLPart::freeClipboard()
256 if (_dragClipboard) {
257 _dragClipboard->setAccessPolicy(KWQClipboard::Numb);
258 _dragClipboard->deref();
263 void KWQKHTMLPart::setSettings (KHTMLSettings *settings)
265 d->m_settings = settings;
268 QString KWQKHTMLPart::generateFrameName()
270 KWQ_BLOCK_EXCEPTIONS;
271 return QString::fromNSString([_bridge generateFrameName]);
272 KWQ_UNBLOCK_EXCEPTIONS;
277 void KWQKHTMLPart::provisionalLoadStarted()
279 // we don't want to wait until we get an actual http response back
280 // to cancel pending redirects, otherwise they might fire before
282 cancelRedirection(true);
285 bool KWQKHTMLPart::openURL(const KURL &url)
287 KWQ_BLOCK_EXCEPTIONS;
289 bool userGesture = true;
291 if (jScript() && jScript()->interpreter()) {
292 KHTMLPart *rootPart = this;
293 while (rootPart->parentPart() != 0)
294 rootPart = rootPart->parentPart();
295 KJS::ScriptInterpreter *interpreter = static_cast<KJS::ScriptInterpreter *>(KJSProxy::proxy(rootPart)->interpreter());
296 userGesture = interpreter->wasRunByUserGesture();
299 // FIXME: The lack of args here to get the reload flag from
300 // indicates a problem in how we use KHTMLPart::processObjectRequest,
301 // where we are opening the URL before the args are set up.
302 [_bridge loadURL:url.getNSURL()
303 referrer:[_bridge referrer]
305 userGesture:userGesture
311 KWQ_UNBLOCK_EXCEPTIONS;
316 void KWQKHTMLPart::openURLRequest(const KURL &url, const URLArgs &args)
318 KWQ_BLOCK_EXCEPTIONS;
320 [_bridge loadURL:url.getNSURL()
321 referrer:[_bridge referrer]
324 target:args.frameName.getNSString()
329 KWQ_UNBLOCK_EXCEPTIONS;
332 void KWQKHTMLPart::didNotOpenURL(const KURL &URL)
334 if (_submittedFormURL == URL) {
335 _submittedFormURL = KURL();
339 // Scans logically forward from "start", including any child frames
340 static HTMLFormElementImpl *scanForForm(NodeImpl *start)
343 for (n = start; n; n = n->traverseNextNode()) {
344 NodeImpl::Id nodeID = idFromNode(n);
345 if (nodeID == ID_FORM) {
346 return static_cast<HTMLFormElementImpl *>(n);
347 } else if (n->isHTMLElement()
348 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()) {
349 return static_cast<HTMLGenericFormElementImpl *>(n)->form();
350 } else if (nodeID == ID_FRAME || nodeID == ID_IFRAME) {
351 NodeImpl *childDoc = static_cast<HTMLFrameElementImpl *>(n)->contentDocument();
352 HTMLFormElementImpl *frameResult = scanForForm(childDoc);
361 // We look for either the form containing the current focus, or for one immediately after it
362 HTMLFormElementImpl *KWQKHTMLPart::currentForm() const
364 // start looking either at the active (first responder) node, or where the selection is
365 NodeImpl *start = activeNode().handle();
367 start = selectionStart();
370 // try walking up the node tree to find a form element
372 for (n = start; n; n = n->parentNode()) {
373 if (idFromNode(n) == ID_FORM) {
374 return static_cast<HTMLFormElementImpl *>(n);
375 } else if (n->isHTMLElement()
376 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()) {
377 return static_cast<HTMLGenericFormElementImpl *>(n)->form();
381 // try walking forward in the node tree to find a form element
382 return start ? scanForForm(start) : 0;
385 // Either get cached regexp or build one that matches any of the labels.
386 // The regexp we build is of the form: (STR1|STR2|STRN)
387 QRegExp *regExpForLabels(NSArray *labels)
389 // All the ObjC calls in this method are simple array and string
390 // calls which we can assume do not raise exceptions
393 // Parallel arrays that we use to cache regExps. In practice the number of expressions
394 // that the app will use is equal to the number of locales is used in searching.
395 static const unsigned int regExpCacheSize = 4;
396 static NSMutableArray *regExpLabels = nil;
397 static QPtrList <QRegExp> regExps;
398 static QRegExp wordRegExp = QRegExp("\\w");
402 regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize];
404 unsigned int cacheHit = [regExpLabels indexOfObject:labels];
405 if (cacheHit != NSNotFound) {
406 result = regExps.at(cacheHit);
408 QString pattern("(");
409 unsigned int numLabels = [labels count];
411 for (i = 0; i < numLabels; i++) {
412 QString label = QString::fromNSString([labels objectAtIndex:i]);
414 bool startsWithWordChar = false;
415 bool endsWithWordChar = false;
416 if (label.length() != 0) {
417 startsWithWordChar = wordRegExp.search(label.at(0)) >= 0;
418 endsWithWordChar = wordRegExp.search(label.at(label.length() - 1)) >= 0;
424 // Search for word boundaries only if label starts/ends with "word characters".
425 // If we always searched for word boundaries, this wouldn't work for languages
427 if (startsWithWordChar) {
428 pattern.append("\\b");
430 pattern.append(label);
431 if (endsWithWordChar) {
432 pattern.append("\\b");
436 result = new QRegExp(pattern, false);
439 // add regexp to the cache, making sure it is at the front for LRU ordering
441 if (cacheHit != NSNotFound) {
442 // remove from old spot
443 [regExpLabels removeObjectAtIndex:cacheHit];
444 regExps.remove(cacheHit);
447 [regExpLabels insertObject:labels atIndex:0];
448 regExps.insert(0, result);
450 if ([regExpLabels count] > regExpCacheSize) {
451 [regExpLabels removeObjectAtIndex:regExpCacheSize];
452 QRegExp *last = regExps.last();
453 regExps.removeLast();
460 NSString *KWQKHTMLPart::searchForLabelsAboveCell(QRegExp *regExp, HTMLTableCellElementImpl *cell)
462 RenderTableCell *cellRenderer = static_cast<RenderTableCell *>(cell->renderer());
463 RenderTableCell *cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer);
465 if (cellAboveRenderer) {
466 HTMLTableCellElementImpl *aboveCell =
467 static_cast<HTMLTableCellElementImpl *>(cellAboveRenderer->element());
470 // search within the above cell we found for a match
471 for (NodeImpl *n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) {
472 if (idFromNode(n) == ID_TEXT
473 && n->renderer() && n->renderer()->style()->visibility() == VISIBLE)
475 // For each text chunk, run the regexp
476 QString nodeString = n->nodeValue().string();
477 int pos = regExp->searchRev(nodeString);
479 return nodeString.mid(pos, regExp->matchedLength()).getNSString();
485 // Any reason in practice to search all cells in that are above cell?
489 NSString *KWQKHTMLPart::searchForLabelsBeforeElement(NSArray *labels, ElementImpl *element)
491 QRegExp *regExp = regExpForLabels(labels);
492 // We stop searching after we've seen this many chars
493 const unsigned int charsSearchedThreshold = 500;
494 // This is the absolute max we search. We allow a little more slop than
495 // charsSearchedThreshold, to make it more likely that we'll search whole nodes.
496 const unsigned int maxCharsSearched = 600;
497 // If the starting element is within a table, the cell that contains it
498 HTMLTableCellElementImpl *startingTableCell = 0;
499 bool searchedCellAbove = false;
501 // walk backwards in the node tree, until another element, or form, or end of tree
502 int unsigned lengthSearched = 0;
504 for (n = element->traversePreviousNode();
505 n && lengthSearched < charsSearchedThreshold;
506 n = n->traversePreviousNode())
508 NodeImpl::Id nodeID = idFromNode(n);
509 if (nodeID == ID_FORM
510 || (n->isHTMLElement()
511 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()))
513 // We hit another form element or the start of the form - bail out
515 } else if (nodeID == ID_TD && !startingTableCell) {
516 startingTableCell = static_cast<HTMLTableCellElementImpl *>(n);
517 } else if (nodeID == ID_TR && startingTableCell) {
518 NSString *result = searchForLabelsAboveCell(regExp, startingTableCell);
522 searchedCellAbove = true;
523 } else if (nodeID == ID_TEXT
524 && n->renderer() && n->renderer()->style()->visibility() == VISIBLE)
526 // For each text chunk, run the regexp
527 QString nodeString = n->nodeValue().string();
528 // add 100 for slop, to make it more likely that we'll search whole nodes
529 if (lengthSearched + nodeString.length() > maxCharsSearched) {
530 nodeString = nodeString.right(charsSearchedThreshold - lengthSearched);
532 int pos = regExp->searchRev(nodeString);
534 return nodeString.mid(pos, regExp->matchedLength()).getNSString();
536 lengthSearched += nodeString.length();
541 // If we started in a cell, but bailed because we found the start of the form or the
542 // previous element, we still might need to search the row above us for a label.
543 if (startingTableCell && !searchedCellAbove) {
544 return searchForLabelsAboveCell(regExp, startingTableCell);
550 NSString *KWQKHTMLPart::matchLabelsAgainstElement(NSArray *labels, ElementImpl *element)
552 QString name = element->getAttribute(ATTR_NAME).string();
553 // Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
554 name.replace(QRegExp("[[:digit:]]"), " ");
555 name.replace('_', ' ');
557 QRegExp *regExp = regExpForLabels(labels);
558 // Use the largest match we can find in the whole name string
565 pos = regExp->search(name, start);
567 length = regExp->matchedLength();
568 if (length >= bestLength) {
577 return name.mid(bestPos, bestLength).getNSString();
583 // Search from the end of the currently selected location if we are first responder, or from
584 // the beginning of the document if nothing is selected or we're not first responder.
585 bool KWQKHTMLPart::findString(NSString *string, bool forward, bool caseFlag, bool wrapFlag)
587 QString target = QString::fromNSString(string);
588 if (target.isEmpty()) {
592 // Start on the correct edge of the selection, search to edge of document.
593 Range searchRange(xmlDocImpl());
594 searchRange.selectNodeContents(xmlDocImpl());
595 if (selectionStart()) {
597 setStart(searchRange, VisiblePosition(selection().end()));
599 setEnd(searchRange, VisiblePosition(selection().start()));
603 // Do the search once, then do it a second time to handle wrapped search.
604 // Searches some or all of document twice in the failure case, but that's probably OK.
605 Range resultRange = findPlainText(searchRange, target, forward, caseFlag);
606 if (resultRange.collapsed() && wrapFlag) {
607 searchRange.selectNodeContents(xmlDocImpl());
608 resultRange = findPlainText(searchRange, target, forward, caseFlag);
609 // If we got back to the same place we started, that doesn't count as success.
610 if (resultRange == selection().toRange()) {
615 if (resultRange.collapsed()) {
619 setSelection(resultRange);
624 void KWQKHTMLPart::clearRecordedFormValues()
626 // It's safe to assume that our own classes and Foundation data
627 // structures won't raise exceptions in dealloc
629 KWQRelease(_formValuesAboutToBeSubmitted);
630 _formValuesAboutToBeSubmitted = nil;
631 KWQRelease(_formAboutToBeSubmitted);
632 _formAboutToBeSubmitted = nil;
635 void KWQKHTMLPart::recordFormValue(const QString &name, const QString &value, HTMLFormElementImpl *element)
637 // It's safe to assume that our own classes and basic Foundation
638 // data structures won't raise exceptions
640 if (!_formValuesAboutToBeSubmitted) {
641 _formValuesAboutToBeSubmitted = KWQRetainNSRelease([[NSMutableDictionary alloc] init]);
642 ASSERT(!_formAboutToBeSubmitted);
643 _formAboutToBeSubmitted = KWQRetain([DOMElement _elementWithImpl:element]);
645 ASSERT([_formAboutToBeSubmitted _elementImpl] == element);
647 [_formValuesAboutToBeSubmitted setObject:value.getNSString() forKey:name.getNSString()];
650 void KWQKHTMLPart::submitForm(const KURL &url, const URLArgs &args)
652 KWQ_BLOCK_EXCEPTIONS;
654 // FIXME: We'd like to remove this altogether and fix the multiple form submission issue another way.
655 // We do not want to submit more than one form from the same page,
656 // nor do we want to submit a single form more than once.
657 // This flag prevents these from happening; not sure how other browsers prevent this.
658 // The flag is reset in each time we start handle a new mouse or key down event, and
659 // also in setView since this part may get reused for a page from the back/forward cache.
660 // The form multi-submit logic here is only needed when we are submitting a form that affects this frame.
661 // FIXME: Frame targeting is only one of the ways the submission could end up doing something other
662 // than replacing this frame's content, so this check is flawed. On the other hand, the check is hardly
663 // needed any more now that we reset _submittedFormURL on each mouse or key down event.
664 WebCoreBridge *target = args.frameName.isEmpty() ? _bridge : [_bridge findFrameNamed:args.frameName.getNSString()];
665 KHTMLPart *targetPart = [target part];
666 bool willReplaceThisFrame = false;
667 for (KHTMLPart *p = this; p; p = p->parentPart()) {
668 if (p == targetPart) {
669 willReplaceThisFrame = true;
673 if (willReplaceThisFrame) {
674 if (_submittedFormURL == url) {
677 _submittedFormURL = url;
680 if (!args.doPost()) {
681 [_bridge loadURL:url.getNSURL()
682 referrer:[_bridge referrer]
685 target:args.frameName.getNSString()
686 triggeringEvent:_currentEvent
687 form:_formAboutToBeSubmitted
688 formValues:_formValuesAboutToBeSubmitted];
690 ASSERT(args.contentType().startsWith("Content-Type: "));
691 [_bridge postWithURL:url.getNSURL()
692 referrer:[_bridge referrer]
693 target:args.frameName.getNSString()
694 data:arrayFromFormData(args.postData)
695 contentType:args.contentType().mid(14).getNSString()
696 triggeringEvent:_currentEvent
697 form:_formAboutToBeSubmitted
698 formValues:_formValuesAboutToBeSubmitted];
700 clearRecordedFormValues();
702 KWQ_UNBLOCK_EXCEPTIONS;
705 void KWQKHTMLPart::setEncoding(const QString &name, bool userChosen)
707 if (!d->m_workingURL.isEmpty()) {
710 d->m_encoding = name;
711 d->m_haveEncoding = userChosen;
714 void KWQKHTMLPart::addData(const char *bytes, int length)
716 ASSERT(d->m_workingURL.isEmpty());
718 ASSERT(d->m_doc->parsing());
719 write(bytes, length);
722 void KHTMLPart::frameDetached()
724 // FIXME: This should be a virtual function, with the first part in KWQKHTMLPart, and the second
725 // part in KHTMLPart, so it works for KHTML too.
727 KWQ_BLOCK_EXCEPTIONS;
728 [KWQ(this)->bridge() frameDetached];
729 KWQ_UNBLOCK_EXCEPTIONS;
731 KHTMLPart *parent = parentPart();
733 FrameList& parentFrames = parent->d->m_frames;
734 FrameIt end = parentFrames.end();
735 for (FrameIt it = parentFrames.begin(); it != end; ++it) {
736 ChildFrame &child = *it;
737 if (child.m_part == this) {
738 parent->disconnectChild(&child);
739 parentFrames.remove(it);
747 void KWQKHTMLPart::urlSelected(const KURL &url, int button, int state, const URLArgs &args)
749 KWQ_BLOCK_EXCEPTIONS;
750 [_bridge loadURL:url.getNSURL()
751 referrer:[_bridge referrer]
754 target:args.frameName.getNSString()
755 triggeringEvent:_currentEvent
758 KWQ_UNBLOCK_EXCEPTIONS;
761 class KWQPluginPart : public ReadOnlyPart
763 virtual bool openURL(const KURL &) { return true; }
764 virtual bool closeURL() { return true; }
767 ReadOnlyPart *KWQKHTMLPart::createPart(const ChildFrame &child, const KURL &url, const QString &mimeType)
769 KWQ_BLOCK_EXCEPTIONS;
772 BOOL needFrame = [_bridge frameRequiredForMIMEType:mimeType.getNSString() URL:url.getNSURL()];
773 if (child.m_type == ChildFrame::Object && !needFrame) {
774 KWQPluginPart *newPart = new KWQPluginPart;
775 newPart->setWidget(new QWidget([_bridge viewForPluginWithURL:url.getNSURL()
776 attributeNames:child.m_paramNames.getNSArray()
777 attributeValues:child.m_paramValues.getNSArray()
778 MIMEType:child.m_args.serviceType.getNSString()]));
781 LOG(Frames, "name %s", child.m_name.ascii());
782 BOOL allowsScrolling = YES;
783 int marginWidth = -1;
784 int marginHeight = -1;
785 if (child.m_type != ChildFrame::Object) {
786 HTMLFrameElementImpl *o = static_cast<HTMLFrameElementImpl *>(child.m_frame->element());
787 allowsScrolling = o->scrollingMode() != QScrollView::AlwaysOff;
788 marginWidth = o->getMarginWidth();
789 marginHeight = o->getMarginHeight();
791 WebCoreBridge *childBridge = [_bridge createChildFrameNamed:child.m_name.getNSString()
792 withURL:url.getNSURL()
793 renderPart:child.m_frame
794 allowsScrolling:allowsScrolling
795 marginWidth:marginWidth
796 marginHeight:marginHeight];
797 // This call needs to return an object with a ref, since the caller will expect to own it.
798 // childBridge owns the only ref so far.
799 [childBridge part]->ref();
800 part = [childBridge part];
805 KWQ_UNBLOCK_EXCEPTIONS;
810 void KWQKHTMLPart::setView(KHTMLView *view)
812 // Detach the document now, so any onUnload handlers get run - if
813 // we wait until the view is destroyed, then things won't be
814 // hooked up enough for some JavaScript calls to work.
815 if (d->m_doc && view == NULL) {
828 // Only one form submission is allowed per view of a part.
829 // Since this part may be getting reused as a result of being
830 // pulled from the back/forward cache, reset this flag.
831 _submittedFormURL = KURL();
834 KHTMLView *KWQKHTMLPart::view() const
839 void KWQKHTMLPart::setTitle(const DOMString &title)
841 QString text = title.string();
842 text.replace('\\', backslashAsCurrencySymbol());
844 KWQ_BLOCK_EXCEPTIONS;
845 [_bridge setTitle:text.getNSString()];
846 KWQ_UNBLOCK_EXCEPTIONS;
849 void KWQKHTMLPart::setStatusBarText(const QString &status)
851 QString text = status;
852 text.replace('\\', backslashAsCurrencySymbol());
854 KWQ_BLOCK_EXCEPTIONS;
855 [_bridge setStatusText:text.getNSString()];
856 KWQ_UNBLOCK_EXCEPTIONS;
859 void KWQKHTMLPart::scheduleClose()
861 KWQ_BLOCK_EXCEPTIONS;
862 [_bridge closeWindowSoon];
863 KWQ_UNBLOCK_EXCEPTIONS;
866 void KWQKHTMLPart::unfocusWindow()
868 KWQ_BLOCK_EXCEPTIONS;
869 [_bridge unfocusWindow];
870 KWQ_UNBLOCK_EXCEPTIONS;
873 void KWQKHTMLPart::jumpToSelection()
875 // Assumes that selection start will only ever be a text node. This is currently
876 // true, but will it always be so?
877 if (d->m_selection.start().isNotNull()) {
878 RenderText *rt = dynamic_cast<RenderText *>(d->m_selection.start().node()->renderer());
881 rt->posOfChar(d->m_selection.start().offset(), x, y);
882 // The -50 offset is copied from KHTMLPart::findTextNext, which sets the contents position
883 // after finding a matched text string.
884 d->m_view->setContentsPos(x - 50, y - 50);
887 Something like this would fix <rdar://problem/3154293>: "Find Next should not scroll page if the next target is already visible"
889 I think this would be a better way to do this, to avoid needless horizontal scrolling,
890 but it is not feasible until selectionRect() returns a tighter rect around the
891 selected text. Right now it works at element granularity.
893 NSView *docView = d->m_view->getDocumentView();
895 KWQ_BLOCK_EXCEPTIONS;
896 NSRect selRect = NSRect(selectionRect());
897 NSRect visRect = [docView visibleRect];
898 if (!NSContainsRect(visRect, selRect)) {
899 // pad a bit so we overscroll slightly
900 selRect = NSInsetRect(selRect, -10.0, -10.0);
901 selRect = NSIntersectionRect(selRect, [docView bounds]);
902 [docView scrollRectToVisible:selRect];
904 KWQ_UNBLOCK_EXCEPTIONS;
909 QString KWQKHTMLPart::advanceToNextMisspelling(bool startBeforeSelection)
911 // The basic approach is to search in two phases - from the selection end to the end of the doc, and
912 // then we wrap and search from the doc start to (approximately) where we started.
914 // Start at the end of the selection, search to edge of document. Starting at the selection end makes
915 // repeated "check spelling" commands work.
916 Range searchRange(xmlDocImpl());
917 searchRange.selectNodeContents(xmlDocImpl());
918 bool startedWithSelection = false;
919 if (selectionStart()) {
920 startedWithSelection = true;
921 if (startBeforeSelection) {
922 VisiblePosition start(selection().start());
923 // We match AppKit's rule: Start 1 character before the selection.
924 VisiblePosition oneBeforeStart = start.previous();
925 setStart(searchRange, oneBeforeStart.isNotNull() ? oneBeforeStart : start);
927 setStart(searchRange, VisiblePosition(selection().end()));
931 // If we're not in an editable node, try to find one, make that our range to work in
932 NodeImpl *editableNodeImpl = searchRange.startContainer().handle();
933 if (!editableNodeImpl->isContentEditable()) {
934 editableNodeImpl = editableNodeImpl->nextEditable();
935 if (!editableNodeImpl) {
938 searchRange.setStartBefore(editableNodeImpl);
939 startedWithSelection = false; // won't need to wrap
942 // topNode defines the whole range we want to operate on
943 Node topNode(editableNodeImpl->rootEditableElement());
944 searchRange.setEndAfter(topNode);
946 // Make sure start of searchRange is not in the middle of a word. Jumping back a char and then
947 // forward by a word happens to do the trick.
948 if (startedWithSelection) {
949 VisiblePosition oneBeforeStart = startVisiblePosition(searchRange).previous();
950 if (oneBeforeStart.isNotNull()) {
951 setStart(searchRange, endOfWord(oneBeforeStart));
952 } // else we were already at the start of the editable node
955 if (searchRange.collapsed()) {
956 return QString(); // nothing to search in
959 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
960 WordAwareIterator it(searchRange);
961 bool wrapped = false;
963 // We go to the end of our first range instead of the start of it, just to be sure
964 // we don't get foiled by any word boundary problems at the start. It means we might
965 // do a tiny bit more searching.
966 Node searchEndAfterWrapNode = it.range().endContainer();
967 long searchEndAfterWrapOffset = it.range().endOffset();
970 if (!it.atEnd()) { // we may be starting at the end of the doc, and already by atEnd
971 const QChar *chars = it.characters();
972 long len = it.length();
973 if (len > 1 || !chars[0].isSpace()) {
974 NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:(unichar *)chars length:len freeWhenDone:NO];
975 NSRange misspelling = [checker checkSpellingOfString:chunk startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:[_bridge spellCheckerDocumentTag] wordCount:NULL];
977 if (misspelling.length > 0) {
978 // Build up result range and string. Note the misspelling may span many text nodes,
979 // but the CharIterator insulates us from this complexity
980 Range misspellingRange(xmlDocImpl());
981 CharacterIterator chars(it.range());
982 chars.advance(misspelling.location);
983 misspellingRange.setStart(chars.range().startContainer(), chars.range().startOffset());
984 QString result = chars.string(misspelling.length);
985 misspellingRange.setEnd(chars.range().startContainer(), chars.range().startOffset());
987 setSelection(misspellingRange);
989 // Mark misspelling in document.
990 xmlDocImpl()->addMarker(misspellingRange, DocumentMarker::Spelling);
998 if (wrapped || !startedWithSelection) {
999 break; // finished the second range, or we did the whole doc with the first range
1001 // we've gone from the selection to the end of doc, now wrap around
1003 searchRange.setStartBefore(topNode);
1004 // going until the end of the very first chunk we tested is far enough
1005 searchRange.setEnd(searchEndAfterWrapNode, searchEndAfterWrapOffset);
1006 it = WordAwareIterator(searchRange);
1014 bool KWQKHTMLPart::scrollOverflow(KWQScrollDirection direction, KWQScrollGranularity granularity)
1016 if (!xmlDocImpl()) {
1020 NodeImpl *node = xmlDocImpl()->focusNode();
1022 node = d->m_mousePressNode.handle();
1026 RenderObject *r = node->renderer();
1028 return r->scroll(direction, granularity);
1035 bool KWQKHTMLPart::scrollOverflowWithScrollWheelEvent(NSEvent *event)
1037 RenderObject *r = renderer();
1042 NSPoint point = [d->m_view->getDocumentView() convertPoint:[event locationInWindow] fromView:nil];
1043 RenderObject::NodeInfo nodeInfo(true, true);
1044 r->layer()->hitTest(nodeInfo, (int)point.x, (int)point.y);
1046 NodeImpl *node = nodeInfo.innerNode();
1051 r = node->renderer();
1056 KWQScrollDirection direction;
1058 float deltaX = [event deltaX];
1059 float deltaY = [event deltaY];
1061 direction = KWQScrollRight;
1062 multiplier = -deltaX;
1063 } else if (deltaX > 0) {
1064 direction = KWQScrollLeft;
1065 multiplier = deltaX;
1066 } else if (deltaY < 0) {
1067 direction = KWQScrollDown;
1068 multiplier = -deltaY;
1069 } else if (deltaY > 0) {
1070 direction = KWQScrollUp;
1071 multiplier = deltaY;
1075 return r->scroll(direction, KWQScrollWheel, multiplier);
1078 void KWQKHTMLPart::redirectionTimerStartedOrStopped()
1080 // Don't report history navigations, just actual redirection.
1081 if (d->m_scheduledRedirection == historyNavigationScheduled) {
1085 KWQ_BLOCK_EXCEPTIONS;
1086 if (d->m_redirectionTimer.isActive()) {
1087 [_bridge reportClientRedirectToURL:KURL(d->m_redirectURL).getNSURL()
1088 delay:d->m_delayRedirect
1089 fireDate:[d->m_redirectionTimer.getNSTimer() fireDate]
1090 lockHistory:d->m_redirectLockHistory
1091 isJavaScriptFormAction:d->m_executingJavaScriptFormAction];
1093 [_bridge reportClientRedirectCancelled:d->m_cancelWithLoadInProgress];
1095 KWQ_UNBLOCK_EXCEPTIONS;
1098 void KWQKHTMLPart::paint(QPainter *p, const QRect &rect)
1102 if (p->device()->devType() == QInternal::Printer)
1103 fillWithRed = false; // Printing, don't fill with red (can't remember why).
1104 else if (!xmlDocImpl() || xmlDocImpl()->ownerElement())
1105 fillWithRed = false; // Subframe, don't fill with red.
1106 else if (view() && view()->isTransparent())
1107 fillWithRed = false; // Transparent, don't fill with red.
1108 else if (_drawSelectionOnly)
1109 fillWithRed = false; // Selections are transparent, don't fill with red.
1110 else if (_elementToDraw != 0)
1111 fillWithRed = false; // Element images are transparent, don't fill with red.
1116 p->fillRect(rect.x(), rect.y(), rect.width(), rect.height(), QColor(0xFF, 0, 0));
1121 // _elementToDraw is used to draw only one element
1122 RenderObject *eltRenderer = (_elementToDraw != 0) ? _elementToDraw.handle()->renderer() : 0;
1123 renderer()->layer()->paint(p, rect, _drawSelectionOnly, eltRenderer);
1126 // Regions may have changed as a result of the visibility/z-index of element changing.
1127 if (renderer()->document()->dashboardRegionsDirty()){
1128 renderer()->canvas()->view()->updateDashboardRegions();
1132 ERROR("called KWQKHTMLPart::paint with nil renderer");
1136 void KWQKHTMLPart::adjustPageHeight(float *newBottom, float oldTop, float oldBottom, float bottomLimit)
1138 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
1140 // Use a printer device, with painting disabled for the pagination phase
1141 QPainter painter(true);
1142 painter.setPaintingDisabled(true);
1144 root->setTruncatedAt((int)floor(oldBottom));
1145 QRect dirtyRect(0, (int)floor(oldTop),
1146 root->docWidth(), (int)ceil(oldBottom-oldTop));
1147 root->setPrintRect(dirtyRect);
1148 root->layer()->paint(&painter, dirtyRect);
1149 *newBottom = root->bestTruncatedAt();
1150 if (*newBottom == 0) {
1151 *newBottom = oldBottom;
1154 *newBottom = oldBottom;
1158 RenderObject *KWQKHTMLPart::renderer() const
1160 DocumentImpl *doc = xmlDocImpl();
1161 return doc ? doc->renderer() : 0;
1164 QString KWQKHTMLPart::userAgent() const
1166 KWQ_BLOCK_EXCEPTIONS;
1167 return QString::fromNSString([_bridge userAgentForURL:m_url.getNSURL()]);
1168 KWQ_UNBLOCK_EXCEPTIONS;
1173 QString KWQKHTMLPart::mimeTypeForFileName(const QString &fileName) const
1175 NSString *ns = fileName.getNSString();
1177 KWQ_BLOCK_EXCEPTIONS;
1178 return QString::fromNSString([_bridge MIMETypeForPath:ns]);
1179 KWQ_UNBLOCK_EXCEPTIONS;
1184 NSView *KWQKHTMLPart::nextKeyViewInFrame(NodeImpl *node, KWQSelectionDirection direction)
1186 DocumentImpl *doc = xmlDocImpl();
1191 node = direction == KWQSelectingNext
1192 ? doc->nextFocusNode(node) : doc->previousFocusNode(node);
1196 RenderWidget *renderWidget = dynamic_cast<RenderWidget *>(node->renderer());
1198 QWidget *widget = renderWidget->widget();
1199 KHTMLView *childFrameWidget = dynamic_cast<KHTMLView *>(widget);
1201 if (childFrameWidget) {
1202 view = KWQ(childFrameWidget->part())->nextKeyViewInFrame(0, direction);
1203 } else if (widget) {
1204 view = widget->getView();
1211 doc->setFocusNode(node);
1213 view()->ensureRectVisibleCentered(node->getRect());
1215 [_bridge makeFirstResponder:[_bridge documentView]];
1216 return [_bridge documentView];
1221 NSView *KWQKHTMLPart::nextKeyViewInFrameHierarchy(NodeImpl *node, KWQSelectionDirection direction)
1223 NSView *next = nextKeyViewInFrame(node, direction);
1225 KWQKHTMLPart *parent = KWQ(parentPart());
1227 next = parent->nextKeyView(parent->childFrame(this)->m_frame->element(), direction);
1231 // remove focus from currently focused node if we're giving focus to another view
1232 if (next && (next != [_bridge documentView])) {
1233 DocumentImpl *doc = xmlDocImpl();
1235 doc->setFocusNode(0);
1242 NSView *KWQKHTMLPart::nextKeyView(NodeImpl *node, KWQSelectionDirection direction)
1244 KWQ_BLOCK_EXCEPTIONS;
1246 NSView * next = nextKeyViewInFrameHierarchy(node, direction);
1251 // Look at views from the top level part up, looking for a next key view that we can use.
1253 next = direction == KWQSelectingNext
1254 ? [_bridge nextKeyViewOutsideWebFrameViews]
1255 : [_bridge previousKeyViewOutsideWebFrameViews];
1261 KWQ_UNBLOCK_EXCEPTIONS;
1263 // If all else fails, make a loop by starting from 0.
1264 return nextKeyViewInFrameHierarchy(0, direction);
1267 NSView *KWQKHTMLPart::nextKeyViewForWidget(QWidget *startingWidget, KWQSelectionDirection direction)
1269 // Use the event filter object to figure out which RenderWidget owns this QWidget and get to the DOM.
1270 // Then get the next key view in the order determined by the DOM.
1271 NodeImpl *node = nodeForWidget(startingWidget);
1273 return partForNode(node)->nextKeyView(node, direction);
1276 bool KWQKHTMLPart::currentEventIsMouseDownInWidget(QWidget *candidate)
1278 KWQ_BLOCK_EXCEPTIONS;
1279 switch ([[NSApp currentEvent] type]) {
1280 case NSLeftMouseDown:
1281 case NSRightMouseDown:
1282 case NSOtherMouseDown:
1287 KWQ_UNBLOCK_EXCEPTIONS;
1289 NodeImpl *node = nodeForWidget(candidate);
1291 return partForNode(node)->nodeUnderMouse() == node;
1294 bool KWQKHTMLPart::currentEventIsKeyboardOptionTab()
1296 KWQ_BLOCK_EXCEPTIONS;
1297 NSEvent *evt = [NSApp currentEvent];
1298 if ([evt type] != NSKeyDown) {
1302 if (([evt modifierFlags] & NSAlternateKeyMask) == 0) {
1306 NSString *chars = [evt charactersIgnoringModifiers];
1307 if ([chars length] != 1)
1310 const unichar tabKey = 0x0009;
1311 const unichar shiftTabKey = 0x0019;
1312 unichar c = [chars characterAtIndex:0];
1313 if (c != tabKey && c != shiftTabKey)
1316 KWQ_UNBLOCK_EXCEPTIONS;
1320 bool KWQKHTMLPart::handleKeyboardOptionTabInView(NSView *view)
1322 if (KWQKHTMLPart::currentEventIsKeyboardOptionTab()) {
1323 if (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) != 0) {
1324 [[view window] selectKeyViewPrecedingView:view];
1326 [[view window] selectKeyViewFollowingView:view];
1334 bool KWQKHTMLPart::tabsToLinks() const
1336 if ([_bridge keyboardUIMode] & WebCoreKeyboardAccessTabsToLinks)
1337 return !KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1339 return KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1342 bool KWQKHTMLPart::tabsToAllControls() const
1344 WebCoreKeyboardUIMode keyboardUIMode = [_bridge keyboardUIMode];
1345 BOOL handlingOptionTab = KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1347 // If tab-to-links is off, option-tab always highlights all controls
1348 if ((keyboardUIMode & WebCoreKeyboardAccessTabsToLinks) == 0 && handlingOptionTab) {
1352 // If system preferences say to include all controls, we always include all controls
1353 if (keyboardUIMode & WebCoreKeyboardAccessFull) {
1357 // Otherwise tab-to-links includes all controls, unless the sense is flipped via option-tab.
1358 if (keyboardUIMode & WebCoreKeyboardAccessTabsToLinks) {
1359 return !handlingOptionTab;
1362 return handlingOptionTab;
1365 KJS::Bindings::RootObject *KWQKHTMLPart::executionContextForDOM()
1367 return bindingRootObject();
1370 KJS::Bindings::RootObject *KWQKHTMLPart::bindingRootObject()
1372 if (!_bindingRoot) {
1373 _bindingRoot = new KJS::Bindings::RootObject(0); // The root gets deleted by JavaScriptCore.
1374 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1375 _bindingRoot->setRootObjectImp (win);
1376 _bindingRoot->setInterpreter (KJSProxy::proxy(this)->interpreter());
1377 addPluginRootObject (_bindingRoot);
1379 return _bindingRoot;
1382 WebScriptObject *KWQKHTMLPart::windowScriptObject()
1384 if (!_windowScriptObject) {
1385 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1386 _windowScriptObject = KWQRetainNSRelease([[WebScriptObject alloc] _initWithObjectImp:win root:bindingRootObject()]);
1389 return _windowScriptObject;
1392 NPObject *KWQKHTMLPart::windowScriptNPObject()
1394 if (!_windowScriptNPObject) {
1395 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1396 _windowScriptNPObject = _NPN_CreateScriptObject (0, win, bindingRootObject());
1399 return _windowScriptNPObject;
1402 void KWQKHTMLPart::partClearedInBegin()
1404 [_bridge windowObjectCleared];
1407 QMap<int, ScheduledAction*> *KWQKHTMLPart::pauseActions(const void *key)
1409 if (d->m_doc && d->m_jscript) {
1410 Window *w = Window::retrieveWindow(this);
1411 if (w && w->hasTimeouts()) {
1412 return w->pauseTimeouts(key);
1418 void KWQKHTMLPart::resumeActions(QMap<int, ScheduledAction*> *actions, const void *key)
1420 if (d->m_doc && d->m_jscript && d->m_bJScriptEnabled) {
1421 Window *w = Window::retrieveWindow(this);
1423 w->resumeTimeouts(actions, key);
1428 bool KWQKHTMLPart::canCachePage()
1430 // Only save page state if:
1431 // 1. We're not a frame or frameset.
1432 // 2. The page has no unload handler.
1433 // 3. The page has no password fields.
1434 // 4. The URL for the page is not https.
1435 // 5. The page has no applets.
1436 if (d->m_frames.count() ||
1438 m_url.protocol().startsWith("https") ||
1439 (d->m_doc && (htmlDocument().applets().length() != 0 ||
1440 d->m_doc->hasWindowEventListener(EventImpl::UNLOAD_EVENT) ||
1441 d->m_doc->hasPasswordField()))) {
1447 void KWQKHTMLPart::saveWindowProperties(SavedProperties *windowProperties)
1449 Window *window = Window::retrieveWindow(this);
1451 window->saveProperties(*windowProperties);
1454 void KWQKHTMLPart::saveLocationProperties(SavedProperties *locationProperties)
1456 Window *window = Window::retrieveWindow(this);
1458 Interpreter::lock();
1459 Location *location = window->location();
1460 Interpreter::unlock();
1461 location->saveProperties(*locationProperties);
1465 void KWQKHTMLPart::restoreWindowProperties(SavedProperties *windowProperties)
1467 Window *window = Window::retrieveWindow(this);
1469 window->restoreProperties(*windowProperties);
1472 void KWQKHTMLPart::restoreLocationProperties(SavedProperties *locationProperties)
1474 Window *window = Window::retrieveWindow(this);
1476 Interpreter::lock();
1477 Location *location = window->location();
1478 Interpreter::unlock();
1479 location->restoreProperties(*locationProperties);
1483 void KWQKHTMLPart::saveInterpreterBuiltins(SavedBuiltins &interpreterBuiltins)
1485 if (jScript() && jScript()->interpreter()) {
1486 jScript()->interpreter()->saveBuiltins(interpreterBuiltins);
1490 void KWQKHTMLPart::restoreInterpreterBuiltins(const SavedBuiltins &interpreterBuiltins)
1492 if (jScript() && jScript()->interpreter()) {
1493 jScript()->interpreter()->restoreBuiltins(interpreterBuiltins);
1497 void KWQKHTMLPart::openURLFromPageCache(KWQPageState *state)
1499 // It's safe to assume none of the KWQPageState methods will raise
1500 // exceptions, since KWQPageState is implemented by WebCore and
1503 DocumentImpl *doc = [state document];
1504 KURL *url = [state URL];
1505 SavedProperties *windowProperties = [state windowProperties];
1506 SavedProperties *locationProperties = [state locationProperties];
1507 SavedBuiltins *interpreterBuiltins = [state interpreterBuiltins];
1508 QMap<int, ScheduledAction*> *actions = [state pausedActions];
1510 cancelRedirection();
1512 // We still have to close the previous part page.
1513 if (!d->m_restored){
1517 d->m_bComplete = false;
1519 // Don't re-emit the load event.
1520 d->m_bLoadEventEmitted = true;
1522 // delete old status bar msg's from kjs (if it _was_ activated on last URL)
1523 if( d->m_bJScriptEnabled )
1525 d->m_kjsStatusBarText = QString::null;
1526 d->m_kjsDefaultStatusBarText = QString::null;
1533 // 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
1534 // data arrives) (Simon)
1535 if(m_url.protocol().startsWith( "http" ) && !m_url.host().isEmpty() && m_url.path().isEmpty()) {
1537 emit d->m_extension->setLocationBarURL( m_url.prettyURL() );
1540 // copy to m_workingURL after fixing m_url above
1541 d->m_workingURL = m_url;
1545 // -----------begin-----------
1548 doc->setInPageCache(NO);
1550 d->m_bCleared = false;
1552 d->m_bComplete = false;
1553 d->m_bLoadEventEmitted = false;
1554 d->m_referrer = m_url.url();
1556 setView(doc->view());
1561 Decoder *decoder = doc->decoder();
1566 d->m_decoder->deref();
1568 d->m_decoder = decoder;
1570 updatePolicyBaseURL();
1572 restoreWindowProperties (windowProperties);
1573 restoreLocationProperties (locationProperties);
1574 restoreInterpreterBuiltins (*interpreterBuiltins);
1577 resumeActions (actions, state);
1582 KWQKHTMLPart *KWQKHTMLPart::partForWidget(const QWidget *widget)
1584 ASSERT_ARG(widget, widget);
1586 NodeImpl *node = nodeForWidget(widget);
1588 return partForNode(node);
1591 // Assume all widgets are either form controls, or KHTMLViews.
1592 const KHTMLView *view = dynamic_cast<const KHTMLView *>(widget);
1594 return KWQ(view->part());
1597 WebCoreBridge *KWQKHTMLPart::bridgeForWidget(const QWidget *widget)
1599 ASSERT_ARG(widget, widget);
1601 KWQKHTMLPart *part = partForWidget(widget);
1603 return part->bridge();
1606 KWQKHTMLPart *KWQKHTMLPart::partForNode(NodeImpl *node)
1608 ASSERT_ARG(node, node);
1609 return KWQ(node->getDocument()->part());
1612 NSView *KWQKHTMLPart::documentViewForNode(DOM::NodeImpl *node)
1614 WebCoreBridge *bridge = partForNode(node)->bridge();
1615 return [bridge documentView];
1618 NodeImpl *KWQKHTMLPart::nodeForWidget(const QWidget *widget)
1620 ASSERT_ARG(widget, widget);
1621 const QObject *o = widget->eventFilterObject();
1622 return o ? static_cast<const RenderWidget *>(o)->element() : 0;
1625 void KWQKHTMLPart::setDocumentFocus(QWidget *widget)
1627 NodeImpl *node = nodeForWidget(widget);
1629 node->getDocument()->setFocusNode(node);
1631 ERROR("unable to clear focus because widget had no corresponding node");
1635 void KWQKHTMLPart::clearDocumentFocus(QWidget *widget)
1637 NodeImpl *node = nodeForWidget(widget);
1639 node->getDocument()->setFocusNode(0);
1641 ERROR("unable to clear focus because widget had no corresponding node");
1645 void KWQKHTMLPart::saveDocumentState()
1647 // Do not save doc state if the page has a password field and a form that would be submitted
1649 if (!(d->m_doc && d->m_doc->hasPasswordField() && d->m_doc->hasSecureForm())) {
1650 KWQ_BLOCK_EXCEPTIONS;
1651 [_bridge saveDocumentState];
1652 KWQ_UNBLOCK_EXCEPTIONS;
1656 void KWQKHTMLPart::restoreDocumentState()
1658 KWQ_BLOCK_EXCEPTIONS;
1659 [_bridge restoreDocumentState];
1660 KWQ_UNBLOCK_EXCEPTIONS;
1663 QPtrList<KWQKHTMLPart> &KWQKHTMLPart::mutableInstances()
1665 static QPtrList<KWQKHTMLPart> instancesList;
1666 return instancesList;
1669 void KWQKHTMLPart::updatePolicyBaseURL()
1671 if (parentPart() && parentPart()->xmlDocImpl()) {
1672 setPolicyBaseURL(parentPart()->xmlDocImpl()->policyBaseURL());
1674 setPolicyBaseURL(m_url.url());
1678 void KWQKHTMLPart::setPolicyBaseURL(const DOMString &s)
1681 xmlDocImpl()->setPolicyBaseURL(s);
1682 ConstFrameIt end = d->m_frames.end();
1683 for (ConstFrameIt it = d->m_frames.begin(); it != end; ++it) {
1684 ReadOnlyPart *subpart = (*it).m_part;
1685 static_cast<KWQKHTMLPart *>(subpart)->setPolicyBaseURL(s);
1689 QString KWQKHTMLPart::requestedURLString() const
1691 KWQ_BLOCK_EXCEPTIONS;
1692 return QString::fromNSString([_bridge requestedURLString]);
1693 KWQ_UNBLOCK_EXCEPTIONS;
1698 QString KWQKHTMLPart::incomingReferrer() const
1700 KWQ_BLOCK_EXCEPTIONS;
1701 return QString::fromNSString([_bridge incomingReferrer]);
1702 KWQ_UNBLOCK_EXCEPTIONS;
1707 void KWQKHTMLPart::forceLayout()
1709 KHTMLView *v = d->m_view;
1712 // We cannot unschedule a pending relayout, since the force can be called with
1713 // a tiny rectangle from a drawRect update. By unscheduling we in effect
1714 // "validate" and stop the necessary full repaint from occurring. Basically any basic
1715 // append/remove DHTML is broken by this call. For now, I have removed the optimization
1716 // until we have a better invalidation stategy. -dwh
1717 //v->unscheduleRelayout();
1721 void KWQKHTMLPart::forceLayoutWithPageWidthRange(float minPageWidth, float maxPageWidth)
1723 // Dumping externalRepresentation(_part->renderer()).ascii() is a good trick to see
1724 // the state of things before and after the layout
1725 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
1727 // This magic is basically copied from khtmlview::print
1728 int pageW = (int)ceil(minPageWidth);
1729 root->setWidth(pageW);
1730 root->setNeedsLayoutAndMinMaxRecalc();
1733 // If we don't fit in the minimum page width, we'll lay out again. If we don't fit in the
1734 // maximum page width, we will lay out to the maximum page width and clip extra content.
1735 // FIXME: We are assuming a shrink-to-fit printing implementation. A cropping
1736 // implementation should not do this!
1737 int rightmostPos = root->rightmostPosition();
1738 if (rightmostPos > minPageWidth) {
1739 pageW = kMin(rightmostPos, (int)ceil(maxPageWidth));
1740 root->setWidth(pageW);
1741 root->setNeedsLayoutAndMinMaxRecalc();
1747 void KWQKHTMLPart::sendResizeEvent()
1749 KHTMLView *v = d->m_view;
1756 void KWQKHTMLPart::sendScrollEvent()
1758 KHTMLView *v = d->m_view;
1760 DocumentImpl *doc = xmlDocImpl();
1763 doc->dispatchHTMLEvent(EventImpl::SCROLL_EVENT, true, false);
1767 void KWQKHTMLPart::runJavaScriptAlert(const QString &message)
1769 QString text = message;
1770 text.replace('\\', backslashAsCurrencySymbol());
1771 KWQ_BLOCK_EXCEPTIONS;
1772 [_bridge runJavaScriptAlertPanelWithMessage:text.getNSString()];
1773 KWQ_UNBLOCK_EXCEPTIONS;
1776 bool KWQKHTMLPart::runJavaScriptConfirm(const QString &message)
1778 QString text = message;
1779 text.replace('\\', backslashAsCurrencySymbol());
1781 KWQ_BLOCK_EXCEPTIONS;
1782 return [_bridge runJavaScriptConfirmPanelWithMessage:text.getNSString()];
1783 KWQ_UNBLOCK_EXCEPTIONS;
1788 bool KWQKHTMLPart::runJavaScriptPrompt(const QString &prompt, const QString &defaultValue, QString &result)
1790 QString promptText = prompt;
1791 promptText.replace('\\', backslashAsCurrencySymbol());
1792 QString defaultValueText = defaultValue;
1793 defaultValueText.replace('\\', backslashAsCurrencySymbol());
1795 KWQ_BLOCK_EXCEPTIONS;
1796 NSString *returnedText = nil;
1798 bool ok = [_bridge runJavaScriptTextInputPanelWithPrompt:prompt.getNSString()
1799 defaultText:defaultValue.getNSString() returningText:&returnedText];
1802 result = QString::fromNSString(returnedText);
1803 result.replace(backslashAsCurrencySymbol(), '\\');
1807 KWQ_UNBLOCK_EXCEPTIONS;
1812 bool KWQKHTMLPart::locationbarVisible()
1814 return [_bridge areToolbarsVisible];
1817 bool KWQKHTMLPart::menubarVisible()
1819 // The menubar is always on in Mac OS X UI
1823 bool KWQKHTMLPart::personalbarVisible()
1825 return [_bridge areToolbarsVisible];
1828 bool KWQKHTMLPart::scrollbarsVisible()
1833 if (view()->hScrollBarMode() == QScrollView::AlwaysOff || view()->vScrollBarMode() == QScrollView::AlwaysOff)
1839 bool KWQKHTMLPart::statusbarVisible()
1841 return [_bridge isStatusBarVisible];
1844 bool KWQKHTMLPart::toolbarVisible()
1846 return [_bridge areToolbarsVisible];
1849 void KWQKHTMLPart::addMessageToConsole(const QString &message, unsigned lineNumber, const QString &sourceURL)
1851 NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
1852 message.getNSString(), @"message",
1853 [NSNumber numberWithInt: lineNumber], @"lineNumber",
1854 sourceURL.getNSString(), @"sourceURL",
1856 [_bridge addMessageToConsole:dictionary];
1859 void KWQKHTMLPart::createEmptyDocument()
1861 // Although it's not completely clear from the name of this function,
1862 // it does nothing if we already have a document, and just creates an
1863 // empty one if we have no document at all.
1865 KWQ_BLOCK_EXCEPTIONS;
1866 [_bridge loadEmptyDocumentSynchronously];
1867 KWQ_UNBLOCK_EXCEPTIONS;
1869 if (parentPart() && (parentPart()->childFrame(this)->m_type == ChildFrame::IFrame ||
1870 parentPart()->childFrame(this)->m_type == ChildFrame::Object)) {
1871 d->m_doc->setBaseURL(parentPart()->d->m_doc->baseURL());
1876 void KWQKHTMLPart::addMetaData(const QString &key, const QString &value)
1878 d->m_job->addMetaData(key, value);
1881 bool KWQKHTMLPart::keyEvent(NSEvent *event)
1883 KWQ_BLOCK_EXCEPTIONS;
1885 ASSERT([event type] == NSKeyDown || [event type] == NSKeyUp);
1887 // Check for cases where we are too early for events -- possible unmatched key up
1888 // from pressing return in the location bar.
1889 DocumentImpl *doc = xmlDocImpl();
1893 NodeImpl *node = doc->focusNode();
1901 if ([event type] == NSKeyDown) {
1902 prepareForUserAction();
1905 NSEvent *oldCurrentEvent = _currentEvent;
1906 _currentEvent = KWQRetain(event);
1908 QKeyEvent qEvent(event);
1909 bool result = !node->dispatchKeyEvent(&qEvent);
1911 // We want to send both a down and a press for the initial key event.
1912 // To get KHTML to do this, we send a second KeyPress QKeyEvent with "is repeat" set to true,
1913 // which causes it to send a press to the DOM.
1914 // That's not a great hack; it would be good to do this in a better way.
1915 if ([event type] == NSKeyDown && ![event isARepeat]) {
1916 QKeyEvent repeatEvent(event, true);
1917 if (!node->dispatchKeyEvent(&repeatEvent)) {
1922 ASSERT(_currentEvent == event);
1924 _currentEvent = oldCurrentEvent;
1928 KWQ_UNBLOCK_EXCEPTIONS;
1933 // This does the same kind of work that KHTMLPart::openURL does, except it relies on the fact
1934 // that a higher level already checked that the URLs match and the scrolling is the right thing to do.
1935 void KWQKHTMLPart::scrollToAnchor(const KURL &URL)
1937 cancelRedirection();
1944 // It's important to model this as a load that starts and immediately finishes.
1945 // Otherwise, the parent frame may think we never finished loading.
1946 d->m_bComplete = false;
1950 bool KWQKHTMLPart::closeURL()
1952 saveDocumentState();
1953 return KHTMLPart::closeURL();
1956 void KWQKHTMLPart::khtmlMousePressEvent(MousePressEvent *event)
1958 bool singleClick = [_currentEvent clickCount] <= 1;
1960 // If we got the event back, that must mean it wasn't prevented,
1961 // so it's allowed to start a drag or selection.
1962 _mouseDownMayStartSelect = true;
1963 // Careful that the drag starting logic stays in sync with eventMayStartDrag()
1964 _mouseDownMayStartDrag = singleClick;
1966 d->m_mousePressNode = event->innerNode();
1968 if (!passWidgetMouseDownEventToWidget(event)) {
1969 // We don't do this at the start of mouse down handling (before calling into WebCore),
1970 // because we don't want to do it until we know we didn't hit a widget.
1971 NSView *view = d->m_view->getDocumentView();
1974 KWQ_BLOCK_EXCEPTIONS;
1975 if ([_bridge firstResponder] != view) {
1976 [_bridge makeFirstResponder:view];
1978 KWQ_UNBLOCK_EXCEPTIONS;
1981 KHTMLPart::khtmlMousePressEvent(event);
1985 void KWQKHTMLPart::khtmlMouseDoubleClickEvent(MouseDoubleClickEvent *event)
1987 if (!passWidgetMouseDownEventToWidget(event)) {
1988 KHTMLPart::khtmlMouseDoubleClickEvent(event);
1992 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(khtml::MouseEvent *event)
1994 // Figure out which view to send the event to.
1995 RenderObject *target = event->innerNode().handle() ? event->innerNode().handle()->renderer() : 0;
1999 QWidget* widget = RenderLayer::gScrollBar;
2001 if (!target->isWidget())
2003 widget = static_cast<RenderWidget *>(target)->widget();
2006 // Doubleclick events don't exist in Cocoa. Since passWidgetMouseDownEventToWidget will
2007 // just pass _currentEvent down to the widget, we don't want to call it for events that
2008 // don't correspond to Cocoa events. The mousedown/ups will have already been passed on as
2009 // part of the pressed/released handling.
2010 if (!MouseDoubleClickEvent::test(event))
2011 return passWidgetMouseDownEventToWidget(widget);
2016 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(RenderWidget *renderWidget)
2018 return passWidgetMouseDownEventToWidget(renderWidget->widget());
2021 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(QWidget* widget)
2023 // FIXME: this method always returns true
2026 ERROR("hit a RenderWidget without a corresponding QWidget, means a frame is half-constructed");
2030 KWQ_BLOCK_EXCEPTIONS;
2032 NSView *nodeView = widget->getView();
2034 ASSERT([nodeView superview]);
2035 NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[_currentEvent locationInWindow] fromView:nil]];
2037 ERROR("KHTML says we hit a RenderWidget, but AppKit doesn't agree we hit the corresponding NSView");
2041 if ([_bridge firstResponder] == view) {
2042 // In the case where we just became first responder, we should send the mouseDown:
2043 // to the NSTextField, not the NSTextField's editor. This code makes sure that happens.
2044 // If we don't do this, we see a flash of selected text when clicking in a text field.
2045 if (![_bridge wasFirstResponderAtMouseDownTime:view] && [view isKindOfClass:[NSTextView class]]) {
2046 NSView *superview = view;
2047 while (superview != nodeView) {
2048 superview = [superview superview];
2050 if ([superview isKindOfClass:[NSControl class]]) {
2051 NSControl *control = superview;
2052 if ([control currentEditor] == view) {
2060 // Normally [NSWindow sendEvent:] handles setting the first responder.
2061 // But in our case, the event was sent to the view representing the entire web page.
2062 if ([_currentEvent clickCount] <= 1 && [view acceptsFirstResponder] && [view needsPanelToBecomeKey]) {
2063 [_bridge makeFirstResponder:view];
2067 // We need to "defer loading" and defer timers while we are tracking the mouse.
2068 // That's because we don't want the new page to load while the user is holding the mouse down.
2070 BOOL wasDeferringLoading = [_bridge defersLoading];
2071 if (!wasDeferringLoading) {
2072 [_bridge setDefersLoading:YES];
2074 BOOL wasDeferringTimers = QObject::defersTimers();
2075 if (!wasDeferringTimers) {
2076 QObject::setDefersTimers(true);
2079 ASSERT(!_sendingEventToSubview);
2080 _sendingEventToSubview = true;
2081 [view mouseDown:_currentEvent];
2082 _sendingEventToSubview = false;
2084 if (!wasDeferringTimers) {
2085 QObject::setDefersTimers(false);
2087 if (!wasDeferringLoading) {
2088 [_bridge setDefersLoading:NO];
2091 // Remember which view we sent the event to, so we can direct the release event properly.
2092 _mouseDownView = view;
2093 _mouseDownWasInSubframe = false;
2095 KWQ_UNBLOCK_EXCEPTIONS;
2100 bool KWQKHTMLPart::lastEventIsMouseUp() const
2102 // Many AK widgets run their own event loops and consume events while the mouse is down.
2103 // When they finish, currentEvent is the mouseUp that they exited on. We need to update
2104 // the khtml state with this mouseUp, which khtml never saw. This method lets us detect
2107 KWQ_BLOCK_EXCEPTIONS;
2108 NSEvent *currentEventAfterHandlingMouseDown = [NSApp currentEvent];
2109 if (_currentEvent != currentEventAfterHandlingMouseDown) {
2110 if ([currentEventAfterHandlingMouseDown type] == NSLeftMouseUp) {
2114 KWQ_UNBLOCK_EXCEPTIONS;
2119 // Note that this does the same kind of check as [target isDescendantOf:superview].
2120 // There are two differences: This is a lot slower because it has to walk the whole
2121 // tree, and this works in cases where the target has already been deallocated.
2122 static bool findViewInSubviews(NSView *superview, NSView *target)
2124 KWQ_BLOCK_EXCEPTIONS;
2125 NSEnumerator *e = [[superview subviews] objectEnumerator];
2127 while ((subview = [e nextObject])) {
2128 if (subview == target || findViewInSubviews(subview, target)) {
2132 KWQ_UNBLOCK_EXCEPTIONS;
2137 NSView *KWQKHTMLPart::mouseDownViewIfStillGood()
2139 // Since we have no way of tracking the lifetime of _mouseDownView, we have to assume that
2140 // it could be deallocated already. We search for it in our subview tree; if we don't find
2141 // it, we set it to nil.
2142 NSView *mouseDownView = _mouseDownView;
2143 if (!mouseDownView) {
2146 KHTMLView *topKHTMLView = d->m_view;
2147 NSView *topView = topKHTMLView ? topKHTMLView->getView() : nil;
2148 if (!topView || !findViewInSubviews(topView, mouseDownView)) {
2149 _mouseDownView = nil;
2152 return mouseDownView;
2155 // The link drag hysteresis is much larger than the others because there
2156 // needs to be enough space to cancel the link press without starting a link drag,
2157 // and because dragging links is rare.
2158 #define LinkDragHysteresis 40.0
2159 #define ImageDragHysteresis 5.0
2160 #define TextDragHysteresis 3.0
2161 #define GeneralDragHysteresis 3.0
2163 #define TextDragDelay 0.15
2165 bool KWQKHTMLPart::dragHysteresisExceeded(float dragLocationX, float dragLocationY) const
2168 d->m_view->viewportToContents((int)dragLocationX, (int)dragLocationY, dragX, dragY);
2169 float deltaX = QABS(dragX - _mouseDownX);
2170 float deltaY = QABS(dragY - _mouseDownY);
2172 float threshold = GeneralDragHysteresis;
2173 if (_dragSrcIsImage) {
2174 threshold = ImageDragHysteresis;
2175 } else if (_dragSrcIsLink) {
2176 threshold = LinkDragHysteresis;
2177 } else if (_dragSrcInSelection) {
2178 threshold = TextDragHysteresis;
2180 return deltaX >= threshold || deltaY >= threshold;
2183 // returns if we should continue "default processing", i.e., whether eventhandler canceled
2184 bool KWQKHTMLPart::dispatchDragSrcEvent(int eventId, const QPoint &loc) const
2186 bool noDefaultProc = d->m_view->dispatchDragEvent(eventId, _dragSrc.handle(), loc, _dragClipboard);
2187 return !noDefaultProc;
2190 bool KWQKHTMLPart::eventMayStartDrag(NSEvent *event) const
2192 // This is a pre-flight check of whether the event might lead to a drag being started. Be careful
2193 // that its logic needs to stay in sync with khtmlMouseMoveEvent() and the way we set
2194 // _mouseDownMayStartDrag in khtmlMousePressEvent
2196 if ([event type] != NSLeftMouseDown || [event clickCount] != 1) {
2200 BOOL DHTMLFlag, UAFlag;
2201 [_bridge allowDHTMLDrag:&DHTMLFlag UADrag:&UAFlag];
2202 if (!DHTMLFlag && !UAFlag) {
2206 NSPoint loc = [event locationInWindow];
2207 int mouseDownX, mouseDownY;
2208 d->m_view->viewportToContents((int)loc.x, (int)loc.y, mouseDownX, mouseDownY);
2209 RenderObject::NodeInfo nodeInfo(true, false);
2210 renderer()->layer()->hitTest(nodeInfo, mouseDownX, mouseDownY);
2212 Node possibleSrc = nodeInfo.innerNode()->renderer()->draggableNode(DHTMLFlag, UAFlag, mouseDownX, mouseDownY, srcIsDHTML);
2213 return !possibleSrc.isNull();
2216 void KWQKHTMLPart::khtmlMouseMoveEvent(MouseMoveEvent *event)
2218 KWQ_BLOCK_EXCEPTIONS;
2220 if ([_currentEvent type] == NSLeftMouseDragged) {
2221 NSView *view = mouseDownViewIfStillGood();
2224 _sendingEventToSubview = true;
2225 [view mouseDragged:_currentEvent];
2226 _sendingEventToSubview = false;
2230 // Careful that the drag starting logic stays in sync with eventMayStartDrag()
2232 if (_mouseDownMayStartDrag && _dragSrc.isNull()) {
2233 BOOL tempFlag1, tempFlag2;
2234 [_bridge allowDHTMLDrag:&tempFlag1 UADrag:&tempFlag2];
2235 _dragSrcMayBeDHTML = tempFlag1;
2236 _dragSrcMayBeUA = tempFlag2;
2237 if (!_dragSrcMayBeDHTML && !_dragSrcMayBeUA) {
2238 _mouseDownMayStartDrag = false; // no element is draggable
2242 if (_mouseDownMayStartDrag && _dragSrc.isNull()) {
2243 // try to find an element that wants to be dragged
2244 RenderObject::NodeInfo nodeInfo(true, false);
2245 renderer()->layer()->hitTest(nodeInfo, _mouseDownX, _mouseDownY);
2246 _dragSrc = nodeInfo.innerNode()->renderer()->draggableNode(_dragSrcMayBeDHTML, _dragSrcMayBeUA, _mouseDownX, _mouseDownY, _dragSrcIsDHTML);
2247 if (_dragSrc.isNull()) {
2248 _mouseDownMayStartDrag = false; // no element is draggable
2250 // remember some facts about this source, while we have a NodeInfo handy
2251 NodeImpl *node = nodeInfo.URLElement();
2252 _dragSrcIsLink = node ? node->hasAnchor() : false;
2254 node = nodeInfo.innerNonSharedNode();
2255 _dragSrcIsImage = (node && node->renderer() && node->renderer()->isImage());
2257 _dragSrcInSelection = isPointInsideSelection(_mouseDownX, _mouseDownY);
2261 // For drags starting in the selection, the user must wait between the mousedown and mousedrag,
2262 // or else we bail on the dragging stuff and allow selection to occur
2263 if (_mouseDownMayStartDrag && _dragSrcInSelection && [_currentEvent timestamp] - _mouseDownTimestamp < TextDragDelay) {
2264 _mouseDownMayStartDrag = false;
2265 // ...but if this was the first click in the window, we don't even want to start selection
2266 if (_activationEventNumber == [_currentEvent eventNumber]) {
2267 _mouseDownMayStartSelect = false;
2271 if (_mouseDownMayStartDrag) {
2272 // We are starting a text/image/url drag, so the cursor should be an arrow
2273 d->m_view->resetCursor();
2275 NSPoint dragLocation = [_currentEvent locationInWindow];
2276 if (dragHysteresisExceeded(dragLocation.x, dragLocation.y)) {
2278 // Once we're past the hysteresis point, we don't want to treat this gesture as a click
2279 d->m_view->invalidateClick();
2281 NSImage *dragImage = nil; // we use these values if WC is out of the loop
2282 NSPoint dragLoc = NSZeroPoint;
2283 NSDragOperation srcOp = NSDragOperationNone;
2284 BOOL wcWrotePasteboard = NO;
2285 if (_dragSrcMayBeDHTML) {
2286 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
2287 // Must be done before ondragstart adds types and data to the pboard,
2288 // also done for security, as it erases data from the last drag
2289 [pasteboard declareTypes:[NSArray array] owner:nil];
2291 freeClipboard(); // would only happen if we missed a dragEnd. Do it anyway, just
2292 // to make sure it gets numbified
2293 _dragClipboard = new KWQClipboard(true, pasteboard, KWQClipboard::Writable, this);
2294 _dragClipboard->ref();
2296 // If this is drag of an element, get set up to generate a default image. Otherwise
2297 // WebKit will generate the default, the element doesn't override.
2298 if (_dragSrcIsDHTML) {
2300 _dragSrc.handle()->renderer()->absolutePosition(srcX, srcY);
2301 _dragClipboard->setDragImageElement(_dragSrc, QPoint(_mouseDownX - srcX, _mouseDownY - srcY));
2304 _mouseDownMayStartDrag = dispatchDragSrcEvent(EventImpl::DRAGSTART_EVENT, QPoint(_mouseDownWinX, _mouseDownWinY));
2305 // Invalidate clipboard here against anymore pasteboard writing for security. The drag
2306 // image can still be changed as we drag, but not the pasteboard data.
2307 _dragClipboard->setAccessPolicy(KWQClipboard::ImageWritable);
2309 if (_mouseDownMayStartDrag) {
2310 // gather values from DHTML element, if it set any
2311 _dragClipboard->sourceOperation(&srcOp);
2313 NSArray *types = [pasteboard types];
2314 wcWrotePasteboard = types && [types count] > 0;
2316 if (_dragSrcMayBeDHTML) {
2317 dragImage = _dragClipboard->dragNSImage(&dragLoc);
2320 // Yuck, dragSourceMovedTo() can be called as a result of kicking off the drag with
2321 // dragImage! Because of that dumb reentrancy, we may think we've not started the
2322 // drag when that happens. So we have to assume it's started before we kick it off.
2323 _dragClipboard->setDragHasStarted();
2327 if (_mouseDownMayStartDrag) {
2328 BOOL startedDrag = [_bridge startDraggingImage:dragImage at:dragLoc operation:srcOp event:_currentEvent sourceIsDHTML:_dragSrcIsDHTML DHTMLWroteData:wcWrotePasteboard];
2329 if (!startedDrag && _dragSrcMayBeDHTML) {
2330 // WebKit canned the drag at the last minute - we owe _dragSrc a DRAGEND event
2331 dispatchDragSrcEvent(EventImpl::DRAGEND_EVENT, QPoint(dragLocation));
2332 _mouseDownMayStartDrag = false;
2336 if (!_mouseDownMayStartDrag) {
2337 // something failed to start the drag, cleanup
2343 // No more default handling (like selection), whether we're past the hysteresis bounds or not
2346 if (!_mouseDownMayStartSelect) {
2350 // Don't allow dragging or click handling after we've started selecting.
2351 _mouseDownMayStartDrag = false;
2352 d->m_view->invalidateClick();
2354 // We use khtml's selection but our own autoscrolling.
2355 [_bridge handleAutoscrollForMouseDragged:_currentEvent];
2357 // If we allowed the other side of the bridge to handle a drag
2358 // last time, then m_bMousePressed might still be set. So we
2359 // clear it now to make sure the next move after a drag
2360 // doesn't look like a drag.
2361 d->m_bMousePressed = false;
2364 KHTMLPart::khtmlMouseMoveEvent(event);
2366 KWQ_UNBLOCK_EXCEPTIONS;
2369 void KWQKHTMLPart::dragSourceMovedTo(const QPoint &loc)
2371 if (!_dragSrc.isNull() && _dragSrcMayBeDHTML) {
2372 // for now we don't care if event handler cancels default behavior, since there is none
2373 dispatchDragSrcEvent(EventImpl::DRAG_EVENT, loc);
2377 void KWQKHTMLPart::dragSourceEndedAt(const QPoint &loc, NSDragOperation operation)
2379 if (!_dragSrc.isNull() && _dragSrcMayBeDHTML) {
2380 _dragClipboard->setDestinationOperation(operation);
2381 // for now we don't care if event handler cancels default behavior, since there is none
2382 dispatchDragSrcEvent(EventImpl::DRAGEND_EVENT, loc);
2388 // Returns whether caller should continue with "the default processing", which is the same as
2389 // the event handler NOT setting the return value to false
2390 bool KWQKHTMLPart::dispatchCPPEvent(int eventId, KWQClipboard::AccessPolicy policy)
2392 NodeImpl *target = d->m_selection.start().element();
2393 if (!target && xmlDocImpl()) {
2394 target = xmlDocImpl()->body();
2400 KWQClipboard *clipboard = new KWQClipboard(false, [NSPasteboard generalPasteboard], (KWQClipboard::AccessPolicy)policy);
2403 int exceptioncode = 0;
2404 EventImpl *evt = new ClipboardEventImpl(static_cast<EventImpl::EventId>(eventId), true, true, clipboard);
2406 target->dispatchEvent(evt, exceptioncode, true);
2407 bool noDefaultProcessing = evt->defaultPrevented();
2410 // invalidate clipboard here for security
2411 clipboard->setAccessPolicy(KWQClipboard::Numb);
2414 return !noDefaultProcessing;
2417 // WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They
2418 // also send onbeforecopy, apparently for symmetry, but it doesn't affect the menu items.
2419 // We need to use onbeforecopy as a real menu enabler because we allow elements that are not
2420 // normally selectable to implement copy/paste (like divs, or a document body).
2422 bool KWQKHTMLPart::mayCut()
2424 return !dispatchCPPEvent(EventImpl::BEFORECUT_EVENT, KWQClipboard::Numb);
2427 bool KWQKHTMLPart::mayCopy()
2429 return !dispatchCPPEvent(EventImpl::BEFORECOPY_EVENT, KWQClipboard::Numb);
2432 bool KWQKHTMLPart::mayPaste()
2434 return !dispatchCPPEvent(EventImpl::BEFOREPASTE_EVENT, KWQClipboard::Numb);
2437 bool KWQKHTMLPart::tryCut()
2439 // Must be done before oncut adds types and data to the pboard,
2440 // also done for security, as it erases data from the last copy/paste.
2441 [[NSPasteboard generalPasteboard] declareTypes:[NSArray array] owner:nil];
2443 return !dispatchCPPEvent(EventImpl::CUT_EVENT, KWQClipboard::Writable);
2446 bool KWQKHTMLPart::tryCopy()
2448 // Must be done before oncopy adds types and data to the pboard,
2449 // also done for security, as it erases data from the last copy/paste.
2450 [[NSPasteboard generalPasteboard] declareTypes:[NSArray array] owner:nil];
2452 return !dispatchCPPEvent(EventImpl::COPY_EVENT, KWQClipboard::Writable);
2455 bool KWQKHTMLPart::tryPaste()
2457 return !dispatchCPPEvent(EventImpl::PASTE_EVENT, KWQClipboard::Readable);
2460 void KWQKHTMLPart::khtmlMouseReleaseEvent(MouseReleaseEvent *event)
2462 NSView *view = mouseDownViewIfStillGood();
2464 // If this was the first click in the window, we don't even want to clear the selection.
2465 // This case occurs when the user clicks on a draggable element, since we have to process
2466 // the mouse down and drag events to see if we might start a drag. For other first clicks
2467 // in a window, we just don't acceptFirstMouse, and the whole down-drag-up sequence gets
2468 // ignored upstream of this layer.
2469 if (_activationEventNumber != [_currentEvent eventNumber]) {
2470 KHTMLPart::khtmlMouseReleaseEvent(event);
2475 _sendingEventToSubview = true;
2476 KWQ_BLOCK_EXCEPTIONS;
2477 [view mouseUp:_currentEvent];
2478 KWQ_UNBLOCK_EXCEPTIONS;
2479 _sendingEventToSubview = false;
2482 void KWQKHTMLPart::clearTimers(KHTMLView *view)
2485 view->unscheduleRelayout();
2487 DocumentImpl* document = view->part()->xmlDocImpl();
2488 if (document && document->renderer() && document->renderer()->layer())
2489 document->renderer()->layer()->suspendMarquees();
2494 void KWQKHTMLPart::clearTimers()
2496 clearTimers(d->m_view);
2499 bool KWQKHTMLPart::passSubframeEventToSubframe(NodeImpl::MouseEvent &event)
2501 KWQ_BLOCK_EXCEPTIONS;
2503 switch ([_currentEvent type]) {
2504 case NSLeftMouseDown: {
2505 NodeImpl *node = event.innerNode.handle();
2509 RenderPart *renderPart = dynamic_cast<RenderPart *>(node->renderer());
2513 if (!passWidgetMouseDownEventToWidget(renderPart)) {
2516 _mouseDownWasInSubframe = true;
2519 case NSLeftMouseUp: {
2520 if (!_mouseDownWasInSubframe) {
2523 NSView *view = mouseDownViewIfStillGood();
2527 ASSERT(!_sendingEventToSubview);
2528 _sendingEventToSubview = true;
2529 [view mouseUp:_currentEvent];
2530 _sendingEventToSubview = false;
2533 case NSLeftMouseDragged: {
2534 if (!_mouseDownWasInSubframe) {
2537 NSView *view = mouseDownViewIfStillGood();
2541 ASSERT(!_sendingEventToSubview);
2542 _sendingEventToSubview = true;
2543 [view mouseDragged:_currentEvent];
2544 _sendingEventToSubview = false;
2550 KWQ_UNBLOCK_EXCEPTIONS;
2555 void KWQKHTMLPart::mouseDown(NSEvent *event)
2557 KHTMLView *v = d->m_view;
2558 if (!v || _sendingEventToSubview) {
2562 KWQ_BLOCK_EXCEPTIONS;
2564 prepareForUserAction();
2566 _mouseDownView = nil;
2569 NSEvent *oldCurrentEvent = _currentEvent;
2570 _currentEvent = KWQRetain(event);
2571 NSPoint loc = [event locationInWindow];
2572 _mouseDownWinX = (int)loc.x;
2573 _mouseDownWinY = (int)loc.y;
2574 d->m_view->viewportToContents(_mouseDownWinX, _mouseDownWinY, _mouseDownX, _mouseDownY);
2575 _mouseDownTimestamp = [event timestamp];
2577 _mouseDownMayStartDrag = false;
2578 _mouseDownMayStartSelect = false;
2580 QMouseEvent kEvent(QEvent::MouseButtonPress, event);
2581 v->viewportMousePressEvent(&kEvent);
2583 ASSERT(_currentEvent == event);
2585 _currentEvent = oldCurrentEvent;
2587 KWQ_UNBLOCK_EXCEPTIONS;
2590 void KWQKHTMLPart::mouseDragged(NSEvent *event)
2592 KHTMLView *v = d->m_view;
2593 if (!v || _sendingEventToSubview) {
2597 KWQ_BLOCK_EXCEPTIONS;
2599 NSEvent *oldCurrentEvent = _currentEvent;
2600 _currentEvent = KWQRetain(event);
2602 QMouseEvent kEvent(QEvent::MouseMove, event);
2603 v->viewportMouseMoveEvent(&kEvent);
2605 ASSERT(_currentEvent == event);
2607 _currentEvent = oldCurrentEvent;
2609 KWQ_UNBLOCK_EXCEPTIONS;
2612 void KWQKHTMLPart::mouseUp(NSEvent *event)
2614 KHTMLView *v = d->m_view;
2615 if (!v || _sendingEventToSubview) {
2619 KWQ_BLOCK_EXCEPTIONS;
2621 NSEvent *oldCurrentEvent = _currentEvent;
2622 _currentEvent = KWQRetain(event);
2624 // Our behavior here is a little different that Qt. Qt always sends
2625 // a mouse release event, even for a double click. To correct problems
2626 // in khtml's DOM click event handling we do not send a release here
2627 // for a double click. Instead we send that event from KHTMLView's
2628 // viewportMouseDoubleClickEvent. Note also that the third click of
2629 // a triple click is treated as a single click, but the fourth is then
2630 // treated as another double click. Hence the "% 2" below.
2631 int clickCount = [event clickCount];
2632 if (clickCount > 0 && clickCount % 2 == 0) {
2633 QMouseEvent doubleClickEvent(QEvent::MouseButtonDblClick, event);
2634 v->viewportMouseDoubleClickEvent(&doubleClickEvent);
2636 QMouseEvent releaseEvent(QEvent::MouseButtonRelease, event);
2637 v->viewportMouseReleaseEvent(&releaseEvent);
2640 ASSERT(_currentEvent == event);
2642 _currentEvent = oldCurrentEvent;
2644 _mouseDownView = nil;
2646 KWQ_UNBLOCK_EXCEPTIONS;
2650 A hack for the benefit of AK's PopUpButton, which uses the Carbon menu manager, which thus
2651 eats all subsequent events after it is starts its modal tracking loop. After the interaction
2652 is done, this routine is used to fix things up. When a mouse down started us tracking in
2653 the widget, we post a fake mouse up to balance the mouse down we started with. When a
2654 key down started us tracking in the widget, we post a fake key up to balance things out.
2655 In addition, we post a fake mouseMoved to get the cursor in sync with whatever we happen to
2656 be over after the tracking is done.
2658 void KWQKHTMLPart::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent)
2660 KWQ_BLOCK_EXCEPTIONS;
2662 _sendingEventToSubview = false;
2663 int eventType = [initiatingEvent type];
2664 ASSERT(eventType == NSLeftMouseDown || eventType == NSKeyDown);
2665 NSEvent *fakeEvent = nil;
2666 if (eventType == NSLeftMouseDown) {
2667 fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
2668 location:[initiatingEvent locationInWindow]
2669 modifierFlags:[initiatingEvent modifierFlags]
2670 timestamp:[initiatingEvent timestamp]
2671 windowNumber:[initiatingEvent windowNumber]
2672 context:[initiatingEvent context]
2673 eventNumber:[initiatingEvent eventNumber]
2674 clickCount:[initiatingEvent clickCount]
2675 pressure:[initiatingEvent pressure]];
2679 else { // eventType == NSKeyDown
2680 fakeEvent = [NSEvent keyEventWithType:NSKeyUp
2681 location:[initiatingEvent locationInWindow]
2682 modifierFlags:[initiatingEvent modifierFlags]
2683 timestamp:[initiatingEvent timestamp]
2684 windowNumber:[initiatingEvent windowNumber]
2685 context:[initiatingEvent context]
2686 characters:[initiatingEvent characters]
2687 charactersIgnoringModifiers:[initiatingEvent charactersIgnoringModifiers]
2688 isARepeat:[initiatingEvent isARepeat]
2689 keyCode:[initiatingEvent keyCode]];
2690 keyEvent(fakeEvent);
2692 // FIXME: We should really get the current modifierFlags here, but there's no way to poll
2693 // them in Cocoa, and because the event stream was stolen by the Carbon menu code we have
2694 // no up-to-date cache of them anywhere.
2695 fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
2696 location:[[_bridge window] convertScreenToBase:[NSEvent mouseLocation]]
2697 modifierFlags:[initiatingEvent modifierFlags]
2698 timestamp:[initiatingEvent timestamp]
2699 windowNumber:[initiatingEvent windowNumber]
2700 context:[initiatingEvent context]
2704 mouseMoved(fakeEvent);
2706 KWQ_UNBLOCK_EXCEPTIONS;
2709 void KWQKHTMLPart::mouseMoved(NSEvent *event)
2711 KHTMLView *v = d->m_view;
2712 // Reject a mouse moved if the button is down - screws up tracking during autoscroll
2713 // These happen because WebKit sometimes has to fake up moved events.
2714 if (!v || d->m_bMousePressed) {
2718 KWQ_BLOCK_EXCEPTIONS;
2720 NSEvent *oldCurrentEvent = _currentEvent;
2721 _currentEvent = KWQRetain(event);
2723 QMouseEvent kEvent(QEvent::MouseMove, event);
2724 v->viewportMouseMoveEvent(&kEvent);
2726 ASSERT(_currentEvent == event);
2728 _currentEvent = oldCurrentEvent;
2730 KWQ_UNBLOCK_EXCEPTIONS;
2733 // Called as we walk up the element chain for nodes with CSS property -khtml-user-drag == auto
2734 bool KWQKHTMLPart::shouldDragAutoNode(DOM::NodeImpl* node, int x, int y) const
2736 // We assume that WebKit only cares about dragging things that can be leaf nodes (text, images, urls).
2737 // This saves a bunch of expensive calls (creating WC and WK element dicts) as we walk farther up
2738 // the node hierarchy, and we also don't have to cook up a way to ask WK about non-leaf nodes
2739 // (since right now WK just hit-tests using a cached lastMouseDown).
2740 if (!node->hasChildNodes() && d->m_view) {
2741 int windowX, windowY;
2742 d->m_view->contentsToViewport(x, y, windowX, windowY);
2743 NSPoint eventLoc = {windowX, windowY};
2744 return [_bridge mayStartDragAtEventLocation:eventLoc];
2750 bool KWQKHTMLPart::sendContextMenuEvent(NSEvent *event)
2752 DocumentImpl *doc = d->m_doc;
2753 KHTMLView *v = d->m_view;
2758 KWQ_BLOCK_EXCEPTIONS;
2760 NSEvent *oldCurrentEvent = _currentEvent;
2761 _currentEvent = KWQRetain(event);
2763 QMouseEvent qev(QEvent::MouseButtonPress, event);
2766 v->viewportToContents(qev.x(), qev.y(), xm, ym);
2768 NodeImpl::MouseEvent mev(qev.stateAfter(), NodeImpl::MousePress);
2769 doc->prepareMouseEvent(false, xm, ym, &mev);
2771 bool swallowEvent = v->dispatchMouseEvent(EventImpl::CONTEXTMENU_EVENT,
2772 mev.innerNode.handle(), true, 0, &qev, true, NodeImpl::MousePress);
2773 if (!swallowEvent && ([_bridge isEditable] || mev.innerNode.handle()->isContentEditable()) && !isPointInsideSelection(xm, ym)) {
2774 selectClosestWordFromMouseEvent(&qev, mev.innerNode, xm, ym);
2777 ASSERT(_currentEvent == event);
2779 _currentEvent = oldCurrentEvent;
2781 return swallowEvent;
2783 KWQ_UNBLOCK_EXCEPTIONS;
2788 struct ListItemInfo {
2793 NSFileWrapper *KWQKHTMLPart::fileWrapperForElement(ElementImpl *e)
2795 KWQ_BLOCK_EXCEPTIONS;
2797 NSFileWrapper *wrapper = nil;
2799 AtomicString attr = e->getAttribute(ATTR_SRC);
2800 if (!attr.isEmpty()) {
2801 NSURL *URL = completeURL(attr.string()).getNSURL();
2802 wrapper = [_bridge fileWrapperForURL:URL];
2805 RenderImage *renderer = static_cast<RenderImage *>(e->renderer());
2806 NSImage *image = renderer->pixmap().image();
2807 NSData *tiffData = [image TIFFRepresentationUsingCompression:NSTIFFCompressionLZW factor:0.0];
2808 wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:tiffData];
2809 [wrapper setPreferredFilename:@"image.tiff"];
2810 [wrapper autorelease];
2815 KWQ_UNBLOCK_EXCEPTIONS;
2820 static ElementImpl *listParent(ElementImpl *item)
2822 // Ick! Avoid use of item->id() which confuses ObjC++.
2823 unsigned short _id = Node(item).elementId();
2825 while (_id != ID_UL && _id != ID_OL) {
2826 item = static_cast<ElementImpl *>(item->parentNode());
2829 _id = Node(item).elementId();
2834 static NodeImpl* isTextFirstInListItem(NodeImpl *e)
2836 if (Node(e).nodeType() != Node::TEXT_NODE)
2838 NodeImpl* par = e->parentNode();
2840 if (par->firstChild() != e)
2842 if (Node(par).elementId() == ID_LI)
2845 par = par->parentNode();
2850 // FIXME: This collosal function needs to be refactored into maintainable smaller bits.
2852 #define BULLET_CHAR 0x2022
2853 #define SQUARE_CHAR 0x25AA
2854 #define CIRCLE_CHAR 0x25E6
2856 NSAttributedString *KWQKHTMLPart::attributedString(NodeImpl *_start, int startOffset, NodeImpl *endNode, int endOffset)
2858 KWQ_BLOCK_EXCEPTIONS;
2860 NodeImpl * _startNode = _start;
2862 if (_startNode == nil) {
2866 NSMutableAttributedString *result = [[[NSMutableAttributedString alloc] init] autorelease];
2868 bool hasNewLine = true;
2869 bool addedSpace = true;
2870 NSAttributedString *pendingStyledSpace = nil;
2871 bool hasParagraphBreak = true;
2872 const ElementImpl *linkStartNode = 0;
2873 unsigned linkStartLocation = 0;
2874 QPtrList<ElementImpl> listItems;
2875 QValueList<ListItemInfo> listItemLocations;
2876 float maxMarkerWidth = 0;
2878 Node n = _startNode;
2880 // If the first item is the entire text of a list item, use the list item node as the start of the
2881 // selection, not the text node. The user's intent was probably to select the list.
2882 if (n.nodeType() == Node::TEXT_NODE && startOffset == 0) {
2883 NodeImpl *startListNode = isTextFirstInListItem(_startNode);
2885 _startNode = startListNode;
2890 while (!n.isNull()) {
2891 RenderObject *renderer = n.handle()->renderer();
2893 RenderStyle *style = renderer->style();
2894 NSFont *font = style->font().getNSFont();
2895 bool needSpace = pendingStyledSpace != nil;
2896 if (n.nodeType() == Node::TEXT_NODE) {
2900 [pendingStyledSpace release];
2901 pendingStyledSpace = nil;
2905 QString str = n.nodeValue().string();
2906 int start = (n == _startNode) ? startOffset : -1;
2907 int end = (n == endNode) ? endOffset : -1;
2908 if (renderer->isText()) {
2909 if (renderer->style()->whiteSpace() == PRE) {
2910 if (needSpace && !addedSpace) {
2911 if (text.isEmpty() && linkStartLocation == [result length]) {
2912 ++linkStartLocation;
2914 [result appendAttributedString:pendingStyledSpace];
2916 int runStart = (start == -1) ? 0 : start;
2917 int runEnd = (end == -1) ? str.length() : end;
2918 text += str.mid(runStart, runEnd-runStart);
2919 [pendingStyledSpace release];
2920 pendingStyledSpace = nil;
2921 addedSpace = str[runEnd-1].direction() == QChar::DirWS;
2924 RenderText* textObj = static_cast<RenderText*>(renderer);
2925 if (!textObj->firstTextBox() && str.length() > 0 && !addedSpace) {
2926 // We have no runs, but we do have a length. This means we must be
2927 // whitespace that collapsed away at the end of a line.
2933 for (InlineTextBox* box = textObj->firstTextBox(); box; box = box->nextTextBox()) {
2934 int runStart = (start == -1) ? box->m_start : start;
2935 int runEnd = (end == -1) ? box->m_start + box->m_len : end;
2936 runEnd = kMin(runEnd, box->m_start + box->m_len);
2937 if (runStart >= box->m_start &&
2938 runStart < box->m_start + box->m_len) {
2939 if (box == textObj->firstTextBox() && box->m_start == runStart && runStart > 0) {
2940 needSpace = true; // collapsed space at the start
2942 if (needSpace && !addedSpace) {
2943 if (pendingStyledSpace != nil) {
2944 if (text.isEmpty() && linkStartLocation == [result length]) {
2945 ++linkStartLocation;
2947 [result appendAttributedString:pendingStyledSpace];
2952 QString runText = str.mid(runStart, runEnd - runStart);
2953 runText.replace('\n', ' ');
2955 int nextRunStart = box->nextTextBox() ? box->nextTextBox()->m_start : str.length(); // collapsed space between runs or at the end
2956 needSpace = nextRunStart > runEnd;
2957 [pendingStyledSpace release];
2958 pendingStyledSpace = nil;
2959 addedSpace = str[runEnd-1].direction() == QChar::DirWS;
2962 if (end != -1 && runEnd >= end)
2969 text.replace('\\', renderer->backslashAsCurrencySymbol());
2971 if (text.length() > 0 || needSpace) {
2972 NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
2973 [attrs setObject:font forKey:NSFontAttributeName];
2974 if (style && style->color().isValid() && qAlpha(style->color().rgb()) != 0)
2975 [attrs setObject:style->color().getNSColor() forKey:NSForegroundColorAttributeName];
2976 if (style && style->backgroundColor().isValid() && qAlpha(style->backgroundColor().rgb()) != 0)
2977 [attrs setObject:style->backgroundColor().getNSColor() forKey:NSBackgroundColorAttributeName];
2979 if (text.length() > 0) {
2980 hasParagraphBreak = false;
2981 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString() attributes:attrs];
2982 [result appendAttributedString: partialString];
2983 [partialString release];
2987 [pendingStyledSpace release];
2988 pendingStyledSpace = [[NSAttributedString alloc] initWithString:@" " attributes:attrs];
2994 // This is our simple HTML -> ASCII transformation:
2996 unsigned short _id = n.elementId();
2999 // Note the start of the <a> element. We will add the NSLinkAttributeName
3000 // attribute to the attributed string when navigating to the next sibling
3002 linkStartLocation = [result length];
3003 linkStartNode = static_cast<ElementImpl*>(n.handle());
3014 ElementImpl *itemParent = listParent(static_cast<ElementImpl *>(n.handle()));
3020 listItems.append(static_cast<ElementImpl*>(n.handle()));
3022 info.start = [result length];
3024 listItemLocations.append (info);
3027 if (itemParent && renderer->isListItem()) {
3028 RenderListItem *listRenderer = static_cast<RenderListItem*>(renderer);
3030 maxMarkerWidth = MAX([font pointSize], maxMarkerWidth);
3031 switch(listRenderer->style()->listStyleType()) {
3033 listText += ((QChar)BULLET_CHAR);
3036 listText += ((QChar)CIRCLE_CHAR);
3039 listText += ((QChar)SQUARE_CHAR);
3044 QString marker = listRenderer->markerStringValue();
3046 // Use AppKit metrics. Will be rendered by AppKit.
3047 float markerWidth = [font widthOfString: marker.getNSString()];
3048 maxMarkerWidth = MAX(markerWidth, maxMarkerWidth);
3054 NSMutableDictionary *attrs;
3056 attrs = [[NSMutableDictionary alloc] init];
3057 [attrs setObject:font forKey:NSFontAttributeName];
3058 if (style && style->color().isValid())
3059 [attrs setObject:style->color().getNSColor() forKey:NSForegroundColorAttributeName];
3060 if (style && style->backgroundColor().isValid())
3061 [attrs setObject:style->backgroundColor().getNSColor() forKey:NSBackgroundColorAttributeName];
3063 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:listText.getNSString() attributes:attrs];
3065 [result appendAttributedString: partialString];
3066 [partialString release];
3101 if (!hasParagraphBreak) {
3103 hasParagraphBreak = true;
3109 if (pendingStyledSpace != nil) {
3110 if (linkStartLocation == [result length]) {
3111 ++linkStartLocation;
3113 [result appendAttributedString:pendingStyledSpace];
3114 [pendingStyledSpace release];
3115 pendingStyledSpace = nil;
3117 NSFileWrapper *fileWrapper = fileWrapperForElement(static_cast<ElementImpl *>(n.handle()));
3118 NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper];
3119 NSAttributedString *iString = [NSAttributedString attributedStringWithAttachment:attachment];
3120 [result appendAttributedString: iString];
3121 [attachment release];
3124 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()];
3125 [result appendAttributedString: partialString];
3126 [partialString release];
3133 Node next = n.firstChild();
3135 next = n.nextSibling();
3138 while (next.isNull() && !n.parentNode().isNull()) {
3143 next = n.nextSibling();
3145 unsigned short _id = n.elementId();
3148 // End of a <a> element. Create an attributed string NSLinkAttributeName attribute
3149 // for the range of the link. Note that we create the attributed string from the DOM, which
3150 // will have corrected any illegally nested <a> elements.
3151 if (linkStartNode && n.handle() == linkStartNode){
3152 DOMString href = parseURL(linkStartNode->getAttribute(ATTR_HREF));
3153 KURL kURL = KWQ(linkStartNode->getDocument()->part())->completeURL(href.string());
3155 NSURL *URL = kURL.getNSURL();
3156 [result addAttribute:NSLinkAttributeName value:URL range:NSMakeRange(linkStartLocation, [result length]-linkStartLocation)];
3170 int i, count = listItems.count();
3171 for (i = 0; i < count; i++){
3172 if (listItems.at(i) == n.handle()){
3173 listItemLocations[i].end = [result length];
3206 // An extra newline is needed at the start, not the end, of these types of tags,
3207 // so don't add another here.
3212 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()];
3213 [result appendAttributedString:partialString];
3214 [partialString release];
3220 [pendingStyledSpace release];
3222 // Apply paragraph styles from outside in. This ensures that nested lists correctly
3223 // override their parent's paragraph style.
3225 unsigned i, count = listItems.count();
3229 #ifdef POSITION_LIST
3230 NodeImpl *containingBlock;
3231 int containingBlockX, containingBlockY;
3233 // Determine the position of the outermost containing block. All paragraph
3234 // styles and tabs should be relative to this position. So, the horizontal position of
3235 // each item in the list (in the resulting attributed string) will be relative to position
3236 // of the outermost containing block.
3238 containingBlock = _startNode;
3239 while (containingBlock->renderer()->isInline()){
3240 containingBlock = containingBlock->parentNode();
3242 containingBlock->renderer()->absolutePosition(containingBlockX, containingBlockY);
3246 for (i = 0; i < count; i++){
3247 e = listItems.at(i);
3248 info = listItemLocations[i];
3250 if (info.end < info.start)
3251 info.end = [result length];
3253 RenderObject *r = e->renderer();
3254 RenderStyle *style = r->style();
3257 NSFont *font = style->font().getNSFont();
3258 float pointSize = [font pointSize];
3260 #ifdef POSITION_LIST
3262 r->absolutePosition(rx, ry);
3263 rx -= containingBlockX;
3265 // Ensure that the text is indented at least enough to allow for the markers.
3266 rx = MAX(rx, (int)maxMarkerWidth);
3268 rx = (int)MAX(maxMarkerWidth, pointSize);
3271 // The bullet text will be right aligned at the first tab marker, followed
3272 // by a space, followed by the list item text. The space is arbitrarily
3273 // picked as pointSize*2/3. The space on the first line of the text item
3274 // is established by a left aligned tab, on subsequent lines it's established
3275 // by the head indent.
3276 NSMutableParagraphStyle *mps = [[NSMutableParagraphStyle alloc] init];
3277 [mps setFirstLineHeadIndent: 0];
3278 [mps setHeadIndent: rx];
3279 [mps setTabStops:[NSArray arrayWithObjects:
3280 [[[NSTextTab alloc] initWithType:NSRightTabStopType location:rx-(pointSize*2/3)] autorelease],
3281 [[[NSTextTab alloc] initWithType:NSLeftTabStopType location:rx] autorelease],
3283 [result addAttribute:NSParagraphStyleAttributeName value:mps range:NSMakeRange(info.start, info.end-info.start)];
3290 KWQ_UNBLOCK_EXCEPTIONS;
3295 QRect KWQKHTMLPart::selectionRect() const
3301 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
3306 return root->selectionRect();
3309 // returns NSRect because going through QRect would truncate any floats
3310 NSRect KWQKHTMLPart::visibleSelectionRect() const
3315 NSView *documentView = d->m_view->getDocumentView();
3316 if (!documentView) {
3319 return NSIntersectionRect(selectionRect(), [documentView visibleRect]);
3322 void KWQKHTMLPart::centerSelectionInVisibleArea() const
3324 switch (selection().state()) {
3325 case Selection::NONE:
3327 case Selection::CARET: {
3329 // passing true forces centering even if selection is already exposed
3330 view()->ensureRectVisibleCentered(selection().caretRect(), true);
3333 case Selection::RANGE:
3335 // passing true forces centering even if selection is already exposed
3336 view()->ensureRectVisibleCentered(selectionRect(), true);
3341 NSImage *KWQKHTMLPart::imageFromRect(NSRect rect) const
3343 NSView *view = d->m_view->getDocumentView();
3348 NSRect bounds = [view bounds];
3349 NSImage *resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease];
3351 KWQ_BLOCK_EXCEPTIONS;
3353 if (rect.size.width != 0 && rect.size.height != 0) {
3354 [resultImage setFlipped:YES];
3355 [resultImage lockFocus];
3357 [NSGraphicsContext saveGraphicsState];
3358 NSPoint translation = { -(NSMinX(rect) - NSMinX(bounds)), -(NSMinY(rect) - NSMinY(bounds)) };
3359 CGContextTranslateCTM((CGContext *)[[NSGraphicsContext currentContext] graphicsPort], translation.x, translation.y);
3361 // We change the coord system at the CG level, out from under the AK focus machinery, because it doesn't
3362 // work to change the coord system of a focused view. However, WebImageRenderer uses the difference
3363 // between the focused view's coord system and the window's coord system to adjust the pattern phase, and
3364 // that calc ignores our translation. So we must tell it about this extra phase offset.
3366 // Window is not flipped, we are, so y coord must be inverted when describing phase, which is a
3367 // window level notion.
3368 translation.y = -translation.y;
3369 [[WebCoreGraphicsBridge sharedBridge] setAdditionalPatternPhase:translation];
3371 [view drawRect:rect];
3373 [[WebCoreGraphicsBridge sharedBridge] setAdditionalPatternPhase:NSZeroPoint];
3374 [NSGraphicsContext restoreGraphicsState];
3376 [resultImage unlockFocus];
3377 [resultImage setFlipped:NO];
3380 KWQ_UNBLOCK_EXCEPTIONS;
3385 NSImage *KWQKHTMLPart::selectionImage() const
3387 _drawSelectionOnly = true; // invoke special drawing mode
3388 NSImage *result = imageFromRect(visibleSelectionRect());
3389 _drawSelectionOnly = false;
3393 NSImage *KWQKHTMLPart::snapshotDragImage(DOM::Node node, NSRect *imageRect, NSRect *elementRect) const
3395 RenderObject *renderer = node.handle()->renderer();
3400 renderer->updateDragState(true); // mark dragged nodes (so they pick up the right CSS)
3401 d->m_doc->updateLayout(); // forces style recalc - needed since changing the drag state might
3402 // imply new styles, plus JS could have changed other things
3404 NSRect paintingRect = renderer->paintingRootRect(topLevelRect);
3406 _elementToDraw = node; // invoke special sub-tree drawing mode
3407 NSImage *result = imageFromRect(paintingRect);
3408 renderer->updateDragState(false);
3412 *elementRect = topLevelRect;
3415 *imageRect = paintingRect;
3420 RenderStyle *KWQKHTMLPart::styleForSelectionStart(NodeImpl *&nodeToRemove) const
3426 if (d->m_selection.isNone())
3429 Position pos = VisiblePosition(d->m_selection.start(), UPSTREAM).deepEquivalent();
3430 if (!pos.inRenderedContent())
3432 NodeImpl *node = pos.node();
3436 if (!d->m_typingStyle)
3437 return node->renderer()->style();
3439 int exceptionCode = 0;
3440 ElementImpl *styleElement = xmlDocImpl()->createHTMLElement("span", exceptionCode);
3441 ASSERT(exceptionCode == 0);
3443 styleElement->setAttribute(ATTR_STYLE, d->m_typingStyle->cssText().implementation(), exceptionCode);
3444 ASSERT(exceptionCode == 0);
3446 TextImpl *text = xmlDocImpl()->createEditingTextNode("");
3447 styleElement->appendChild(text, exceptionCode);
3448 ASSERT(exceptionCode == 0);
3450 node->parentNode()->appendChild(styleElement, exceptionCode);
3451 ASSERT(exceptionCode == 0);
3453 nodeToRemove = styleElement;
3454 return styleElement->renderer()->style();
3457 NSFont *KWQKHTMLPart::fontForSelection(bool *hasMultipleFonts) const
3459 if (hasMultipleFonts)
3460 *hasMultipleFonts = false;
3462 if (!d->m_selection.isRange()) {
3463 NodeImpl *nodeToRemove;
3464 RenderStyle *style = styleForSelectionStart(nodeToRemove); // sets nodeToRemove
3468 result = style->font().getNSFont();
3472 nodeToRemove->remove(exceptionCode);
3473 ASSERT(exceptionCode == 0);
3481 Range r = d->m_selection.toRange();
3482 RangeImpl *range = r.handle();
3483 NodeImpl *pastEnd = range->pastEndNode();
3484 for (NodeImpl *n = range->startNode(); n != pastEnd; n = n->traverseNextNode()) {
3485 RenderObject *renderer = n->renderer();
3488 // FIXME: Are there any node types that have renderers, but that we should be skipping?
3489 NSFont *f = renderer->style()->font().getNSFont();
3492 if (!hasMultipleFonts)
3494 } else if (font != f) {
3495 *hasMultipleFonts = true;
3503 NSDictionary *KWQKHTMLPart::fontAttributesForSelectionStart() const
3505 NodeImpl *nodeToRemove;
3506 RenderStyle *style = styleForSelectionStart(nodeToRemove);
3510 NSMutableDictionary *result = [NSMutableDictionary dictionary];
3512 if (style->backgroundColor().isValid() && style->backgroundColor().alpha() != 0)
3513 [result setObject:style->backgroundColor().getNSColor() forKey:NSBackgroundColorAttributeName];
3515 if (style->font().getNSFont())
3516 [result setObject:style->font().getNSFont() forKey:NSFontAttributeName];
3518 if (style->color().isValid() && style->color() != black)
3519 [result setObject:style->color().getNSColor() forKey:NSForegroundColorAttributeName];
3521 ShadowData *shadow = style->textShadow();
3523 NSShadow *s = [[NSShadow alloc] init];
3524 [s setShadowOffset:NSMakeSize(shadow->x, shadow->y)];
3525 [s setShadowBlurRadius:shadow->blur];
3526 [s setShadowColor:shadow->color.getNSColor()];
3527 [result setObject:s forKey:NSShadowAttributeName];
3530 int decoration = style->textDecorationsInEffect();
3531 if (decoration & khtml::LINE_THROUGH)
3532 [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName];
3534 int superscriptInt = 0;
3535 switch (style->verticalAlign()) {
3536 case khtml::BASELINE:
3538 case khtml::BASELINE_MIDDLE:
3541 case khtml::TEXT_BOTTOM:
3542 case khtml::TEXT_TOP:
3546 superscriptInt = -1;
3553 [result setObject:[NSNumber numberWithInt:superscriptInt] forKey:NSSuperscriptAttributeName];
3555 if (decoration & khtml::UNDERLINE)
3556 [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
3559 int exceptionCode = 0;
3560 nodeToRemove->remove(exceptionCode);
3561 ASSERT(exceptionCode == 0);
3567 KWQWindowWidget *KWQKHTMLPart::topLevelWidget()
3569 return _windowWidget;
3572 void KWQKHTMLPart::tokenizerProcessedData()
3575 [_bridge tokenizerProcessedData];
3578 int KWQKHTMLPart::selectionStartOffset() const
3580 return d->m_selection.start().offset();
3583 int KWQKHTMLPart::selectionEndOffset() const
3585 return d->m_selection.end().offset();
3588 NodeImpl *KWQKHTMLPart::selectionStart() const
3590 return d->m_selection.start().node();
3593 NodeImpl *KWQKHTMLPart::selectionEnd() const
3595 return d->m_selection.end().node();
3598 void KWQKHTMLPart::setBridge(WebCoreBridge *p)
3601 delete _windowWidget;
3604 _windowWidget = new KWQWindowWidget(_bridge);
3607 QString KWQKHTMLPart::overrideMediaType() const
3609 NSString *overrideType = [_bridge overrideMediaType];
3611 return QString::fromNSString(overrideType);
3615 void KWQKHTMLPart::setMediaType(const QString &type)
3618 d->m_view->setMediaType(type);
3622 void KWQKHTMLPart::setSelectionFromNone()
3624 // Put the caret someplace if the selection is empty and the part is editable.
3625 // This has the effect of flashing the caret in a contentEditable view automatically
3626 // without requiring the programmer to set a selection explicitly.
3627 DocumentImpl *doc = xmlDocImpl();
3628 if (doc && selection().isNone() && isContentEditable()) {
3629 NodeImpl *node = doc->documentElement();
3631 // Look for a block flow, but skip over the HTML element, since we really
3632 // want to get at least as far as the the BODY element in a document.
3633 if (node->isBlockFlow() && node->identifier() != ID_HTML)
3635 node = node->traverseNextNode();
3638 setSelection(Position(node, 0));
3642 void KWQKHTMLPart::setDisplaysWithFocusAttributes(bool flag)
3644 if (d->m_isFocused == flag)
3646 d->m_isFocused = flag;
3648 // This method does the job of updating the view based on whether the view is "active".
3649 // This involves three kinds of drawing updates:
3651 // 1. The background color used to draw behind selected content (active | inactive color)
3653 d->m_view->updateContents(QRect(visibleSelectionRect()));
3655 // 2. Caret blinking (blinks | does not blink)
3657 setSelectionFromNone();
3658 setCaretVisible(d->m_isFocused);
3660 // 3. The drawing of a focus ring around links in web pages.
3661 DocumentImpl *doc = xmlDocImpl();
3663 NodeImpl *node = doc->focusNode();
3664 if (node && node->renderer())
3665 node->renderer()->repaint();
3668 if (doc && doc->body()) {
3670 // Send onfocus event.
3671 doc->body()->dispatchWindowEvent(EventImpl::FOCUS_EVENT, false, false);
3674 // Send onblur event.
3675 doc->body()->dispatchWindowEvent(EventImpl::BLUR_EVENT, false, false);
3680 bool KWQKHTMLPart::displaysWithFocusAttributes() const
3682 return d->m_isFocused;
3685 QChar KWQKHTMLPart::backslashAsCurrencySymbol() const
3687 DocumentImpl *doc = xmlDocImpl();
3691 Decoder *decoder = doc->decoder();
3695 const QTextCodec *codec = decoder->codec();
3699 return codec->backslashAsCurrencySymbol();
3702 NSColor *KWQKHTMLPart::bodyBackgroundColor() const
3704 if (xmlDocImpl() && xmlDocImpl()->body() && xmlDocImpl()->body()->renderer()) {
3705 QColor bgColor = xmlDocImpl()->body()->renderer()->style()->backgroundColor();
3706 if (bgColor.isValid()) {
3707 return bgColor.getNSColor();
3713 WebCoreKeyboardUIMode KWQKHTMLPart::keyboardUIMode() const
3715 KWQ_BLOCK_EXCEPTIONS;
3716 return [_bridge keyboardUIMode];
3717 KWQ_UNBLOCK_EXCEPTIONS;
3719 return WebCoreKeyboardAccessDefault;
3722 void KWQKHTMLPart::setName(const QString &name)
3726 KWQKHTMLPart *parent = KWQ(parentPart());
3728 if (parent && (name.isEmpty() || parent->frameExists(name))) {
3729 n = parent->requestFrameName();
3732 KHTMLPart::setName(n);
3734 KWQ_BLOCK_EXCEPTIONS;
3735 [_bridge didSetName:n.getNSString()];
3736 KWQ_UNBLOCK_EXCEPTIONS;
3739 void KWQKHTMLPart::didTellBridgeAboutLoad(const QString &urlString)
3742 urlsBridgeKnowsAbout.insert(urlString, &dummy);
3745 bool KWQKHTMLPart::haveToldBridgeAboutLoad(const QString &urlString)
3747 return urlsBridgeKnowsAbout.find(urlString) != 0;
3750 void KWQKHTMLPart::clear()
3752 urlsBridgeKnowsAbout.clear();
3753 setMarkedTextRange(0);
3757 void KHTMLPart::print()
3759 [KWQ(this)->_bridge print];
3762 KJS::Bindings::Instance *KWQKHTMLPart::getAppletInstanceForView (NSView *aView)
3766 // Get a pointer to the actual Java applet instance.
3767 if ([_bridge respondsToSelector:@selector(getAppletInView:)])
3768 applet = [_bridge getAppletInView:aView];
3770 applet = [_bridge pollForAppletInView:aView];
3773 // Wrap the Java instance in a language neutral binding and hand
3774 // off ownership to the APPLET element.
3775 KJS::Bindings::Instance *instance = KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::JavaLanguage, applet);
3777 KJS::Bindings::RootObject *root = KJS::Bindings::RootObject::findRootObjectForNativeHandleFunction ()(aView);
3778 instance->setExecutionContext (root);
3786 @interface NSObject (WebPlugIn)
3787 - (id)objectForWebScript;
3788 - (void *)pluginScriptableObject;
3791 KJS::Bindings::Instance *KWQKHTMLPart::getEmbedInstanceForView (NSView *aView)
3793 if ([aView respondsToSelector:@selector(objectForWebScript)]){
3794 id object = [aView objectForWebScript];
3796 return KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::ObjectiveCLanguage, object);
3798 else if ([aView respondsToSelector:@selector(pluginScriptableObject)]){
3799 void *object = [aView pluginScriptableObject];
3801 return KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::CLanguage, object);
3806 void KWQKHTMLPart::addPluginRootObject(const KJS::Bindings::RootObject *root)
3808 rootObjects.append (root);
3811 void KWQKHTMLPart::cleanupPluginRootObjects()
3813 KJS::Bindings::RootObject *root;
3814 while ((root = rootObjects.getLast())) {
3815 root->removeAllNativeReferences ();
3816 rootObjects.removeLast();
3820 static NSString *nbspSpaceString = 0;
3821 static NSString *nbspNBSPSpaceString = 0;
3823 DocumentFragmentImpl *KWQKHTMLPart::documentFragmentWithText(NSString *text)
3828 DocumentFragmentImpl *fragment = xmlDocImpl()->createDocumentFragment();
3829 NSMutableString *string = [text mutableCopy];
3831 // Replace tabs with four plain spaces.
3832 // These spaces will get converted along with the other existing spaces below.
3833 [string replaceOccurrencesOfString:@"\t" withString:@" " options:0 range:NSMakeRange(0, [string length])];
3835 if (!nbspSpaceString) {
3836 unichar nbspSpace[] = { 0xa0, ' ' };
3837 nbspSpaceString = [[NSString alloc] initWithCharacters:nbspSpace length:2];
3840 if (!nbspNBSPSpaceString) {
3841 unichar nbspNBSPSpace[] = { 0xa0, 0xa0, ' ' };
3842 nbspNBSPSpaceString = [[NSString alloc] initWithCharacters:nbspNBSPSpace length:3];
3845 unsigned stringLength = [string length];
3846 NSRange range = NSMakeRange(0, stringLength);
3848 // FIXME: This only converts plain old spaces, and does not
3849 // deal with more exotic whitespace. Note that we want to
3850 // leave newlines and returns alone at this point anyway,
3851 // since those are handled specially later.
3852 NSRange replaceRange = [string rangeOfString:@" " options:NSLiteralSearch range:range];
3853 if (replaceRange.location == NSNotFound)
3856 // Found two adjoining spaces.
3857 // Now, lookahead to see if these two spaces are followed by:
3858 // 1. another space and then a non-space
3859 // 2. another space and then the end of the string
3860 // If either 1 or 2 is true, replace the three spaces found with nbsp+nbsp+space,
3861 // otherwise, replace the first two spaces with nbsp+space.
3862 unsigned lookahead = replaceRange.location + 2;
3863 if ((lookahead + 2 < stringLength && [string characterAtIndex:lookahead] == ' ' && [string characterAtIndex:lookahead + 1] != ' ') ||
3864 (lookahead + 1 == stringLength && [string characterAtIndex:lookahead] == ' ')) {
3865 replaceRange.length = 3;
3866 [string replaceCharactersInRange:replaceRange withString:nbspNBSPSpaceString];
3869 [string replaceCharactersInRange:replaceRange withString:nbspSpaceString];
3871 range.location = replaceRange.location + 2;
3872 range.length = stringLength - range.location;
3875 // Handle line endings, replacing them with BR elements.
3876 [string replaceOccurrencesOfString:@"\r\n" withString:@"\n" options:0 range:NSMakeRange(0, [string length])];
3877 [string replaceOccurrencesOfString:@"\r" withString:@"\n" options:0 range:NSMakeRange(0, [string length])];
3878 NSArray *array = [string componentsSeparatedByString:@"\n"];
3880 int count = [array count];
3882 for (i = 0; i < count; i++) {
3883 int exceptionCode = 0;
3885 ElementImpl *breakNode = xmlDocImpl()->createHTMLElement("br", exceptionCode);
3886 ASSERT(exceptionCode == 0);
3887 fragment->appendChild(breakNode, exceptionCode);
3888 ASSERT(exceptionCode == 0);
3890 NSString *component = (NSString *)[array objectAtIndex:i];
3891 if ([component length] > 0) {
3892 NodeImpl *textNode = xmlDocImpl()->createTextNode(component);
3893 fragment->appendChild(textNode, exceptionCode);
3901 void KWQKHTMLPart::registerCommandForUndo(const EditCommandPtr &cmd)
3904 KWQEditCommand *kwq = [KWQEditCommand commandWithEditCommand:cmd.get()];
3905 [[_bridge undoManager] registerUndoWithTarget:_bridge selector:@selector(undoEditing:) object:kwq];
3906 _haveUndoRedoOperations = YES;
3909 void KWQKHTMLPart::registerCommandForRedo(const EditCommandPtr &cmd)
3912 KWQEditCommand *kwq = [KWQEditCommand commandWithEditCommand:cmd.get()];
3913 [[_bridge undoManager] registerUndoWithTarget:_bridge selector:@selector(redoEditing:) object:kwq];
3914 _haveUndoRedoOperations = YES;
3917 void KWQKHTMLPart::clearUndoRedoOperations()
3919 if (_haveUndoRedoOperations) {
3920 [[_bridge undoManager] removeAllActionsWithTarget:_bridge];
3921 _haveUndoRedoOperations = NO;
3925 void KWQKHTMLPart::issueUndoCommand()
3928 [[_bridge undoManager] undo];
3931 void KWQKHTMLPart::issueRedoCommand()
3934 [[_bridge undoManager] redo];
3937 void KWQKHTMLPart::issueCutCommand()
3939 [_bridge issueCutCommand];
3942 void KWQKHTMLPart::issueCopyCommand()
3944 [_bridge issueCopyCommand];
3947 void KWQKHTMLPart::issuePasteCommand()
3949 [_bridge issuePasteCommand];
3952 bool KHTMLPart::canUndo() const
3954 return [[KWQ(this)->_bridge undoManager] canUndo];
3957 bool KHTMLPart::canRedo() const
3959 return [[KWQ(this)->_bridge undoManager] canRedo];
3962 bool KHTMLPart::canPaste() const
3964 return [KWQ(this)->_bridge canPaste];
3967 void KWQKHTMLPart::markMisspellingsInAdjacentWords(const VisiblePosition &p)
3969 if (![_bridge isContinuousSpellCheckingEnabled])
3971 markMisspellings(Selection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)));
3974 void KWQKHTMLPart::markMisspellings(const Selection &selection)
3976 // This function is called with a selection already expanded to word boundaries.
3977 // Might be nice to assert that here.
3979 if (![_bridge isContinuousSpellCheckingEnabled])
3982 Range searchRange(selection.toRange());
3983 if (searchRange.isNull() || searchRange.isDetached())
3986 // If we're not in an editable node, bail.
3987 NodeImpl *editableNodeImpl = searchRange.startContainer().handle();
3988 if (!editableNodeImpl->isContentEditable())
3991 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
3992 WordAwareIterator it(searchRange);
3994 while (!it.atEnd()) { // we may be starting at the end of the doc, and already by atEnd
3995 const QChar *chars = it.characters();
3996 long len = it.length();
3997 if (len > 1 || !chars[0].isSpace()) {
3998 NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:(unichar *)chars length:len freeWhenDone:NO];
4000 // Loop over the chunk to find each misspelling in it.
4001 while (startIndex < len) {
4002 NSRange misspelling = [checker checkSpellingOfString:chunk startingAt:startIndex language:nil wrap:NO inSpellDocumentWithTag:[_bridge spellCheckerDocumentTag] wordCount:NULL];
4003 if (misspelling.length == 0) {
4007 // Build up result range and string. Note the misspelling may span many text nodes,
4008 // but the CharIterator insulates us from this complexity
4009 Range misspellingRange(xmlDocImpl());
4010 CharacterIterator chars(it.range());
4011 chars.advance(misspelling.location);
4012 misspellingRange.setStart(chars.range().startContainer(), chars.range().startOffset());
4013 chars.advance(misspelling.length);
4014 misspellingRange.setEnd(chars.range().startContainer(), chars.range().startOffset());
4015 // Mark misspelling in document.
4016 xmlDocImpl()->addMarker(misspellingRange, DocumentMarker::Spelling);
4017 startIndex = misspelling.location + misspelling.length;
4027 void KWQKHTMLPart::respondToChangedSelection(const Selection &oldSelection, bool closeTyping)
4030 if ([_bridge isContinuousSpellCheckingEnabled]) {
4031 VisiblePosition oldStart(oldSelection.start());
4032 Selection oldAdjacentWords(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary));
4034 VisiblePosition newStart(selection().start());
4035 Selection newAdjacentWords(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary));
4037 if (oldAdjacentWords != newAdjacentWords) {
4038 // Mark misspellings in the portion that was previously unmarked because of
4039 // the proximity of the start of the selection. We only spell check words in
4040 // the vicinity of the start of the old selection because the spelling checker
4041 // is not fast enough to do a lot of spelling checking implicitly. This matches
4042 // AppKit. This function is really the only code that knows that rule. The
4043 // markMisspellings function is prepared to handler larger ranges.
4045 // When typing we check spelling elsewhere, so don't redo it here.
4047 markMisspellings(oldAdjacentWords);
4050 // This only erases a marker in the first word of the selection.
4051 // Perhaps peculiar, but it matches AppKit.
4052 xmlDocImpl()->removeMarker(newAdjacentWords.toRange(), DocumentMarker::Spelling);
4055 // When continuous spell checking is off, no markers appear after the selection changes.
4056 xmlDocImpl()->removeAllMarkers();
4060 [_bridge respondToChangedSelection];
4063 void KWQKHTMLPart::respondToChangedContents()
4065 [_bridge respondToChangedContents];
4068 bool KWQKHTMLPart::isContentEditable() const
4070 return KHTMLPart::isContentEditable() || [_bridge isEditable];
4073 bool KWQKHTMLPart::shouldBeginEditing(const Range &range) const
4075 ASSERT(!range.isNull());
4076 return [_bridge shouldBeginEditing:[DOMRange _rangeWithImpl:range.handle()]];
4079 bool KWQKHTMLPart::shouldEndEditing(const Range &range) const
4081 ASSERT(!range.isNull());
4082 return [_bridge shouldEndEditing:[DOMRange _rangeWithImpl:range.handle()]];
4085 DOM::Range KWQKHTMLPart::markedTextRange() const
4087 return m_markedTextRange;
4090 void KWQKHTMLPart::setMarkedTextRange(const DOM::Range &range)
4092 ASSERT(!range.handle() || range.startContainer() == range.endContainer());
4093 ASSERT(!range.handle() || range.collapsed() || range.startContainer().nodeType() == Node::TEXT_NODE);
4095 if (m_markedTextRange.handle() && xmlDocImpl()
4096 && m_markedTextRange.startContainer().handle()->renderer()) {
4097 m_markedTextRange.startContainer().handle()->renderer()->repaint();