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 bool KHTMLView::isKHTMLView() const
203 static void redirectionTimerMonitor(void *context)
205 KWQKHTMLPart *kwq = static_cast<KWQKHTMLPart *>(context);
206 kwq->redirectionTimerStartedOrStopped();
209 KWQKHTMLPart::KWQKHTMLPart()
210 : _started(this, SIGNAL(started(KIO::Job *)))
211 , _completed(this, SIGNAL(completed()))
212 , _completedWithBool(this, SIGNAL(completed(bool)))
213 , _mouseDownView(nil)
214 , _sendingEventToSubview(false)
215 , _mouseDownMayStartDrag(false)
216 , _mouseDownMayStartSelect(false)
217 , _activationEventNumber(0)
218 , _formValuesAboutToBeSubmitted(nil)
219 , _formAboutToBeSubmitted(nil)
220 , _windowWidget(NULL)
221 , _drawSelectionOnly(false)
223 , _windowScriptObject(0)
224 , _windowScriptNPObject(0)
229 // Must init the cache before connecting to any signals
232 // The widget is made outside this class in our case.
233 KHTMLPart::init( 0, DefaultGUI );
235 mutableInstances().prepend(this);
236 d->m_redirectionTimer.setMonitor(redirectionTimerMonitor, this);
239 KWQKHTMLPart::~KWQKHTMLPart()
241 cleanupPluginRootObjects();
243 mutableInstances().remove(this);
248 // these are all basic Foundation classes and our own classes - we
249 // know they will not raise in dealloc, so no need to block
251 KWQRelease(_formValuesAboutToBeSubmitted);
252 KWQRelease(_formAboutToBeSubmitted);
254 KWQRelease(_windowScriptObject);
256 delete _windowWidget;
259 void KWQKHTMLPart::freeClipboard()
261 if (_dragClipboard) {
262 _dragClipboard->setAccessPolicy(KWQClipboard::Numb);
263 _dragClipboard->deref();
268 void KWQKHTMLPart::setSettings (KHTMLSettings *settings)
270 d->m_settings = settings;
273 QString KWQKHTMLPart::generateFrameName()
275 KWQ_BLOCK_EXCEPTIONS;
276 return QString::fromNSString([_bridge generateFrameName]);
277 KWQ_UNBLOCK_EXCEPTIONS;
282 void KWQKHTMLPart::provisionalLoadStarted()
284 // we don't want to wait until we get an actual http response back
285 // to cancel pending redirects, otherwise they might fire before
287 cancelRedirection(true);
290 bool KWQKHTMLPart::openURL(const KURL &url)
292 KWQ_BLOCK_EXCEPTIONS;
294 bool userGesture = true;
296 if (jScript() && jScript()->interpreter()) {
297 KHTMLPart *rootPart = this;
298 while (rootPart->parentPart() != 0)
299 rootPart = rootPart->parentPart();
300 KJS::ScriptInterpreter *interpreter = static_cast<KJS::ScriptInterpreter *>(KJSProxy::proxy(rootPart)->interpreter());
301 userGesture = interpreter->wasRunByUserGesture();
304 // FIXME: The lack of args here to get the reload flag from
305 // indicates a problem in how we use KHTMLPart::processObjectRequest,
306 // where we are opening the URL before the args are set up.
307 [_bridge loadURL:url.getNSURL()
308 referrer:[_bridge referrer]
310 userGesture:userGesture
316 KWQ_UNBLOCK_EXCEPTIONS;
321 void KWQKHTMLPart::openURLRequest(const KURL &url, const URLArgs &args)
323 KWQ_BLOCK_EXCEPTIONS;
325 [_bridge loadURL:url.getNSURL()
326 referrer:[_bridge referrer]
329 target:args.frameName.getNSString()
334 KWQ_UNBLOCK_EXCEPTIONS;
337 void KWQKHTMLPart::didNotOpenURL(const KURL &URL)
339 if (_submittedFormURL == URL) {
340 _submittedFormURL = KURL();
344 // Scans logically forward from "start", including any child frames
345 static HTMLFormElementImpl *scanForForm(NodeImpl *start)
348 for (n = start; n; n = n->traverseNextNode()) {
349 NodeImpl::Id nodeID = idFromNode(n);
350 if (nodeID == ID_FORM) {
351 return static_cast<HTMLFormElementImpl *>(n);
352 } else if (n->isHTMLElement()
353 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()) {
354 return static_cast<HTMLGenericFormElementImpl *>(n)->form();
355 } else if (nodeID == ID_FRAME || nodeID == ID_IFRAME) {
356 NodeImpl *childDoc = static_cast<HTMLFrameElementImpl *>(n)->contentDocument();
357 HTMLFormElementImpl *frameResult = scanForForm(childDoc);
366 // We look for either the form containing the current focus, or for one immediately after it
367 HTMLFormElementImpl *KWQKHTMLPart::currentForm() const
369 // start looking either at the active (first responder) node, or where the selection is
370 NodeImpl *start = activeNode().handle();
372 start = selectionStart();
375 // try walking up the node tree to find a form element
377 for (n = start; n; n = n->parentNode()) {
378 if (idFromNode(n) == ID_FORM) {
379 return static_cast<HTMLFormElementImpl *>(n);
380 } else if (n->isHTMLElement()
381 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()) {
382 return static_cast<HTMLGenericFormElementImpl *>(n)->form();
386 // try walking forward in the node tree to find a form element
387 return start ? scanForForm(start) : 0;
390 // Either get cached regexp or build one that matches any of the labels.
391 // The regexp we build is of the form: (STR1|STR2|STRN)
392 QRegExp *regExpForLabels(NSArray *labels)
394 // All the ObjC calls in this method are simple array and string
395 // calls which we can assume do not raise exceptions
398 // Parallel arrays that we use to cache regExps. In practice the number of expressions
399 // that the app will use is equal to the number of locales is used in searching.
400 static const unsigned int regExpCacheSize = 4;
401 static NSMutableArray *regExpLabels = nil;
402 static QPtrList <QRegExp> regExps;
403 static QRegExp wordRegExp = QRegExp("\\w");
407 regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize];
409 unsigned int cacheHit = [regExpLabels indexOfObject:labels];
410 if (cacheHit != NSNotFound) {
411 result = regExps.at(cacheHit);
413 QString pattern("(");
414 unsigned int numLabels = [labels count];
416 for (i = 0; i < numLabels; i++) {
417 QString label = QString::fromNSString([labels objectAtIndex:i]);
419 bool startsWithWordChar = false;
420 bool endsWithWordChar = false;
421 if (label.length() != 0) {
422 startsWithWordChar = wordRegExp.search(label.at(0)) >= 0;
423 endsWithWordChar = wordRegExp.search(label.at(label.length() - 1)) >= 0;
429 // Search for word boundaries only if label starts/ends with "word characters".
430 // If we always searched for word boundaries, this wouldn't work for languages
432 if (startsWithWordChar) {
433 pattern.append("\\b");
435 pattern.append(label);
436 if (endsWithWordChar) {
437 pattern.append("\\b");
441 result = new QRegExp(pattern, false);
444 // add regexp to the cache, making sure it is at the front for LRU ordering
446 if (cacheHit != NSNotFound) {
447 // remove from old spot
448 [regExpLabels removeObjectAtIndex:cacheHit];
449 regExps.remove(cacheHit);
452 [regExpLabels insertObject:labels atIndex:0];
453 regExps.insert(0, result);
455 if ([regExpLabels count] > regExpCacheSize) {
456 [regExpLabels removeObjectAtIndex:regExpCacheSize];
457 QRegExp *last = regExps.last();
458 regExps.removeLast();
465 NSString *KWQKHTMLPart::searchForLabelsAboveCell(QRegExp *regExp, HTMLTableCellElementImpl *cell)
467 RenderTableCell *cellRenderer = static_cast<RenderTableCell *>(cell->renderer());
468 RenderTableCell *cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer);
470 if (cellAboveRenderer) {
471 HTMLTableCellElementImpl *aboveCell =
472 static_cast<HTMLTableCellElementImpl *>(cellAboveRenderer->element());
475 // search within the above cell we found for a match
476 for (NodeImpl *n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) {
477 if (idFromNode(n) == ID_TEXT
478 && n->renderer() && n->renderer()->style()->visibility() == VISIBLE)
480 // For each text chunk, run the regexp
481 QString nodeString = n->nodeValue().string();
482 int pos = regExp->searchRev(nodeString);
484 return nodeString.mid(pos, regExp->matchedLength()).getNSString();
490 // Any reason in practice to search all cells in that are above cell?
494 NSString *KWQKHTMLPart::searchForLabelsBeforeElement(NSArray *labels, ElementImpl *element)
496 QRegExp *regExp = regExpForLabels(labels);
497 // We stop searching after we've seen this many chars
498 const unsigned int charsSearchedThreshold = 500;
499 // This is the absolute max we search. We allow a little more slop than
500 // charsSearchedThreshold, to make it more likely that we'll search whole nodes.
501 const unsigned int maxCharsSearched = 600;
502 // If the starting element is within a table, the cell that contains it
503 HTMLTableCellElementImpl *startingTableCell = 0;
504 bool searchedCellAbove = false;
506 // walk backwards in the node tree, until another element, or form, or end of tree
507 int unsigned lengthSearched = 0;
509 for (n = element->traversePreviousNode();
510 n && lengthSearched < charsSearchedThreshold;
511 n = n->traversePreviousNode())
513 NodeImpl::Id nodeID = idFromNode(n);
514 if (nodeID == ID_FORM
515 || (n->isHTMLElement()
516 && static_cast<HTMLElementImpl *>(n)->isGenericFormElement()))
518 // We hit another form element or the start of the form - bail out
520 } else if (nodeID == ID_TD && !startingTableCell) {
521 startingTableCell = static_cast<HTMLTableCellElementImpl *>(n);
522 } else if (nodeID == ID_TR && startingTableCell) {
523 NSString *result = searchForLabelsAboveCell(regExp, startingTableCell);
527 searchedCellAbove = true;
528 } else if (nodeID == ID_TEXT
529 && n->renderer() && n->renderer()->style()->visibility() == VISIBLE)
531 // For each text chunk, run the regexp
532 QString nodeString = n->nodeValue().string();
533 // add 100 for slop, to make it more likely that we'll search whole nodes
534 if (lengthSearched + nodeString.length() > maxCharsSearched) {
535 nodeString = nodeString.right(charsSearchedThreshold - lengthSearched);
537 int pos = regExp->searchRev(nodeString);
539 return nodeString.mid(pos, regExp->matchedLength()).getNSString();
541 lengthSearched += nodeString.length();
546 // If we started in a cell, but bailed because we found the start of the form or the
547 // previous element, we still might need to search the row above us for a label.
548 if (startingTableCell && !searchedCellAbove) {
549 return searchForLabelsAboveCell(regExp, startingTableCell);
555 NSString *KWQKHTMLPart::matchLabelsAgainstElement(NSArray *labels, ElementImpl *element)
557 QString name = element->getAttribute(ATTR_NAME).string();
558 // Make numbers and _'s in field names behave like word boundaries, e.g., "address2"
559 name.replace(QRegExp("[[:digit:]]"), " ");
560 name.replace('_', ' ');
562 QRegExp *regExp = regExpForLabels(labels);
563 // Use the largest match we can find in the whole name string
570 pos = regExp->search(name, start);
572 length = regExp->matchedLength();
573 if (length >= bestLength) {
582 return name.mid(bestPos, bestLength).getNSString();
588 // Search from the end of the currently selected location if we are first responder, or from
589 // the beginning of the document if nothing is selected or we're not first responder.
590 bool KWQKHTMLPart::findString(NSString *string, bool forward, bool caseFlag, bool wrapFlag)
592 QString target = QString::fromNSString(string);
593 if (target.isEmpty()) {
597 // Start on the correct edge of the selection, search to edge of document.
598 Range searchRange(xmlDocImpl());
599 searchRange.selectNodeContents(xmlDocImpl());
600 if (selectionStart()) {
602 setStart(searchRange, VisiblePosition(selection().end()));
604 setEnd(searchRange, VisiblePosition(selection().start()));
608 // Do the search once, then do it a second time to handle wrapped search.
609 // Searches some or all of document twice in the failure case, but that's probably OK.
610 Range resultRange = findPlainText(searchRange, target, forward, caseFlag);
611 if (resultRange.collapsed() && wrapFlag) {
612 searchRange.selectNodeContents(xmlDocImpl());
613 resultRange = findPlainText(searchRange, target, forward, caseFlag);
614 // If we got back to the same place we started, that doesn't count as success.
615 if (resultRange == selection().toRange()) {
620 if (resultRange.collapsed()) {
624 setSelection(resultRange);
629 void KWQKHTMLPart::clearRecordedFormValues()
631 // It's safe to assume that our own classes and Foundation data
632 // structures won't raise exceptions in dealloc
634 KWQRelease(_formValuesAboutToBeSubmitted);
635 _formValuesAboutToBeSubmitted = nil;
636 KWQRelease(_formAboutToBeSubmitted);
637 _formAboutToBeSubmitted = nil;
640 void KWQKHTMLPart::recordFormValue(const QString &name, const QString &value, HTMLFormElementImpl *element)
642 // It's safe to assume that our own classes and basic Foundation
643 // data structures won't raise exceptions
645 if (!_formValuesAboutToBeSubmitted) {
646 _formValuesAboutToBeSubmitted = KWQRetainNSRelease([[NSMutableDictionary alloc] init]);
647 ASSERT(!_formAboutToBeSubmitted);
648 _formAboutToBeSubmitted = KWQRetain([DOMElement _elementWithImpl:element]);
650 ASSERT([_formAboutToBeSubmitted _elementImpl] == element);
652 [_formValuesAboutToBeSubmitted setObject:value.getNSString() forKey:name.getNSString()];
655 void KWQKHTMLPart::submitForm(const KURL &url, const URLArgs &args)
657 KWQ_BLOCK_EXCEPTIONS;
659 // FIXME: We'd like to remove this altogether and fix the multiple form submission issue another way.
660 // We do not want to submit more than one form from the same page,
661 // nor do we want to submit a single form more than once.
662 // This flag prevents these from happening; not sure how other browsers prevent this.
663 // The flag is reset in each time we start handle a new mouse or key down event, and
664 // also in setView since this part may get reused for a page from the back/forward cache.
665 // The form multi-submit logic here is only needed when we are submitting a form that affects this frame.
666 // FIXME: Frame targeting is only one of the ways the submission could end up doing something other
667 // than replacing this frame's content, so this check is flawed. On the other hand, the check is hardly
668 // needed any more now that we reset _submittedFormURL on each mouse or key down event.
669 WebCoreBridge *target = args.frameName.isEmpty() ? _bridge : [_bridge findFrameNamed:args.frameName.getNSString()];
670 KHTMLPart *targetPart = [target part];
671 bool willReplaceThisFrame = false;
672 for (KHTMLPart *p = this; p; p = p->parentPart()) {
673 if (p == targetPart) {
674 willReplaceThisFrame = true;
678 if (willReplaceThisFrame) {
679 if (_submittedFormURL == url) {
682 _submittedFormURL = url;
685 if (!args.doPost()) {
686 [_bridge loadURL:url.getNSURL()
687 referrer:[_bridge referrer]
690 target:args.frameName.getNSString()
691 triggeringEvent:_currentEvent
692 form:_formAboutToBeSubmitted
693 formValues:_formValuesAboutToBeSubmitted];
695 ASSERT(args.contentType().startsWith("Content-Type: "));
696 [_bridge postWithURL:url.getNSURL()
697 referrer:[_bridge referrer]
698 target:args.frameName.getNSString()
699 data:arrayFromFormData(args.postData)
700 contentType:args.contentType().mid(14).getNSString()
701 triggeringEvent:_currentEvent
702 form:_formAboutToBeSubmitted
703 formValues:_formValuesAboutToBeSubmitted];
705 clearRecordedFormValues();
707 KWQ_UNBLOCK_EXCEPTIONS;
710 void KWQKHTMLPart::setEncoding(const QString &name, bool userChosen)
712 if (!d->m_workingURL.isEmpty()) {
715 d->m_encoding = name;
716 d->m_haveEncoding = userChosen;
719 void KWQKHTMLPart::addData(const char *bytes, int length)
721 ASSERT(d->m_workingURL.isEmpty());
723 ASSERT(d->m_doc->parsing());
724 write(bytes, length);
727 void KHTMLPart::frameDetached()
729 // FIXME: This should be a virtual function, with the first part in KWQKHTMLPart, and the second
730 // part in KHTMLPart, so it works for KHTML too.
732 KWQ_BLOCK_EXCEPTIONS;
733 [KWQ(this)->bridge() frameDetached];
734 KWQ_UNBLOCK_EXCEPTIONS;
736 KHTMLPart *parent = parentPart();
738 FrameList& parentFrames = parent->d->m_frames;
739 FrameIt end = parentFrames.end();
740 for (FrameIt it = parentFrames.begin(); it != end; ++it) {
741 ChildFrame &child = *it;
742 if (child.m_part == this) {
743 parent->disconnectChild(&child);
744 parentFrames.remove(it);
752 void KWQKHTMLPart::urlSelected(const KURL &url, int button, int state, const URLArgs &args)
754 KWQ_BLOCK_EXCEPTIONS;
755 [_bridge loadURL:url.getNSURL()
756 referrer:[_bridge referrer]
759 target:args.frameName.getNSString()
760 triggeringEvent:_currentEvent
763 KWQ_UNBLOCK_EXCEPTIONS;
766 class KWQPluginPart : public ReadOnlyPart
768 virtual bool openURL(const KURL &) { return true; }
769 virtual bool closeURL() { return true; }
772 ReadOnlyPart *KWQKHTMLPart::createPart(const ChildFrame &child, const KURL &url, const QString &mimeType)
774 KWQ_BLOCK_EXCEPTIONS;
777 BOOL needFrame = [_bridge frameRequiredForMIMEType:mimeType.getNSString() URL:url.getNSURL()];
778 if (child.m_type == ChildFrame::Object && !needFrame) {
779 KWQPluginPart *newPart = new KWQPluginPart;
780 newPart->setWidget(new QWidget([_bridge viewForPluginWithURL:url.getNSURL()
781 attributeNames:child.m_paramNames.getNSArray()
782 attributeValues:child.m_paramValues.getNSArray()
783 MIMEType:child.m_args.serviceType.getNSString()]));
786 LOG(Frames, "name %s", child.m_name.ascii());
787 BOOL allowsScrolling = YES;
788 int marginWidth = -1;
789 int marginHeight = -1;
790 if (child.m_type != ChildFrame::Object) {
791 HTMLFrameElementImpl *o = static_cast<HTMLFrameElementImpl *>(child.m_frame->element());
792 allowsScrolling = o->scrollingMode() != QScrollView::AlwaysOff;
793 marginWidth = o->getMarginWidth();
794 marginHeight = o->getMarginHeight();
796 WebCoreBridge *childBridge = [_bridge createChildFrameNamed:child.m_name.getNSString()
797 withURL:url.getNSURL()
798 renderPart:child.m_frame
799 allowsScrolling:allowsScrolling
800 marginWidth:marginWidth
801 marginHeight:marginHeight];
802 // This call needs to return an object with a ref, since the caller will expect to own it.
803 // childBridge owns the only ref so far.
804 [childBridge part]->ref();
805 part = [childBridge part];
810 KWQ_UNBLOCK_EXCEPTIONS;
815 void KWQKHTMLPart::setView(KHTMLView *view)
817 // Detach the document now, so any onUnload handlers get run - if
818 // we wait until the view is destroyed, then things won't be
819 // hooked up enough for some JavaScript calls to work.
820 if (d->m_doc && view == NULL) {
833 // Only one form submission is allowed per view of a part.
834 // Since this part may be getting reused as a result of being
835 // pulled from the back/forward cache, reset this flag.
836 _submittedFormURL = KURL();
839 KHTMLView *KWQKHTMLPart::view() const
844 void KWQKHTMLPart::setTitle(const DOMString &title)
846 QString text = title.string();
847 text.replace(QChar('\\'), backslashAsCurrencySymbol());
849 KWQ_BLOCK_EXCEPTIONS;
850 [_bridge setTitle:text.getNSString()];
851 KWQ_UNBLOCK_EXCEPTIONS;
854 void KWQKHTMLPart::setStatusBarText(const QString &status)
856 QString text = status;
857 text.replace(QChar('\\'), backslashAsCurrencySymbol());
859 KWQ_BLOCK_EXCEPTIONS;
860 [_bridge setStatusText:text.getNSString()];
861 KWQ_UNBLOCK_EXCEPTIONS;
864 void KWQKHTMLPart::scheduleClose()
866 KWQ_BLOCK_EXCEPTIONS;
867 [_bridge closeWindowSoon];
868 KWQ_UNBLOCK_EXCEPTIONS;
871 void KWQKHTMLPart::unfocusWindow()
873 KWQ_BLOCK_EXCEPTIONS;
874 [_bridge unfocusWindow];
875 KWQ_UNBLOCK_EXCEPTIONS;
878 void KWQKHTMLPart::jumpToSelection()
880 // Assumes that selection start will only ever be a text node. This is currently
881 // true, but will it always be so?
882 if (d->m_selection.start().isNotNull()) {
883 RenderText *rt = dynamic_cast<RenderText *>(d->m_selection.start().node()->renderer());
886 rt->posOfChar(d->m_selection.start().offset(), x, y);
887 // The -50 offset is copied from KHTMLPart::findTextNext, which sets the contents position
888 // after finding a matched text string.
889 d->m_view->setContentsPos(x - 50, y - 50);
892 Something like this would fix <rdar://problem/3154293>: "Find Next should not scroll page if the next target is already visible"
894 I think this would be a better way to do this, to avoid needless horizontal scrolling,
895 but it is not feasible until selectionRect() returns a tighter rect around the
896 selected text. Right now it works at element granularity.
898 NSView *docView = d->m_view->getDocumentView();
900 KWQ_BLOCK_EXCEPTIONS;
901 NSRect selRect = NSRect(selectionRect());
902 NSRect visRect = [docView visibleRect];
903 if (!NSContainsRect(visRect, selRect)) {
904 // pad a bit so we overscroll slightly
905 selRect = NSInsetRect(selRect, -10.0, -10.0);
906 selRect = NSIntersectionRect(selRect, [docView bounds]);
907 [docView scrollRectToVisible:selRect];
909 KWQ_UNBLOCK_EXCEPTIONS;
914 QString KWQKHTMLPart::advanceToNextMisspelling(bool startBeforeSelection)
916 // The basic approach is to search in two phases - from the selection end to the end of the doc, and
917 // then we wrap and search from the doc start to (approximately) where we started.
919 // Start at the end of the selection, search to edge of document. Starting at the selection end makes
920 // repeated "check spelling" commands work.
921 Range searchRange(xmlDocImpl());
922 searchRange.selectNodeContents(xmlDocImpl());
923 bool startedWithSelection = false;
924 if (selectionStart()) {
925 startedWithSelection = true;
926 if (startBeforeSelection) {
927 VisiblePosition start(selection().start());
928 // We match AppKit's rule: Start 1 character before the selection.
929 VisiblePosition oneBeforeStart = start.previous();
930 setStart(searchRange, oneBeforeStart.isNotNull() ? oneBeforeStart : start);
932 setStart(searchRange, VisiblePosition(selection().end()));
936 // If we're not in an editable node, try to find one, make that our range to work in
937 NodeImpl *editableNodeImpl = searchRange.startContainer().handle();
938 if (!editableNodeImpl->isContentEditable()) {
939 editableNodeImpl = editableNodeImpl->nextEditable();
940 if (!editableNodeImpl) {
943 searchRange.setStartBefore(editableNodeImpl);
944 startedWithSelection = false; // won't need to wrap
947 // topNode defines the whole range we want to operate on
948 Node topNode(editableNodeImpl->rootEditableElement());
949 searchRange.setEndAfter(topNode);
951 // Make sure start of searchRange is not in the middle of a word. Jumping back a char and then
952 // forward by a word happens to do the trick.
953 if (startedWithSelection) {
954 VisiblePosition oneBeforeStart = startVisiblePosition(searchRange).previous();
955 if (oneBeforeStart.isNotNull()) {
956 setStart(searchRange, endOfWord(oneBeforeStart));
957 } // else we were already at the start of the editable node
960 if (searchRange.collapsed()) {
961 return QString(); // nothing to search in
964 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
965 WordAwareIterator it(searchRange);
966 bool wrapped = false;
968 // We go to the end of our first range instead of the start of it, just to be sure
969 // we don't get foiled by any word boundary problems at the start. It means we might
970 // do a tiny bit more searching.
971 Node searchEndAfterWrapNode = it.range().endContainer();
972 long searchEndAfterWrapOffset = it.range().endOffset();
975 if (!it.atEnd()) { // we may be starting at the end of the doc, and already by atEnd
976 const QChar *chars = it.characters();
977 long len = it.length();
978 if (len > 1 || !chars[0].isSpace()) {
979 NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:(unichar *)chars length:len freeWhenDone:NO];
980 NSRange misspelling = [checker checkSpellingOfString:chunk startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:[_bridge spellCheckerDocumentTag] wordCount:NULL];
982 if (misspelling.length > 0) {
983 // Build up result range and string. Note the misspelling may span many text nodes,
984 // but the CharIterator insulates us from this complexity
985 Range misspellingRange(xmlDocImpl());
986 CharacterIterator chars(it.range());
987 chars.advance(misspelling.location);
988 misspellingRange.setStart(chars.range().startContainer(), chars.range().startOffset());
989 QString result = chars.string(misspelling.length);
990 misspellingRange.setEnd(chars.range().startContainer(), chars.range().startOffset());
992 setSelection(misspellingRange);
994 // Mark misspelling in document.
995 xmlDocImpl()->addMarker(misspellingRange, DocumentMarker::Spelling);
1003 if (wrapped || !startedWithSelection) {
1004 break; // finished the second range, or we did the whole doc with the first range
1006 // we've gone from the selection to the end of doc, now wrap around
1008 searchRange.setStartBefore(topNode);
1009 // going until the end of the very first chunk we tested is far enough
1010 searchRange.setEnd(searchEndAfterWrapNode, searchEndAfterWrapOffset);
1011 it = WordAwareIterator(searchRange);
1019 bool KWQKHTMLPart::scrollOverflow(KWQScrollDirection direction, KWQScrollGranularity granularity)
1021 if (!xmlDocImpl()) {
1025 NodeImpl *node = xmlDocImpl()->focusNode();
1027 node = d->m_mousePressNode.handle();
1031 RenderObject *r = node->renderer();
1033 return r->scroll(direction, granularity);
1040 bool KWQKHTMLPart::scrollOverflowWithScrollWheelEvent(NSEvent *event)
1042 RenderObject *r = renderer();
1047 NSPoint point = [d->m_view->getDocumentView() convertPoint:[event locationInWindow] fromView:nil];
1048 RenderObject::NodeInfo nodeInfo(true, true);
1049 r->layer()->hitTest(nodeInfo, (int)point.x, (int)point.y);
1051 NodeImpl *node = nodeInfo.innerNode();
1056 r = node->renderer();
1061 KWQScrollDirection direction;
1063 float deltaX = [event deltaX];
1064 float deltaY = [event deltaY];
1066 direction = KWQScrollRight;
1067 multiplier = -deltaX;
1068 } else if (deltaX > 0) {
1069 direction = KWQScrollLeft;
1070 multiplier = deltaX;
1071 } else if (deltaY < 0) {
1072 direction = KWQScrollDown;
1073 multiplier = -deltaY;
1074 } else if (deltaY > 0) {
1075 direction = KWQScrollUp;
1076 multiplier = deltaY;
1080 return r->scroll(direction, KWQScrollWheel, multiplier);
1083 void KWQKHTMLPart::redirectionTimerStartedOrStopped()
1085 // Don't report history navigations, just actual redirection.
1086 if (d->m_scheduledRedirection == historyNavigationScheduled) {
1090 KWQ_BLOCK_EXCEPTIONS;
1091 if (d->m_redirectionTimer.isActive()) {
1092 [_bridge reportClientRedirectToURL:KURL(d->m_redirectURL).getNSURL()
1093 delay:d->m_delayRedirect
1094 fireDate:[d->m_redirectionTimer.getNSTimer() fireDate]
1095 lockHistory:d->m_redirectLockHistory
1096 isJavaScriptFormAction:d->m_executingJavaScriptFormAction];
1098 [_bridge reportClientRedirectCancelled:d->m_cancelWithLoadInProgress];
1100 KWQ_UNBLOCK_EXCEPTIONS;
1103 void KWQKHTMLPart::paint(QPainter *p, const QRect &rect)
1107 if (p->device()->devType() == QInternal::Printer)
1108 fillWithRed = false; // Printing, don't fill with red (can't remember why).
1109 else if (!xmlDocImpl() || xmlDocImpl()->ownerElement())
1110 fillWithRed = false; // Subframe, don't fill with red.
1111 else if (view() && view()->isTransparent())
1112 fillWithRed = false; // Transparent, don't fill with red.
1113 else if (_drawSelectionOnly)
1114 fillWithRed = false; // Selections are transparent, don't fill with red.
1115 else if (_elementToDraw != 0)
1116 fillWithRed = false; // Element images are transparent, don't fill with red.
1121 p->fillRect(rect.x(), rect.y(), rect.width(), rect.height(), QColor(0xFF, 0, 0));
1126 // _elementToDraw is used to draw only one element
1127 RenderObject *eltRenderer = (_elementToDraw != 0) ? _elementToDraw.handle()->renderer() : 0;
1128 renderer()->layer()->paint(p, rect, _drawSelectionOnly, eltRenderer);
1131 // Regions may have changed as a result of the visibility/z-index of element changing.
1132 if (renderer()->document()->dashboardRegionsDirty()){
1133 renderer()->canvas()->view()->updateDashboardRegions();
1137 ERROR("called KWQKHTMLPart::paint with nil renderer");
1141 void KWQKHTMLPart::adjustPageHeight(float *newBottom, float oldTop, float oldBottom, float bottomLimit)
1143 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
1145 // Use a printer device, with painting disabled for the pagination phase
1146 QPainter painter(true);
1147 painter.setPaintingDisabled(true);
1149 root->setTruncatedAt((int)floor(oldBottom));
1150 QRect dirtyRect(0, (int)floor(oldTop),
1151 root->docWidth(), (int)ceil(oldBottom-oldTop));
1152 root->setPrintRect(dirtyRect);
1153 root->layer()->paint(&painter, dirtyRect);
1154 *newBottom = root->bestTruncatedAt();
1155 if (*newBottom == 0) {
1156 *newBottom = oldBottom;
1159 *newBottom = oldBottom;
1163 RenderObject *KWQKHTMLPart::renderer() const
1165 DocumentImpl *doc = xmlDocImpl();
1166 return doc ? doc->renderer() : 0;
1169 QString KWQKHTMLPart::userAgent() const
1171 KWQ_BLOCK_EXCEPTIONS;
1172 return QString::fromNSString([_bridge userAgentForURL:m_url.getNSURL()]);
1173 KWQ_UNBLOCK_EXCEPTIONS;
1178 QString KWQKHTMLPart::mimeTypeForFileName(const QString &fileName) const
1180 NSString *ns = fileName.getNSString();
1182 KWQ_BLOCK_EXCEPTIONS;
1183 return QString::fromNSString([_bridge MIMETypeForPath:ns]);
1184 KWQ_UNBLOCK_EXCEPTIONS;
1189 NSView *KWQKHTMLPart::nextKeyViewInFrame(NodeImpl *node, KWQSelectionDirection direction)
1191 DocumentImpl *doc = xmlDocImpl();
1196 node = direction == KWQSelectingNext
1197 ? doc->nextFocusNode(node) : doc->previousFocusNode(node);
1201 RenderWidget *renderWidget = dynamic_cast<RenderWidget *>(node->renderer());
1203 QWidget *widget = renderWidget->widget();
1204 KHTMLView *childFrameWidget = dynamic_cast<KHTMLView *>(widget);
1206 if (childFrameWidget) {
1207 view = KWQ(childFrameWidget->part())->nextKeyViewInFrame(0, direction);
1208 } else if (widget) {
1209 view = widget->getView();
1216 doc->setFocusNode(node);
1218 view()->ensureRectVisibleCentered(node->getRect());
1220 [_bridge makeFirstResponder:[_bridge documentView]];
1221 return [_bridge documentView];
1226 NSView *KWQKHTMLPart::nextKeyViewInFrameHierarchy(NodeImpl *node, KWQSelectionDirection direction)
1228 NSView *next = nextKeyViewInFrame(node, direction);
1230 KWQKHTMLPart *parent = KWQ(parentPart());
1232 next = parent->nextKeyView(parent->childFrame(this)->m_frame->element(), direction);
1236 // remove focus from currently focused node if we're giving focus to another view
1237 if (next && (next != [_bridge documentView])) {
1238 DocumentImpl *doc = xmlDocImpl();
1240 doc->setFocusNode(0);
1247 NSView *KWQKHTMLPart::nextKeyView(NodeImpl *node, KWQSelectionDirection direction)
1249 KWQ_BLOCK_EXCEPTIONS;
1251 NSView * next = nextKeyViewInFrameHierarchy(node, direction);
1256 // Look at views from the top level part up, looking for a next key view that we can use.
1258 next = direction == KWQSelectingNext
1259 ? [_bridge nextKeyViewOutsideWebFrameViews]
1260 : [_bridge previousKeyViewOutsideWebFrameViews];
1266 KWQ_UNBLOCK_EXCEPTIONS;
1268 // If all else fails, make a loop by starting from 0.
1269 return nextKeyViewInFrameHierarchy(0, direction);
1272 NSView *KWQKHTMLPart::nextKeyViewForWidget(QWidget *startingWidget, KWQSelectionDirection direction)
1274 // Use the event filter object to figure out which RenderWidget owns this QWidget and get to the DOM.
1275 // Then get the next key view in the order determined by the DOM.
1276 NodeImpl *node = nodeForWidget(startingWidget);
1278 return partForNode(node)->nextKeyView(node, direction);
1281 bool KWQKHTMLPart::currentEventIsMouseDownInWidget(QWidget *candidate)
1283 KWQ_BLOCK_EXCEPTIONS;
1284 switch ([[NSApp currentEvent] type]) {
1285 case NSLeftMouseDown:
1286 case NSRightMouseDown:
1287 case NSOtherMouseDown:
1292 KWQ_UNBLOCK_EXCEPTIONS;
1294 NodeImpl *node = nodeForWidget(candidate);
1296 return partForNode(node)->nodeUnderMouse() == node;
1299 bool KWQKHTMLPart::currentEventIsKeyboardOptionTab()
1301 KWQ_BLOCK_EXCEPTIONS;
1302 NSEvent *evt = [NSApp currentEvent];
1303 if ([evt type] != NSKeyDown) {
1307 if (([evt modifierFlags] & NSAlternateKeyMask) == 0) {
1311 NSString *chars = [evt charactersIgnoringModifiers];
1312 if ([chars length] != 1)
1315 const unichar tabKey = 0x0009;
1316 const unichar shiftTabKey = 0x0019;
1317 unichar c = [chars characterAtIndex:0];
1318 if (c != tabKey && c != shiftTabKey)
1321 KWQ_UNBLOCK_EXCEPTIONS;
1325 bool KWQKHTMLPart::handleKeyboardOptionTabInView(NSView *view)
1327 if (KWQKHTMLPart::currentEventIsKeyboardOptionTab()) {
1328 if (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) != 0) {
1329 [[view window] selectKeyViewPrecedingView:view];
1331 [[view window] selectKeyViewFollowingView:view];
1339 bool KWQKHTMLPart::tabsToLinks() const
1341 if ([_bridge keyboardUIMode] & WebCoreKeyboardAccessTabsToLinks)
1342 return !KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1344 return KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1347 bool KWQKHTMLPart::tabsToAllControls() const
1349 WebCoreKeyboardUIMode keyboardUIMode = [_bridge keyboardUIMode];
1350 BOOL handlingOptionTab = KWQKHTMLPart::currentEventIsKeyboardOptionTab();
1352 // If tab-to-links is off, option-tab always highlights all controls
1353 if ((keyboardUIMode & WebCoreKeyboardAccessTabsToLinks) == 0 && handlingOptionTab) {
1357 // If system preferences say to include all controls, we always include all controls
1358 if (keyboardUIMode & WebCoreKeyboardAccessFull) {
1362 // Otherwise tab-to-links includes all controls, unless the sense is flipped via option-tab.
1363 if (keyboardUIMode & WebCoreKeyboardAccessTabsToLinks) {
1364 return !handlingOptionTab;
1367 return handlingOptionTab;
1370 KJS::Bindings::RootObject *KWQKHTMLPart::executionContextForDOM()
1372 return bindingRootObject();
1375 KJS::Bindings::RootObject *KWQKHTMLPart::bindingRootObject()
1377 if (!_bindingRoot) {
1378 _bindingRoot = new KJS::Bindings::RootObject(0); // The root gets deleted by JavaScriptCore.
1379 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1380 _bindingRoot->setRootObjectImp (win);
1381 _bindingRoot->setInterpreter (KJSProxy::proxy(this)->interpreter());
1382 addPluginRootObject (_bindingRoot);
1384 return _bindingRoot;
1387 WebScriptObject *KWQKHTMLPart::windowScriptObject()
1389 if (!_windowScriptObject) {
1390 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1391 _windowScriptObject = KWQRetainNSRelease([[WebScriptObject alloc] _initWithObjectImp:win root:bindingRootObject()]);
1394 return _windowScriptObject;
1397 NPObject *KWQKHTMLPart::windowScriptNPObject()
1399 if (!_windowScriptNPObject) {
1400 KJS::ObjectImp *win = static_cast<KJS::ObjectImp *>(KJS::Window::retrieveWindow(this));
1401 _windowScriptNPObject = _NPN_CreateScriptObject (0, win, bindingRootObject());
1404 return _windowScriptNPObject;
1407 void KWQKHTMLPart::partClearedInBegin()
1409 [_bridge windowObjectCleared];
1412 QMap<int, ScheduledAction*> *KWQKHTMLPart::pauseActions(const void *key)
1414 if (d->m_doc && d->m_jscript) {
1415 Window *w = Window::retrieveWindow(this);
1416 if (w && w->hasTimeouts()) {
1417 return w->pauseTimeouts(key);
1423 void KWQKHTMLPart::resumeActions(QMap<int, ScheduledAction*> *actions, const void *key)
1425 if (d->m_doc && d->m_jscript && d->m_bJScriptEnabled) {
1426 Window *w = Window::retrieveWindow(this);
1428 w->resumeTimeouts(actions, key);
1433 bool KWQKHTMLPart::canCachePage()
1435 // Only save page state if:
1436 // 1. We're not a frame or frameset.
1437 // 2. The page has no unload handler.
1438 // 3. The page has no password fields.
1439 // 4. The URL for the page is not https.
1440 // 5. The page has no applets.
1441 if (d->m_frames.count() ||
1443 m_url.protocol().startsWith("https") ||
1444 (d->m_doc && (htmlDocument().applets().length() != 0 ||
1445 d->m_doc->hasWindowEventListener(EventImpl::UNLOAD_EVENT) ||
1446 d->m_doc->hasPasswordField()))) {
1452 void KWQKHTMLPart::saveWindowProperties(SavedProperties *windowProperties)
1454 Window *window = Window::retrieveWindow(this);
1456 window->saveProperties(*windowProperties);
1459 void KWQKHTMLPart::saveLocationProperties(SavedProperties *locationProperties)
1461 Window *window = Window::retrieveWindow(this);
1463 Interpreter::lock();
1464 Location *location = window->location();
1465 Interpreter::unlock();
1466 location->saveProperties(*locationProperties);
1470 void KWQKHTMLPart::restoreWindowProperties(SavedProperties *windowProperties)
1472 Window *window = Window::retrieveWindow(this);
1474 window->restoreProperties(*windowProperties);
1477 void KWQKHTMLPart::restoreLocationProperties(SavedProperties *locationProperties)
1479 Window *window = Window::retrieveWindow(this);
1481 Interpreter::lock();
1482 Location *location = window->location();
1483 Interpreter::unlock();
1484 location->restoreProperties(*locationProperties);
1488 void KWQKHTMLPart::saveInterpreterBuiltins(SavedBuiltins &interpreterBuiltins)
1490 if (jScript() && jScript()->interpreter()) {
1491 jScript()->interpreter()->saveBuiltins(interpreterBuiltins);
1495 void KWQKHTMLPart::restoreInterpreterBuiltins(const SavedBuiltins &interpreterBuiltins)
1497 if (jScript() && jScript()->interpreter()) {
1498 jScript()->interpreter()->restoreBuiltins(interpreterBuiltins);
1502 void KWQKHTMLPart::openURLFromPageCache(KWQPageState *state)
1504 // It's safe to assume none of the KWQPageState methods will raise
1505 // exceptions, since KWQPageState is implemented by WebCore and
1508 DocumentImpl *doc = [state document];
1509 KURL *url = [state URL];
1510 SavedProperties *windowProperties = [state windowProperties];
1511 SavedProperties *locationProperties = [state locationProperties];
1512 SavedBuiltins *interpreterBuiltins = [state interpreterBuiltins];
1513 QMap<int, ScheduledAction*> *actions = [state pausedActions];
1515 cancelRedirection();
1517 // We still have to close the previous part page.
1518 if (!d->m_restored){
1522 d->m_bComplete = false;
1524 // Don't re-emit the load event.
1525 d->m_bLoadEventEmitted = true;
1527 // delete old status bar msg's from kjs (if it _was_ activated on last URL)
1528 if( d->m_bJScriptEnabled )
1530 d->m_kjsStatusBarText = QString::null;
1531 d->m_kjsDefaultStatusBarText = QString::null;
1538 // 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
1539 // data arrives) (Simon)
1540 if(m_url.protocol().startsWith( "http" ) && !m_url.host().isEmpty() && m_url.path().isEmpty()) {
1542 emit d->m_extension->setLocationBarURL( m_url.prettyURL() );
1545 // copy to m_workingURL after fixing m_url above
1546 d->m_workingURL = m_url;
1550 // -----------begin-----------
1553 doc->setInPageCache(NO);
1555 d->m_bCleared = false;
1557 d->m_bComplete = false;
1558 d->m_bLoadEventEmitted = false;
1559 d->m_referrer = m_url.url();
1561 setView(doc->view());
1566 Decoder *decoder = doc->decoder();
1571 d->m_decoder->deref();
1573 d->m_decoder = decoder;
1575 updatePolicyBaseURL();
1577 restoreWindowProperties (windowProperties);
1578 restoreLocationProperties (locationProperties);
1579 restoreInterpreterBuiltins (*interpreterBuiltins);
1582 resumeActions (actions, state);
1587 KWQKHTMLPart *KWQKHTMLPart::partForWidget(const QWidget *widget)
1589 ASSERT_ARG(widget, widget);
1591 NodeImpl *node = nodeForWidget(widget);
1593 return partForNode(node);
1596 // Assume all widgets are either form controls, or KHTMLViews.
1597 const KHTMLView *view = dynamic_cast<const KHTMLView *>(widget);
1599 return KWQ(view->part());
1602 WebCoreBridge *KWQKHTMLPart::bridgeForWidget(const QWidget *widget)
1604 ASSERT_ARG(widget, widget);
1606 KWQKHTMLPart *part = partForWidget(widget);
1608 return part->bridge();
1611 KWQKHTMLPart *KWQKHTMLPart::partForNode(NodeImpl *node)
1613 ASSERT_ARG(node, node);
1614 return KWQ(node->getDocument()->part());
1617 NSView *KWQKHTMLPart::documentViewForNode(DOM::NodeImpl *node)
1619 WebCoreBridge *bridge = partForNode(node)->bridge();
1620 return [bridge documentView];
1623 NodeImpl *KWQKHTMLPart::nodeForWidget(const QWidget *widget)
1625 ASSERT_ARG(widget, widget);
1626 const QObject *o = widget->eventFilterObject();
1627 return o ? static_cast<const RenderWidget *>(o)->element() : 0;
1630 void KWQKHTMLPart::setDocumentFocus(QWidget *widget)
1632 NodeImpl *node = nodeForWidget(widget);
1634 node->getDocument()->setFocusNode(node);
1636 ERROR("unable to clear focus because widget had no corresponding node");
1640 void KWQKHTMLPart::clearDocumentFocus(QWidget *widget)
1642 NodeImpl *node = nodeForWidget(widget);
1644 node->getDocument()->setFocusNode(0);
1646 ERROR("unable to clear focus because widget had no corresponding node");
1650 void KWQKHTMLPart::saveDocumentState()
1652 // Do not save doc state if the page has a password field and a form that would be submitted
1654 if (!(d->m_doc && d->m_doc->hasPasswordField() && d->m_doc->hasSecureForm())) {
1655 KWQ_BLOCK_EXCEPTIONS;
1656 [_bridge saveDocumentState];
1657 KWQ_UNBLOCK_EXCEPTIONS;
1661 void KWQKHTMLPart::restoreDocumentState()
1663 KWQ_BLOCK_EXCEPTIONS;
1664 [_bridge restoreDocumentState];
1665 KWQ_UNBLOCK_EXCEPTIONS;
1668 QPtrList<KWQKHTMLPart> &KWQKHTMLPart::mutableInstances()
1670 static QPtrList<KWQKHTMLPart> instancesList;
1671 return instancesList;
1674 void KWQKHTMLPart::updatePolicyBaseURL()
1676 if (parentPart() && parentPart()->xmlDocImpl()) {
1677 setPolicyBaseURL(parentPart()->xmlDocImpl()->policyBaseURL());
1679 setPolicyBaseURL(m_url.url());
1683 void KWQKHTMLPart::setPolicyBaseURL(const DOMString &s)
1686 xmlDocImpl()->setPolicyBaseURL(s);
1687 ConstFrameIt end = d->m_frames.end();
1688 for (ConstFrameIt it = d->m_frames.begin(); it != end; ++it) {
1689 ReadOnlyPart *subpart = (*it).m_part;
1690 static_cast<KWQKHTMLPart *>(subpart)->setPolicyBaseURL(s);
1694 QString KWQKHTMLPart::requestedURLString() const
1696 KWQ_BLOCK_EXCEPTIONS;
1697 return QString::fromNSString([_bridge requestedURLString]);
1698 KWQ_UNBLOCK_EXCEPTIONS;
1703 QString KWQKHTMLPart::incomingReferrer() const
1705 KWQ_BLOCK_EXCEPTIONS;
1706 return QString::fromNSString([_bridge incomingReferrer]);
1707 KWQ_UNBLOCK_EXCEPTIONS;
1712 void KWQKHTMLPart::forceLayout()
1714 KHTMLView *v = d->m_view;
1717 // We cannot unschedule a pending relayout, since the force can be called with
1718 // a tiny rectangle from a drawRect update. By unscheduling we in effect
1719 // "validate" and stop the necessary full repaint from occurring. Basically any basic
1720 // append/remove DHTML is broken by this call. For now, I have removed the optimization
1721 // until we have a better invalidation stategy. -dwh
1722 //v->unscheduleRelayout();
1726 void KWQKHTMLPart::forceLayoutWithPageWidthRange(float minPageWidth, float maxPageWidth)
1728 // Dumping externalRepresentation(_part->renderer()).ascii() is a good trick to see
1729 // the state of things before and after the layout
1730 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
1732 // This magic is basically copied from khtmlview::print
1733 int pageW = (int)ceil(minPageWidth);
1734 root->setWidth(pageW);
1735 root->setNeedsLayoutAndMinMaxRecalc();
1738 // If we don't fit in the minimum page width, we'll lay out again. If we don't fit in the
1739 // maximum page width, we will lay out to the maximum page width and clip extra content.
1740 // FIXME: We are assuming a shrink-to-fit printing implementation. A cropping
1741 // implementation should not do this!
1742 int rightmostPos = root->rightmostPosition();
1743 if (rightmostPos > minPageWidth) {
1744 pageW = kMin(rightmostPos, (int)ceil(maxPageWidth));
1745 root->setWidth(pageW);
1746 root->setNeedsLayoutAndMinMaxRecalc();
1752 void KWQKHTMLPart::sendResizeEvent()
1754 KHTMLView *v = d->m_view;
1761 void KWQKHTMLPart::sendScrollEvent()
1763 KHTMLView *v = d->m_view;
1765 DocumentImpl *doc = xmlDocImpl();
1768 doc->dispatchHTMLEvent(EventImpl::SCROLL_EVENT, true, false);
1772 void KWQKHTMLPart::runJavaScriptAlert(const QString &message)
1774 QString text = message;
1775 text.replace(QChar('\\'), backslashAsCurrencySymbol());
1776 KWQ_BLOCK_EXCEPTIONS;
1777 [_bridge runJavaScriptAlertPanelWithMessage:text.getNSString()];
1778 KWQ_UNBLOCK_EXCEPTIONS;
1781 bool KWQKHTMLPart::runJavaScriptConfirm(const QString &message)
1783 QString text = message;
1784 text.replace(QChar('\\'), backslashAsCurrencySymbol());
1786 KWQ_BLOCK_EXCEPTIONS;
1787 return [_bridge runJavaScriptConfirmPanelWithMessage:text.getNSString()];
1788 KWQ_UNBLOCK_EXCEPTIONS;
1793 bool KWQKHTMLPart::runJavaScriptPrompt(const QString &prompt, const QString &defaultValue, QString &result)
1795 QString promptText = prompt;
1796 promptText.replace(QChar('\\'), backslashAsCurrencySymbol());
1797 QString defaultValueText = defaultValue;
1798 defaultValueText.replace(QChar('\\'), backslashAsCurrencySymbol());
1800 KWQ_BLOCK_EXCEPTIONS;
1801 NSString *returnedText = nil;
1803 bool ok = [_bridge runJavaScriptTextInputPanelWithPrompt:prompt.getNSString()
1804 defaultText:defaultValue.getNSString() returningText:&returnedText];
1807 result = QString::fromNSString(returnedText);
1808 result.replace(backslashAsCurrencySymbol(), QChar('\\'));
1812 KWQ_UNBLOCK_EXCEPTIONS;
1817 bool KWQKHTMLPart::locationbarVisible()
1819 return [_bridge areToolbarsVisible];
1822 bool KWQKHTMLPart::menubarVisible()
1824 // The menubar is always on in Mac OS X UI
1828 bool KWQKHTMLPart::personalbarVisible()
1830 return [_bridge areToolbarsVisible];
1833 bool KWQKHTMLPart::scrollbarsVisible()
1838 if (view()->hScrollBarMode() == QScrollView::AlwaysOff || view()->vScrollBarMode() == QScrollView::AlwaysOff)
1844 bool KWQKHTMLPart::statusbarVisible()
1846 return [_bridge isStatusBarVisible];
1849 bool KWQKHTMLPart::toolbarVisible()
1851 return [_bridge areToolbarsVisible];
1854 void KWQKHTMLPart::addMessageToConsole(const QString &message, unsigned lineNumber, const QString &sourceURL)
1856 NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
1857 message.getNSString(), @"message",
1858 [NSNumber numberWithInt: lineNumber], @"lineNumber",
1859 sourceURL.getNSString(), @"sourceURL",
1861 [_bridge addMessageToConsole:dictionary];
1864 void KWQKHTMLPart::createEmptyDocument()
1866 // Although it's not completely clear from the name of this function,
1867 // it does nothing if we already have a document, and just creates an
1868 // empty one if we have no document at all.
1870 KWQ_BLOCK_EXCEPTIONS;
1871 [_bridge loadEmptyDocumentSynchronously];
1872 KWQ_UNBLOCK_EXCEPTIONS;
1874 if (parentPart() && (parentPart()->childFrame(this)->m_type == ChildFrame::IFrame ||
1875 parentPart()->childFrame(this)->m_type == ChildFrame::Object)) {
1876 d->m_doc->setBaseURL(parentPart()->d->m_doc->baseURL());
1881 void KWQKHTMLPart::addMetaData(const QString &key, const QString &value)
1883 d->m_job->addMetaData(key, value);
1886 bool KWQKHTMLPart::keyEvent(NSEvent *event)
1888 KWQ_BLOCK_EXCEPTIONS;
1890 ASSERT([event type] == NSKeyDown || [event type] == NSKeyUp);
1892 // Check for cases where we are too early for events -- possible unmatched key up
1893 // from pressing return in the location bar.
1894 DocumentImpl *doc = xmlDocImpl();
1898 NodeImpl *node = doc->focusNode();
1906 if ([event type] == NSKeyDown) {
1907 prepareForUserAction();
1910 NSEvent *oldCurrentEvent = _currentEvent;
1911 _currentEvent = KWQRetain(event);
1913 QKeyEvent qEvent(event);
1914 bool result = !node->dispatchKeyEvent(&qEvent);
1916 // We want to send both a down and a press for the initial key event.
1917 // To get KHTML to do this, we send a second KeyPress QKeyEvent with "is repeat" set to true,
1918 // which causes it to send a press to the DOM.
1919 // That's not a great hack; it would be good to do this in a better way.
1920 if ([event type] == NSKeyDown && ![event isARepeat]) {
1921 QKeyEvent repeatEvent(event, true);
1922 if (!node->dispatchKeyEvent(&repeatEvent)) {
1927 ASSERT(_currentEvent == event);
1929 _currentEvent = oldCurrentEvent;
1933 KWQ_UNBLOCK_EXCEPTIONS;
1938 // This does the same kind of work that KHTMLPart::openURL does, except it relies on the fact
1939 // that a higher level already checked that the URLs match and the scrolling is the right thing to do.
1940 void KWQKHTMLPart::scrollToAnchor(const KURL &URL)
1942 cancelRedirection();
1949 // It's important to model this as a load that starts and immediately finishes.
1950 // Otherwise, the parent frame may think we never finished loading.
1951 d->m_bComplete = false;
1955 bool KWQKHTMLPart::closeURL()
1957 saveDocumentState();
1958 return KHTMLPart::closeURL();
1961 void KWQKHTMLPart::khtmlMousePressEvent(MousePressEvent *event)
1963 bool singleClick = [_currentEvent clickCount] <= 1;
1965 // If we got the event back, that must mean it wasn't prevented,
1966 // so it's allowed to start a drag or selection.
1967 _mouseDownMayStartSelect = true;
1968 // Careful that the drag starting logic stays in sync with eventMayStartDrag()
1969 _mouseDownMayStartDrag = singleClick;
1971 d->m_mousePressNode = event->innerNode();
1973 if (!passWidgetMouseDownEventToWidget(event)) {
1974 // We don't do this at the start of mouse down handling (before calling into WebCore),
1975 // because we don't want to do it until we know we didn't hit a widget.
1976 NSView *view = d->m_view->getDocumentView();
1979 KWQ_BLOCK_EXCEPTIONS;
1980 if ([_bridge firstResponder] != view) {
1981 [_bridge makeFirstResponder:view];
1983 KWQ_UNBLOCK_EXCEPTIONS;
1986 KHTMLPart::khtmlMousePressEvent(event);
1990 void KWQKHTMLPart::khtmlMouseDoubleClickEvent(MouseDoubleClickEvent *event)
1992 if (!passWidgetMouseDownEventToWidget(event)) {
1993 KHTMLPart::khtmlMouseDoubleClickEvent(event);
1997 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(khtml::MouseEvent *event)
1999 // Figure out which view to send the event to.
2000 RenderObject *target = event->innerNode().handle() ? event->innerNode().handle()->renderer() : 0;
2004 QWidget* widget = RenderLayer::gScrollBar;
2006 if (!target->isWidget())
2008 widget = static_cast<RenderWidget *>(target)->widget();
2011 // Doubleclick events don't exist in Cocoa. Since passWidgetMouseDownEventToWidget will
2012 // just pass _currentEvent down to the widget, we don't want to call it for events that
2013 // don't correspond to Cocoa events. The mousedown/ups will have already been passed on as
2014 // part of the pressed/released handling.
2015 if (!MouseDoubleClickEvent::test(event))
2016 return passWidgetMouseDownEventToWidget(widget);
2021 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(RenderWidget *renderWidget)
2023 return passWidgetMouseDownEventToWidget(renderWidget->widget());
2026 bool KWQKHTMLPart::passWidgetMouseDownEventToWidget(QWidget* widget)
2028 // FIXME: this method always returns true
2031 ERROR("hit a RenderWidget without a corresponding QWidget, means a frame is half-constructed");
2035 KWQ_BLOCK_EXCEPTIONS;
2037 NSView *nodeView = widget->getView();
2039 ASSERT([nodeView superview]);
2040 NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[_currentEvent locationInWindow] fromView:nil]];
2042 ERROR("KHTML says we hit a RenderWidget, but AppKit doesn't agree we hit the corresponding NSView");
2046 if ([_bridge firstResponder] == view) {
2047 // In the case where we just became first responder, we should send the mouseDown:
2048 // to the NSTextField, not the NSTextField's editor. This code makes sure that happens.
2049 // If we don't do this, we see a flash of selected text when clicking in a text field.
2050 if (![_bridge wasFirstResponderAtMouseDownTime:view] && [view isKindOfClass:[NSTextView class]]) {
2051 NSView *superview = view;
2052 while (superview != nodeView) {
2053 superview = [superview superview];
2055 if ([superview isKindOfClass:[NSControl class]]) {
2056 NSControl *control = superview;
2057 if ([control currentEditor] == view) {
2065 // Normally [NSWindow sendEvent:] handles setting the first responder.
2066 // But in our case, the event was sent to the view representing the entire web page.
2067 if ([_currentEvent clickCount] <= 1 && [view acceptsFirstResponder] && [view needsPanelToBecomeKey]) {
2068 [_bridge makeFirstResponder:view];
2072 // We need to "defer loading" and defer timers while we are tracking the mouse.
2073 // That's because we don't want the new page to load while the user is holding the mouse down.
2075 BOOL wasDeferringLoading = [_bridge defersLoading];
2076 if (!wasDeferringLoading) {
2077 [_bridge setDefersLoading:YES];
2079 BOOL wasDeferringTimers = QObject::defersTimers();
2080 if (!wasDeferringTimers) {
2081 QObject::setDefersTimers(true);
2084 ASSERT(!_sendingEventToSubview);
2085 _sendingEventToSubview = true;
2086 [view mouseDown:_currentEvent];
2087 _sendingEventToSubview = false;
2089 if (!wasDeferringTimers) {
2090 QObject::setDefersTimers(false);
2092 if (!wasDeferringLoading) {
2093 [_bridge setDefersLoading:NO];
2096 // Remember which view we sent the event to, so we can direct the release event properly.
2097 _mouseDownView = view;
2098 _mouseDownWasInSubframe = false;
2100 KWQ_UNBLOCK_EXCEPTIONS;
2105 bool KWQKHTMLPart::lastEventIsMouseUp() const
2107 // Many AK widgets run their own event loops and consume events while the mouse is down.
2108 // When they finish, currentEvent is the mouseUp that they exited on. We need to update
2109 // the khtml state with this mouseUp, which khtml never saw. This method lets us detect
2112 KWQ_BLOCK_EXCEPTIONS;
2113 NSEvent *currentEventAfterHandlingMouseDown = [NSApp currentEvent];
2114 if (_currentEvent != currentEventAfterHandlingMouseDown) {
2115 if ([currentEventAfterHandlingMouseDown type] == NSLeftMouseUp) {
2119 KWQ_UNBLOCK_EXCEPTIONS;
2124 // Note that this does the same kind of check as [target isDescendantOf:superview].
2125 // There are two differences: This is a lot slower because it has to walk the whole
2126 // tree, and this works in cases where the target has already been deallocated.
2127 static bool findViewInSubviews(NSView *superview, NSView *target)
2129 KWQ_BLOCK_EXCEPTIONS;
2130 NSEnumerator *e = [[superview subviews] objectEnumerator];
2132 while ((subview = [e nextObject])) {
2133 if (subview == target || findViewInSubviews(subview, target)) {
2137 KWQ_UNBLOCK_EXCEPTIONS;
2142 NSView *KWQKHTMLPart::mouseDownViewIfStillGood()
2144 // Since we have no way of tracking the lifetime of _mouseDownView, we have to assume that
2145 // it could be deallocated already. We search for it in our subview tree; if we don't find
2146 // it, we set it to nil.
2147 NSView *mouseDownView = _mouseDownView;
2148 if (!mouseDownView) {
2151 KHTMLView *topKHTMLView = d->m_view;
2152 NSView *topView = topKHTMLView ? topKHTMLView->getView() : nil;
2153 if (!topView || !findViewInSubviews(topView, mouseDownView)) {
2154 _mouseDownView = nil;
2157 return mouseDownView;
2160 // The link drag hysteresis is much larger than the others because there
2161 // needs to be enough space to cancel the link press without starting a link drag,
2162 // and because dragging links is rare.
2163 #define LinkDragHysteresis 40.0
2164 #define ImageDragHysteresis 5.0
2165 #define TextDragHysteresis 3.0
2166 #define GeneralDragHysteresis 3.0
2168 #define TextDragDelay 0.15
2170 bool KWQKHTMLPart::dragHysteresisExceeded(float dragLocationX, float dragLocationY) const
2173 d->m_view->viewportToContents((int)dragLocationX, (int)dragLocationY, dragX, dragY);
2174 float deltaX = QABS(dragX - _mouseDownX);
2175 float deltaY = QABS(dragY - _mouseDownY);
2177 float threshold = GeneralDragHysteresis;
2178 if (_dragSrcIsImage) {
2179 threshold = ImageDragHysteresis;
2180 } else if (_dragSrcIsLink) {
2181 threshold = LinkDragHysteresis;
2182 } else if (_dragSrcInSelection) {
2183 threshold = TextDragHysteresis;
2185 return deltaX >= threshold || deltaY >= threshold;
2188 // returns if we should continue "default processing", i.e., whether eventhandler canceled
2189 bool KWQKHTMLPart::dispatchDragSrcEvent(int eventId, const QPoint &loc) const
2191 bool noDefaultProc = d->m_view->dispatchDragEvent(eventId, _dragSrc.handle(), loc, _dragClipboard);
2192 return !noDefaultProc;
2195 bool KWQKHTMLPart::eventMayStartDrag(NSEvent *event) const
2197 // This is a pre-flight check of whether the event might lead to a drag being started. Be careful
2198 // that its logic needs to stay in sync with khtmlMouseMoveEvent() and the way we set
2199 // _mouseDownMayStartDrag in khtmlMousePressEvent
2201 if ([event type] != NSLeftMouseDown || [event clickCount] != 1) {
2205 BOOL DHTMLFlag, UAFlag;
2206 [_bridge allowDHTMLDrag:&DHTMLFlag UADrag:&UAFlag];
2207 if (!DHTMLFlag && !UAFlag) {
2211 NSPoint loc = [event locationInWindow];
2212 int mouseDownX, mouseDownY;
2213 d->m_view->viewportToContents((int)loc.x, (int)loc.y, mouseDownX, mouseDownY);
2214 RenderObject::NodeInfo nodeInfo(true, false);
2215 renderer()->layer()->hitTest(nodeInfo, mouseDownX, mouseDownY);
2217 Node possibleSrc = nodeInfo.innerNode()->renderer()->draggableNode(DHTMLFlag, UAFlag, mouseDownX, mouseDownY, srcIsDHTML);
2218 return !possibleSrc.isNull();
2221 void KWQKHTMLPart::khtmlMouseMoveEvent(MouseMoveEvent *event)
2223 KWQ_BLOCK_EXCEPTIONS;
2225 if ([_currentEvent type] == NSLeftMouseDragged) {
2226 NSView *view = mouseDownViewIfStillGood();
2229 _sendingEventToSubview = true;
2230 [view mouseDragged:_currentEvent];
2231 _sendingEventToSubview = false;
2235 // Careful that the drag starting logic stays in sync with eventMayStartDrag()
2237 if (_mouseDownMayStartDrag && _dragSrc.isNull()) {
2238 BOOL tempFlag1, tempFlag2;
2239 [_bridge allowDHTMLDrag:&tempFlag1 UADrag:&tempFlag2];
2240 _dragSrcMayBeDHTML = tempFlag1;
2241 _dragSrcMayBeUA = tempFlag2;
2242 if (!_dragSrcMayBeDHTML && !_dragSrcMayBeUA) {
2243 _mouseDownMayStartDrag = false; // no element is draggable
2247 if (_mouseDownMayStartDrag && _dragSrc.isNull()) {
2248 // try to find an element that wants to be dragged
2249 RenderObject::NodeInfo nodeInfo(true, false);
2250 renderer()->layer()->hitTest(nodeInfo, _mouseDownX, _mouseDownY);
2251 _dragSrc = nodeInfo.innerNode()->renderer()->draggableNode(_dragSrcMayBeDHTML, _dragSrcMayBeUA, _mouseDownX, _mouseDownY, _dragSrcIsDHTML);
2252 if (_dragSrc.isNull()) {
2253 _mouseDownMayStartDrag = false; // no element is draggable
2255 // remember some facts about this source, while we have a NodeInfo handy
2256 NodeImpl *node = nodeInfo.URLElement();
2257 _dragSrcIsLink = node ? node->hasAnchor() : false;
2259 node = nodeInfo.innerNonSharedNode();
2260 _dragSrcIsImage = (node && node->renderer() && node->renderer()->isImage());
2262 _dragSrcInSelection = isPointInsideSelection(_mouseDownX, _mouseDownY);
2266 // For drags starting in the selection, the user must wait between the mousedown and mousedrag,
2267 // or else we bail on the dragging stuff and allow selection to occur
2268 if (_mouseDownMayStartDrag && _dragSrcInSelection && [_currentEvent timestamp] - _mouseDownTimestamp < TextDragDelay) {
2269 _mouseDownMayStartDrag = false;
2270 // ...but if this was the first click in the window, we don't even want to start selection
2271 if (_activationEventNumber == [_currentEvent eventNumber]) {
2272 _mouseDownMayStartSelect = false;
2276 if (_mouseDownMayStartDrag) {
2277 // We are starting a text/image/url drag, so the cursor should be an arrow
2278 d->m_view->resetCursor();
2280 NSPoint dragLocation = [_currentEvent locationInWindow];
2281 if (dragHysteresisExceeded(dragLocation.x, dragLocation.y)) {
2283 // Once we're past the hysteresis point, we don't want to treat this gesture as a click
2284 d->m_view->invalidateClick();
2286 NSImage *dragImage = nil; // we use these values if WC is out of the loop
2287 NSPoint dragLoc = NSZeroPoint;
2288 NSDragOperation srcOp = NSDragOperationNone;
2289 BOOL wcWrotePasteboard = NO;
2290 if (_dragSrcMayBeDHTML) {
2291 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
2292 // Must be done before ondragstart adds types and data to the pboard,
2293 // also done for security, as it erases data from the last drag
2294 [pasteboard declareTypes:[NSArray array] owner:nil];
2296 freeClipboard(); // would only happen if we missed a dragEnd. Do it anyway, just
2297 // to make sure it gets numbified
2298 _dragClipboard = new KWQClipboard(true, pasteboard, KWQClipboard::Writable, this);
2299 _dragClipboard->ref();
2301 // If this is drag of an element, get set up to generate a default image. Otherwise
2302 // WebKit will generate the default, the element doesn't override.
2303 if (_dragSrcIsDHTML) {
2305 _dragSrc.handle()->renderer()->absolutePosition(srcX, srcY);
2306 _dragClipboard->setDragImageElement(_dragSrc, QPoint(_mouseDownX - srcX, _mouseDownY - srcY));
2309 _mouseDownMayStartDrag = dispatchDragSrcEvent(EventImpl::DRAGSTART_EVENT, QPoint(_mouseDownWinX, _mouseDownWinY));
2310 // Invalidate clipboard here against anymore pasteboard writing for security. The drag
2311 // image can still be changed as we drag, but not the pasteboard data.
2312 _dragClipboard->setAccessPolicy(KWQClipboard::ImageWritable);
2314 if (_mouseDownMayStartDrag) {
2315 // gather values from DHTML element, if it set any
2316 _dragClipboard->sourceOperation(&srcOp);
2318 NSArray *types = [pasteboard types];
2319 wcWrotePasteboard = types && [types count] > 0;
2321 if (_dragSrcMayBeDHTML) {
2322 dragImage = _dragClipboard->dragNSImage(&dragLoc);
2325 // Yuck, dragSourceMovedTo() can be called as a result of kicking off the drag with
2326 // dragImage! Because of that dumb reentrancy, we may think we've not started the
2327 // drag when that happens. So we have to assume it's started before we kick it off.
2328 _dragClipboard->setDragHasStarted();
2332 if (_mouseDownMayStartDrag) {
2333 BOOL startedDrag = [_bridge startDraggingImage:dragImage at:dragLoc operation:srcOp event:_currentEvent sourceIsDHTML:_dragSrcIsDHTML DHTMLWroteData:wcWrotePasteboard];
2334 if (!startedDrag && _dragSrcMayBeDHTML) {
2335 // WebKit canned the drag at the last minute - we owe _dragSrc a DRAGEND event
2336 dispatchDragSrcEvent(EventImpl::DRAGEND_EVENT, QPoint(dragLocation));
2337 _mouseDownMayStartDrag = false;
2341 if (!_mouseDownMayStartDrag) {
2342 // something failed to start the drag, cleanup
2348 // No more default handling (like selection), whether we're past the hysteresis bounds or not
2351 if (!_mouseDownMayStartSelect) {
2355 // Don't allow dragging or click handling after we've started selecting.
2356 _mouseDownMayStartDrag = false;
2357 d->m_view->invalidateClick();
2359 // We use khtml's selection but our own autoscrolling.
2360 [_bridge handleAutoscrollForMouseDragged:_currentEvent];
2362 // If we allowed the other side of the bridge to handle a drag
2363 // last time, then m_bMousePressed might still be set. So we
2364 // clear it now to make sure the next move after a drag
2365 // doesn't look like a drag.
2366 d->m_bMousePressed = false;
2369 KHTMLPart::khtmlMouseMoveEvent(event);
2371 KWQ_UNBLOCK_EXCEPTIONS;
2374 void KWQKHTMLPart::dragSourceMovedTo(const QPoint &loc)
2376 if (!_dragSrc.isNull() && _dragSrcMayBeDHTML) {
2377 // for now we don't care if event handler cancels default behavior, since there is none
2378 dispatchDragSrcEvent(EventImpl::DRAG_EVENT, loc);
2382 void KWQKHTMLPart::dragSourceEndedAt(const QPoint &loc, NSDragOperation operation)
2384 if (!_dragSrc.isNull() && _dragSrcMayBeDHTML) {
2385 _dragClipboard->setDestinationOperation(operation);
2386 // for now we don't care if event handler cancels default behavior, since there is none
2387 dispatchDragSrcEvent(EventImpl::DRAGEND_EVENT, loc);
2393 // Returns whether caller should continue with "the default processing", which is the same as
2394 // the event handler NOT setting the return value to false
2395 bool KWQKHTMLPart::dispatchCPPEvent(int eventId, KWQClipboard::AccessPolicy policy)
2397 NodeImpl *target = d->m_selection.start().element();
2398 if (!target && xmlDocImpl()) {
2399 target = xmlDocImpl()->body();
2405 KWQClipboard *clipboard = new KWQClipboard(false, [NSPasteboard generalPasteboard], (KWQClipboard::AccessPolicy)policy);
2408 int exceptioncode = 0;
2409 EventImpl *evt = new ClipboardEventImpl(static_cast<EventImpl::EventId>(eventId), true, true, clipboard);
2411 target->dispatchEvent(evt, exceptioncode, true);
2412 bool noDefaultProcessing = evt->defaultPrevented();
2415 // invalidate clipboard here for security
2416 clipboard->setAccessPolicy(KWQClipboard::Numb);
2419 return !noDefaultProcessing;
2422 // WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They
2423 // also send onbeforecopy, apparently for symmetry, but it doesn't affect the menu items.
2424 // We need to use onbeforecopy as a real menu enabler because we allow elements that are not
2425 // normally selectable to implement copy/paste (like divs, or a document body).
2427 bool KWQKHTMLPart::mayCut()
2429 return !dispatchCPPEvent(EventImpl::BEFORECUT_EVENT, KWQClipboard::Numb);
2432 bool KWQKHTMLPart::mayCopy()
2434 return !dispatchCPPEvent(EventImpl::BEFORECOPY_EVENT, KWQClipboard::Numb);
2437 bool KWQKHTMLPart::mayPaste()
2439 return !dispatchCPPEvent(EventImpl::BEFOREPASTE_EVENT, KWQClipboard::Numb);
2442 bool KWQKHTMLPart::tryCut()
2444 // Must be done before oncut adds types and data to the pboard,
2445 // also done for security, as it erases data from the last copy/paste.
2446 [[NSPasteboard generalPasteboard] declareTypes:[NSArray array] owner:nil];
2448 return !dispatchCPPEvent(EventImpl::CUT_EVENT, KWQClipboard::Writable);
2451 bool KWQKHTMLPart::tryCopy()
2453 // Must be done before oncopy adds types and data to the pboard,
2454 // also done for security, as it erases data from the last copy/paste.
2455 [[NSPasteboard generalPasteboard] declareTypes:[NSArray array] owner:nil];
2457 return !dispatchCPPEvent(EventImpl::COPY_EVENT, KWQClipboard::Writable);
2460 bool KWQKHTMLPart::tryPaste()
2462 return !dispatchCPPEvent(EventImpl::PASTE_EVENT, KWQClipboard::Readable);
2465 void KWQKHTMLPart::khtmlMouseReleaseEvent(MouseReleaseEvent *event)
2467 NSView *view = mouseDownViewIfStillGood();
2469 // If this was the first click in the window, we don't even want to clear the selection.
2470 // This case occurs when the user clicks on a draggable element, since we have to process
2471 // the mouse down and drag events to see if we might start a drag. For other first clicks
2472 // in a window, we just don't acceptFirstMouse, and the whole down-drag-up sequence gets
2473 // ignored upstream of this layer.
2474 if (_activationEventNumber != [_currentEvent eventNumber]) {
2475 KHTMLPart::khtmlMouseReleaseEvent(event);
2480 _sendingEventToSubview = true;
2481 KWQ_BLOCK_EXCEPTIONS;
2482 [view mouseUp:_currentEvent];
2483 KWQ_UNBLOCK_EXCEPTIONS;
2484 _sendingEventToSubview = false;
2487 void KWQKHTMLPart::clearTimers(KHTMLView *view)
2490 view->unscheduleRelayout();
2492 DocumentImpl* document = view->part()->xmlDocImpl();
2493 if (document && document->renderer() && document->renderer()->layer())
2494 document->renderer()->layer()->suspendMarquees();
2499 void KWQKHTMLPart::clearTimers()
2501 clearTimers(d->m_view);
2504 bool KWQKHTMLPart::passSubframeEventToSubframe(NodeImpl::MouseEvent &event)
2506 KWQ_BLOCK_EXCEPTIONS;
2508 switch ([_currentEvent type]) {
2509 case NSLeftMouseDown: {
2510 NodeImpl *node = event.innerNode.handle();
2514 RenderPart *renderPart = dynamic_cast<RenderPart *>(node->renderer());
2518 if (!passWidgetMouseDownEventToWidget(renderPart)) {
2521 _mouseDownWasInSubframe = true;
2524 case NSLeftMouseUp: {
2525 if (!_mouseDownWasInSubframe) {
2528 NSView *view = mouseDownViewIfStillGood();
2532 ASSERT(!_sendingEventToSubview);
2533 _sendingEventToSubview = true;
2534 [view mouseUp:_currentEvent];
2535 _sendingEventToSubview = false;
2538 case NSLeftMouseDragged: {
2539 if (!_mouseDownWasInSubframe) {
2542 NSView *view = mouseDownViewIfStillGood();
2546 ASSERT(!_sendingEventToSubview);
2547 _sendingEventToSubview = true;
2548 [view mouseDragged:_currentEvent];
2549 _sendingEventToSubview = false;
2555 KWQ_UNBLOCK_EXCEPTIONS;
2560 void KWQKHTMLPart::mouseDown(NSEvent *event)
2562 KHTMLView *v = d->m_view;
2563 if (!v || _sendingEventToSubview) {
2567 KWQ_BLOCK_EXCEPTIONS;
2569 prepareForUserAction();
2571 _mouseDownView = nil;
2574 NSEvent *oldCurrentEvent = _currentEvent;
2575 _currentEvent = KWQRetain(event);
2576 NSPoint loc = [event locationInWindow];
2577 _mouseDownWinX = (int)loc.x;
2578 _mouseDownWinY = (int)loc.y;
2579 d->m_view->viewportToContents(_mouseDownWinX, _mouseDownWinY, _mouseDownX, _mouseDownY);
2580 _mouseDownTimestamp = [event timestamp];
2582 _mouseDownMayStartDrag = false;
2583 _mouseDownMayStartSelect = false;
2585 QMouseEvent kEvent(QEvent::MouseButtonPress, event);
2586 v->viewportMousePressEvent(&kEvent);
2588 ASSERT(_currentEvent == event);
2590 _currentEvent = oldCurrentEvent;
2592 KWQ_UNBLOCK_EXCEPTIONS;
2595 void KWQKHTMLPart::mouseDragged(NSEvent *event)
2597 KHTMLView *v = d->m_view;
2598 if (!v || _sendingEventToSubview) {
2602 KWQ_BLOCK_EXCEPTIONS;
2604 NSEvent *oldCurrentEvent = _currentEvent;
2605 _currentEvent = KWQRetain(event);
2607 QMouseEvent kEvent(QEvent::MouseMove, event);
2608 v->viewportMouseMoveEvent(&kEvent);
2610 ASSERT(_currentEvent == event);
2612 _currentEvent = oldCurrentEvent;
2614 KWQ_UNBLOCK_EXCEPTIONS;
2617 void KWQKHTMLPart::mouseUp(NSEvent *event)
2619 KHTMLView *v = d->m_view;
2620 if (!v || _sendingEventToSubview) {
2624 KWQ_BLOCK_EXCEPTIONS;
2626 NSEvent *oldCurrentEvent = _currentEvent;
2627 _currentEvent = KWQRetain(event);
2629 // Our behavior here is a little different that Qt. Qt always sends
2630 // a mouse release event, even for a double click. To correct problems
2631 // in khtml's DOM click event handling we do not send a release here
2632 // for a double click. Instead we send that event from KHTMLView's
2633 // viewportMouseDoubleClickEvent. Note also that the third click of
2634 // a triple click is treated as a single click, but the fourth is then
2635 // treated as another double click. Hence the "% 2" below.
2636 int clickCount = [event clickCount];
2637 if (clickCount > 0 && clickCount % 2 == 0) {
2638 QMouseEvent doubleClickEvent(QEvent::MouseButtonDblClick, event);
2639 v->viewportMouseDoubleClickEvent(&doubleClickEvent);
2641 QMouseEvent releaseEvent(QEvent::MouseButtonRelease, event);
2642 v->viewportMouseReleaseEvent(&releaseEvent);
2645 ASSERT(_currentEvent == event);
2647 _currentEvent = oldCurrentEvent;
2649 _mouseDownView = nil;
2651 KWQ_UNBLOCK_EXCEPTIONS;
2655 A hack for the benefit of AK's PopUpButton, which uses the Carbon menu manager, which thus
2656 eats all subsequent events after it is starts its modal tracking loop. After the interaction
2657 is done, this routine is used to fix things up. When a mouse down started us tracking in
2658 the widget, we post a fake mouse up to balance the mouse down we started with. When a
2659 key down started us tracking in the widget, we post a fake key up to balance things out.
2660 In addition, we post a fake mouseMoved to get the cursor in sync with whatever we happen to
2661 be over after the tracking is done.
2663 void KWQKHTMLPart::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent)
2665 KWQ_BLOCK_EXCEPTIONS;
2667 _sendingEventToSubview = false;
2668 int eventType = [initiatingEvent type];
2669 ASSERT(eventType == NSLeftMouseDown || eventType == NSKeyDown);
2670 NSEvent *fakeEvent = nil;
2671 if (eventType == NSLeftMouseDown) {
2672 fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
2673 location:[initiatingEvent locationInWindow]
2674 modifierFlags:[initiatingEvent modifierFlags]
2675 timestamp:[initiatingEvent timestamp]
2676 windowNumber:[initiatingEvent windowNumber]
2677 context:[initiatingEvent context]
2678 eventNumber:[initiatingEvent eventNumber]
2679 clickCount:[initiatingEvent clickCount]
2680 pressure:[initiatingEvent pressure]];
2684 else { // eventType == NSKeyDown
2685 fakeEvent = [NSEvent keyEventWithType:NSKeyUp
2686 location:[initiatingEvent locationInWindow]
2687 modifierFlags:[initiatingEvent modifierFlags]
2688 timestamp:[initiatingEvent timestamp]
2689 windowNumber:[initiatingEvent windowNumber]
2690 context:[initiatingEvent context]
2691 characters:[initiatingEvent characters]
2692 charactersIgnoringModifiers:[initiatingEvent charactersIgnoringModifiers]
2693 isARepeat:[initiatingEvent isARepeat]
2694 keyCode:[initiatingEvent keyCode]];
2695 keyEvent(fakeEvent);
2697 // FIXME: We should really get the current modifierFlags here, but there's no way to poll
2698 // them in Cocoa, and because the event stream was stolen by the Carbon menu code we have
2699 // no up-to-date cache of them anywhere.
2700 fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
2701 location:[[_bridge window] convertScreenToBase:[NSEvent mouseLocation]]
2702 modifierFlags:[initiatingEvent modifierFlags]
2703 timestamp:[initiatingEvent timestamp]
2704 windowNumber:[initiatingEvent windowNumber]
2705 context:[initiatingEvent context]
2709 mouseMoved(fakeEvent);
2711 KWQ_UNBLOCK_EXCEPTIONS;
2714 void KWQKHTMLPart::mouseMoved(NSEvent *event)
2716 KHTMLView *v = d->m_view;
2717 // Reject a mouse moved if the button is down - screws up tracking during autoscroll
2718 // These happen because WebKit sometimes has to fake up moved events.
2719 if (!v || d->m_bMousePressed) {
2723 KWQ_BLOCK_EXCEPTIONS;
2725 NSEvent *oldCurrentEvent = _currentEvent;
2726 _currentEvent = KWQRetain(event);
2728 QMouseEvent kEvent(QEvent::MouseMove, event);
2729 v->viewportMouseMoveEvent(&kEvent);
2731 ASSERT(_currentEvent == event);
2733 _currentEvent = oldCurrentEvent;
2735 KWQ_UNBLOCK_EXCEPTIONS;
2738 // Called as we walk up the element chain for nodes with CSS property -khtml-user-drag == auto
2739 bool KWQKHTMLPart::shouldDragAutoNode(DOM::NodeImpl* node, int x, int y) const
2741 // We assume that WebKit only cares about dragging things that can be leaf nodes (text, images, urls).
2742 // This saves a bunch of expensive calls (creating WC and WK element dicts) as we walk farther up
2743 // the node hierarchy, and we also don't have to cook up a way to ask WK about non-leaf nodes
2744 // (since right now WK just hit-tests using a cached lastMouseDown).
2745 if (!node->hasChildNodes() && d->m_view) {
2746 int windowX, windowY;
2747 d->m_view->contentsToViewport(x, y, windowX, windowY);
2748 NSPoint eventLoc = {windowX, windowY};
2749 return [_bridge mayStartDragAtEventLocation:eventLoc];
2755 bool KWQKHTMLPart::sendContextMenuEvent(NSEvent *event)
2757 DocumentImpl *doc = d->m_doc;
2758 KHTMLView *v = d->m_view;
2763 KWQ_BLOCK_EXCEPTIONS;
2765 NSEvent *oldCurrentEvent = _currentEvent;
2766 _currentEvent = KWQRetain(event);
2768 QMouseEvent qev(QEvent::MouseButtonPress, event);
2771 v->viewportToContents(qev.x(), qev.y(), xm, ym);
2773 NodeImpl::MouseEvent mev(qev.stateAfter(), NodeImpl::MousePress);
2774 doc->prepareMouseEvent(false, xm, ym, &mev);
2776 bool swallowEvent = v->dispatchMouseEvent(EventImpl::CONTEXTMENU_EVENT,
2777 mev.innerNode.handle(), true, 0, &qev, true, NodeImpl::MousePress);
2778 if (!swallowEvent && ([_bridge isEditable] || mev.innerNode.handle()->isContentEditable()) && !isPointInsideSelection(xm, ym)) {
2779 selectClosestWordFromMouseEvent(&qev, mev.innerNode, xm, ym);
2782 ASSERT(_currentEvent == event);
2784 _currentEvent = oldCurrentEvent;
2786 return swallowEvent;
2788 KWQ_UNBLOCK_EXCEPTIONS;
2793 struct ListItemInfo {
2798 NSFileWrapper *KWQKHTMLPart::fileWrapperForElement(ElementImpl *e)
2800 KWQ_BLOCK_EXCEPTIONS;
2802 NSFileWrapper *wrapper = nil;
2804 AtomicString attr = e->getAttribute(ATTR_SRC);
2805 if (!attr.isEmpty()) {
2806 NSURL *URL = completeURL(attr.string()).getNSURL();
2807 wrapper = [_bridge fileWrapperForURL:URL];
2810 RenderImage *renderer = static_cast<RenderImage *>(e->renderer());
2811 NSImage *image = renderer->pixmap().image();
2812 NSData *tiffData = [image TIFFRepresentationUsingCompression:NSTIFFCompressionLZW factor:0.0];
2813 wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:tiffData];
2814 [wrapper setPreferredFilename:@"image.tiff"];
2815 [wrapper autorelease];
2820 KWQ_UNBLOCK_EXCEPTIONS;
2825 static ElementImpl *listParent(ElementImpl *item)
2827 // Ick! Avoid use of item->id() which confuses ObjC++.
2828 unsigned short _id = Node(item).elementId();
2830 while (_id != ID_UL && _id != ID_OL) {
2831 item = static_cast<ElementImpl *>(item->parentNode());
2834 _id = Node(item).elementId();
2839 static NodeImpl* isTextFirstInListItem(NodeImpl *e)
2841 if (Node(e).nodeType() != Node::TEXT_NODE)
2843 NodeImpl* par = e->parentNode();
2845 if (par->firstChild() != e)
2847 if (Node(par).elementId() == ID_LI)
2850 par = par->parentNode();
2855 // FIXME: This collosal function needs to be refactored into maintainable smaller bits.
2857 #define BULLET_CHAR 0x2022
2858 #define SQUARE_CHAR 0x25AA
2859 #define CIRCLE_CHAR 0x25E6
2861 NSAttributedString *KWQKHTMLPart::attributedString(NodeImpl *_start, int startOffset, NodeImpl *endNode, int endOffset)
2863 KWQ_BLOCK_EXCEPTIONS;
2865 NodeImpl * _startNode = _start;
2867 if (_startNode == nil) {
2871 NSMutableAttributedString *result = [[[NSMutableAttributedString alloc] init] autorelease];
2873 bool hasNewLine = true;
2874 bool addedSpace = true;
2875 NSAttributedString *pendingStyledSpace = nil;
2876 bool hasParagraphBreak = true;
2877 const ElementImpl *linkStartNode = 0;
2878 unsigned linkStartLocation = 0;
2879 QPtrList<ElementImpl> listItems;
2880 QValueList<ListItemInfo> listItemLocations;
2881 float maxMarkerWidth = 0;
2883 Node n = _startNode;
2885 // If the first item is the entire text of a list item, use the list item node as the start of the
2886 // selection, not the text node. The user's intent was probably to select the list.
2887 if (n.nodeType() == Node::TEXT_NODE && startOffset == 0) {
2888 NodeImpl *startListNode = isTextFirstInListItem(_startNode);
2890 _startNode = startListNode;
2895 while (!n.isNull()) {
2896 RenderObject *renderer = n.handle()->renderer();
2898 RenderStyle *style = renderer->style();
2899 NSFont *font = style->font().getNSFont();
2900 bool needSpace = pendingStyledSpace != nil;
2901 if (n.nodeType() == Node::TEXT_NODE) {
2905 [pendingStyledSpace release];
2906 pendingStyledSpace = nil;
2910 QString str = n.nodeValue().string();
2911 int start = (n == _startNode) ? startOffset : -1;
2912 int end = (n == endNode) ? endOffset : -1;
2913 if (renderer->isText()) {
2914 if (renderer->style()->whiteSpace() == PRE) {
2915 if (needSpace && !addedSpace) {
2916 if (text.isEmpty() && linkStartLocation == [result length]) {
2917 ++linkStartLocation;
2919 [result appendAttributedString:pendingStyledSpace];
2921 int runStart = (start == -1) ? 0 : start;
2922 int runEnd = (end == -1) ? str.length() : end;
2923 text += str.mid(runStart, runEnd-runStart);
2924 [pendingStyledSpace release];
2925 pendingStyledSpace = nil;
2926 addedSpace = str[runEnd-1].direction() == QChar::DirWS;
2929 RenderText* textObj = static_cast<RenderText*>(renderer);
2930 if (!textObj->firstTextBox() && str.length() > 0 && !addedSpace) {
2931 // We have no runs, but we do have a length. This means we must be
2932 // whitespace that collapsed away at the end of a line.
2938 for (InlineTextBox* box = textObj->firstTextBox(); box; box = box->nextTextBox()) {
2939 int runStart = (start == -1) ? box->m_start : start;
2940 int runEnd = (end == -1) ? box->m_start + box->m_len : end;
2941 runEnd = kMin(runEnd, box->m_start + box->m_len);
2942 if (runStart >= box->m_start &&
2943 runStart < box->m_start + box->m_len) {
2944 if (box == textObj->firstTextBox() && box->m_start == runStart && runStart > 0) {
2945 needSpace = true; // collapsed space at the start
2947 if (needSpace && !addedSpace) {
2948 if (pendingStyledSpace != nil) {
2949 if (text.isEmpty() && linkStartLocation == [result length]) {
2950 ++linkStartLocation;
2952 [result appendAttributedString:pendingStyledSpace];
2957 QString runText = str.mid(runStart, runEnd - runStart);
2958 runText.replace('\n', ' ');
2960 int nextRunStart = box->nextTextBox() ? box->nextTextBox()->m_start : str.length(); // collapsed space between runs or at the end
2961 needSpace = nextRunStart > runEnd;
2962 [pendingStyledSpace release];
2963 pendingStyledSpace = nil;
2964 addedSpace = str[runEnd-1].direction() == QChar::DirWS;
2967 if (end != -1 && runEnd >= end)
2974 text.replace(QChar('\\'), renderer->backslashAsCurrencySymbol());
2976 if (text.length() > 0 || needSpace) {
2977 NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
2978 [attrs setObject:font forKey:NSFontAttributeName];
2979 if (style && style->color().isValid() && qAlpha(style->color().rgb()) != 0)
2980 [attrs setObject:style->color().getNSColor() forKey:NSForegroundColorAttributeName];
2981 if (style && style->backgroundColor().isValid() && qAlpha(style->backgroundColor().rgb()) != 0)
2982 [attrs setObject:style->backgroundColor().getNSColor() forKey:NSBackgroundColorAttributeName];
2984 if (text.length() > 0) {
2985 hasParagraphBreak = false;
2986 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString() attributes:attrs];
2987 [result appendAttributedString: partialString];
2988 [partialString release];
2992 [pendingStyledSpace release];
2993 pendingStyledSpace = [[NSAttributedString alloc] initWithString:@" " attributes:attrs];
2999 // This is our simple HTML -> ASCII transformation:
3001 unsigned short _id = n.elementId();
3004 // Note the start of the <a> element. We will add the NSLinkAttributeName
3005 // attribute to the attributed string when navigating to the next sibling
3007 linkStartLocation = [result length];
3008 linkStartNode = static_cast<ElementImpl*>(n.handle());
3019 ElementImpl *itemParent = listParent(static_cast<ElementImpl *>(n.handle()));
3025 listItems.append(static_cast<ElementImpl*>(n.handle()));
3027 info.start = [result length];
3029 listItemLocations.append (info);
3032 if (itemParent && renderer->isListItem()) {
3033 RenderListItem *listRenderer = static_cast<RenderListItem*>(renderer);
3035 maxMarkerWidth = MAX([font pointSize], maxMarkerWidth);
3036 switch(listRenderer->style()->listStyleType()) {
3038 listText += ((QChar)BULLET_CHAR);
3041 listText += ((QChar)CIRCLE_CHAR);
3044 listText += ((QChar)SQUARE_CHAR);
3049 QString marker = listRenderer->markerStringValue();
3051 // Use AppKit metrics. Will be rendered by AppKit.
3052 float markerWidth = [font widthOfString: marker.getNSString()];
3053 maxMarkerWidth = MAX(markerWidth, maxMarkerWidth);
3059 NSMutableDictionary *attrs;
3061 attrs = [[NSMutableDictionary alloc] init];
3062 [attrs setObject:font forKey:NSFontAttributeName];
3063 if (style && style->color().isValid())
3064 [attrs setObject:style->color().getNSColor() forKey:NSForegroundColorAttributeName];
3065 if (style && style->backgroundColor().isValid())
3066 [attrs setObject:style->backgroundColor().getNSColor() forKey:NSBackgroundColorAttributeName];
3068 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:listText.getNSString() attributes:attrs];
3070 [result appendAttributedString: partialString];
3071 [partialString release];
3106 if (!hasParagraphBreak) {
3108 hasParagraphBreak = true;
3114 if (pendingStyledSpace != nil) {
3115 if (linkStartLocation == [result length]) {
3116 ++linkStartLocation;
3118 [result appendAttributedString:pendingStyledSpace];
3119 [pendingStyledSpace release];
3120 pendingStyledSpace = nil;
3122 NSFileWrapper *fileWrapper = fileWrapperForElement(static_cast<ElementImpl *>(n.handle()));
3123 NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper];
3124 NSAttributedString *iString = [NSAttributedString attributedStringWithAttachment:attachment];
3125 [result appendAttributedString: iString];
3126 [attachment release];
3129 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()];
3130 [result appendAttributedString: partialString];
3131 [partialString release];
3138 Node next = n.firstChild();
3140 next = n.nextSibling();
3143 while (next.isNull() && !n.parentNode().isNull()) {
3148 next = n.nextSibling();
3150 unsigned short _id = n.elementId();
3153 // End of a <a> element. Create an attributed string NSLinkAttributeName attribute
3154 // for the range of the link. Note that we create the attributed string from the DOM, which
3155 // will have corrected any illegally nested <a> elements.
3156 if (linkStartNode && n.handle() == linkStartNode){
3157 DOMString href = parseURL(linkStartNode->getAttribute(ATTR_HREF));
3158 KURL kURL = KWQ(linkStartNode->getDocument()->part())->completeURL(href.string());
3160 NSURL *URL = kURL.getNSURL();
3161 [result addAttribute:NSLinkAttributeName value:URL range:NSMakeRange(linkStartLocation, [result length]-linkStartLocation)];
3175 int i, count = listItems.count();
3176 for (i = 0; i < count; i++){
3177 if (listItems.at(i) == n.handle()){
3178 listItemLocations[i].end = [result length];
3211 // An extra newline is needed at the start, not the end, of these types of tags,
3212 // so don't add another here.
3217 NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()];
3218 [result appendAttributedString:partialString];
3219 [partialString release];
3225 [pendingStyledSpace release];
3227 // Apply paragraph styles from outside in. This ensures that nested lists correctly
3228 // override their parent's paragraph style.
3230 unsigned i, count = listItems.count();
3234 #ifdef POSITION_LIST
3235 NodeImpl *containingBlock;
3236 int containingBlockX, containingBlockY;
3238 // Determine the position of the outermost containing block. All paragraph
3239 // styles and tabs should be relative to this position. So, the horizontal position of
3240 // each item in the list (in the resulting attributed string) will be relative to position
3241 // of the outermost containing block.
3243 containingBlock = _startNode;
3244 while (containingBlock->renderer()->isInline()){
3245 containingBlock = containingBlock->parentNode();
3247 containingBlock->renderer()->absolutePosition(containingBlockX, containingBlockY);
3251 for (i = 0; i < count; i++){
3252 e = listItems.at(i);
3253 info = listItemLocations[i];
3255 if (info.end < info.start)
3256 info.end = [result length];
3258 RenderObject *r = e->renderer();
3259 RenderStyle *style = r->style();
3262 NSFont *font = style->font().getNSFont();
3263 float pointSize = [font pointSize];
3265 #ifdef POSITION_LIST
3267 r->absolutePosition(rx, ry);
3268 rx -= containingBlockX;
3270 // Ensure that the text is indented at least enough to allow for the markers.
3271 rx = MAX(rx, (int)maxMarkerWidth);
3273 rx = (int)MAX(maxMarkerWidth, pointSize);
3276 // The bullet text will be right aligned at the first tab marker, followed
3277 // by a space, followed by the list item text. The space is arbitrarily
3278 // picked as pointSize*2/3. The space on the first line of the text item
3279 // is established by a left aligned tab, on subsequent lines it's established
3280 // by the head indent.
3281 NSMutableParagraphStyle *mps = [[NSMutableParagraphStyle alloc] init];
3282 [mps setFirstLineHeadIndent: 0];
3283 [mps setHeadIndent: rx];
3284 [mps setTabStops:[NSArray arrayWithObjects:
3285 [[[NSTextTab alloc] initWithType:NSRightTabStopType location:rx-(pointSize*2/3)] autorelease],
3286 [[[NSTextTab alloc] initWithType:NSLeftTabStopType location:rx] autorelease],
3288 [result addAttribute:NSParagraphStyleAttributeName value:mps range:NSMakeRange(info.start, info.end-info.start)];
3295 KWQ_UNBLOCK_EXCEPTIONS;
3300 QRect KWQKHTMLPart::selectionRect() const
3306 RenderCanvas *root = static_cast<RenderCanvas *>(xmlDocImpl()->renderer());
3311 return root->selectionRect();
3314 // returns NSRect because going through QRect would truncate any floats
3315 NSRect KWQKHTMLPart::visibleSelectionRect() const
3320 NSView *documentView = d->m_view->getDocumentView();
3321 if (!documentView) {
3324 return NSIntersectionRect(selectionRect(), [documentView visibleRect]);
3327 void KWQKHTMLPart::centerSelectionInVisibleArea() const
3329 switch (selection().state()) {
3330 case Selection::NONE:
3332 case Selection::CARET: {
3334 // passing true forces centering even if selection is already exposed
3335 view()->ensureRectVisibleCentered(selection().caretRect(), true);
3338 case Selection::RANGE:
3340 // passing true forces centering even if selection is already exposed
3341 view()->ensureRectVisibleCentered(selectionRect(), true);
3346 NSImage *KWQKHTMLPart::imageFromRect(NSRect rect) const
3348 NSView *view = d->m_view->getDocumentView();
3353 NSRect bounds = [view bounds];
3354 NSImage *resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease];
3356 KWQ_BLOCK_EXCEPTIONS;
3358 if (rect.size.width != 0 && rect.size.height != 0) {
3359 [resultImage setFlipped:YES];
3360 [resultImage lockFocus];
3362 [NSGraphicsContext saveGraphicsState];
3363 NSPoint translation = { -(NSMinX(rect) - NSMinX(bounds)), -(NSMinY(rect) - NSMinY(bounds)) };
3364 CGContextTranslateCTM((CGContext *)[[NSGraphicsContext currentContext] graphicsPort], translation.x, translation.y);
3366 // We change the coord system at the CG level, out from under the AK focus machinery, because it doesn't
3367 // work to change the coord system of a focused view. However, WebImageRenderer uses the difference
3368 // between the focused view's coord system and the window's coord system to adjust the pattern phase, and
3369 // that calc ignores our translation. So we must tell it about this extra phase offset.
3371 // Window is not flipped, we are, so y coord must be inverted when describing phase, which is a
3372 // window level notion.
3373 translation.y = -translation.y;
3374 [[WebCoreGraphicsBridge sharedBridge] setAdditionalPatternPhase:translation];
3376 [view drawRect:rect];
3378 [[WebCoreGraphicsBridge sharedBridge] setAdditionalPatternPhase:NSZeroPoint];
3379 [NSGraphicsContext restoreGraphicsState];
3381 [resultImage unlockFocus];
3382 [resultImage setFlipped:NO];
3385 KWQ_UNBLOCK_EXCEPTIONS;
3390 NSImage *KWQKHTMLPart::selectionImage() const
3392 _drawSelectionOnly = true; // invoke special drawing mode
3393 NSImage *result = imageFromRect(visibleSelectionRect());
3394 _drawSelectionOnly = false;
3398 NSImage *KWQKHTMLPart::snapshotDragImage(DOM::Node node, NSRect *imageRect, NSRect *elementRect) const
3400 RenderObject *renderer = node.handle()->renderer();
3405 renderer->updateDragState(true); // mark dragged nodes (so they pick up the right CSS)
3406 d->m_doc->updateLayout(); // forces style recalc - needed since changing the drag state might
3407 // imply new styles, plus JS could have changed other things
3409 NSRect paintingRect = renderer->paintingRootRect(topLevelRect);
3411 _elementToDraw = node; // invoke special sub-tree drawing mode
3412 NSImage *result = imageFromRect(paintingRect);
3413 renderer->updateDragState(false);
3417 *elementRect = topLevelRect;
3420 *imageRect = paintingRect;
3425 RenderStyle *KWQKHTMLPart::styleForSelectionStart(NodeImpl *&nodeToRemove) const
3431 if (d->m_selection.isNone())
3434 Position pos = VisiblePosition(d->m_selection.start(), UPSTREAM).deepEquivalent();
3435 if (!pos.inRenderedContent())
3437 NodeImpl *node = pos.node();
3441 if (!d->m_typingStyle)
3442 return node->renderer()->style();
3444 int exceptionCode = 0;
3445 ElementImpl *styleElement = xmlDocImpl()->createHTMLElement("span", exceptionCode);
3446 ASSERT(exceptionCode == 0);
3448 styleElement->setAttribute(ATTR_STYLE, d->m_typingStyle->cssText().implementation(), exceptionCode);
3449 ASSERT(exceptionCode == 0);
3451 TextImpl *text = xmlDocImpl()->createEditingTextNode("");
3452 styleElement->appendChild(text, exceptionCode);
3453 ASSERT(exceptionCode == 0);
3455 node->parentNode()->appendChild(styleElement, exceptionCode);
3456 ASSERT(exceptionCode == 0);
3458 nodeToRemove = styleElement;
3459 return styleElement->renderer()->style();
3462 NSFont *KWQKHTMLPart::fontForSelection(bool *hasMultipleFonts) const
3464 if (hasMultipleFonts)
3465 *hasMultipleFonts = false;