| /* |
| * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "MacFrame.h" |
| |
| #import "BrowserExtensionMac.h" |
| #import "Cache.h" |
| #import "Cursor.h" |
| #import "DOMInternal.h" |
| #import "EventNames.h" |
| #import "FloatRect.h" |
| #import "FoundationExtras.h" |
| #import "FramePrivate.h" |
| #import "FrameView.h" |
| #import "GraphicsContext.h" |
| #import "HTMLFormElementImpl.h" |
| #import "HTMLGenericFormElementImpl.h" |
| #import "InlineTextBox.h" |
| #import "KWQAccObjectCache.h" |
| #import "KWQClipboard.h" |
| #import "KWQEditCommand.h" |
| #import "KWQExceptions.h" |
| #import "KWQFormData.h" |
| #import "TransferJob.h" |
| #import "Logging.h" |
| #import "KWQPageState.h" |
| #import "KWQRegExp.h" |
| #import "KWQScrollBar.h" |
| #import "TextEncoding.h" |
| #import "KeyEvent.h" |
| #import "MouseEvent.h" |
| #import "MouseEventWithHitTestResults.h" |
| #import "Plugin.h" |
| #import "RenderTableCell.h" |
| #import "SelectionController.h" |
| #import "VisiblePosition.h" |
| #import "WebCoreFrameBridge.h" |
| #import "WebCoreGraphicsBridge.h" |
| #import "WebCoreViewFactory.h" |
| #import "WebDashboardRegion.h" |
| #import "WheelEvent.h" |
| #import "css_computedstyle.h" |
| #import "csshelper.h" |
| #import "dom2_eventsimpl.h" |
| #import "dom2_rangeimpl.h" |
| #import "dom_position.h" |
| #import "html_documentimpl.h" |
| #import "html_tableimpl.h" |
| #import "kjs_binding.h" |
| #import "kjs_window.h" |
| #import "render_canvas.h" |
| #import "render_frames.h" |
| #import "render_image.h" |
| #import "render_list.h" |
| #import "render_style.h" |
| #import "render_theme.h" |
| #import "visible_text.h" |
| #import "visible_units.h" |
| #import <JavaScriptCore/NP_jsobject.h> |
| #import <JavaScriptCore/WebScriptObjectPrivate.h> |
| #import <JavaScriptCore/identifier.h> |
| #import <JavaScriptCore/interpreter.h> |
| #import <JavaScriptCore/npruntime_impl.h> |
| #import <JavaScriptCore/property_map.h> |
| #import <JavaScriptCore/runtime.h> |
| #import <JavaScriptCore/runtime_root.h> |
| |
| #undef _KWQ_TIMING |
| |
| @interface NSObject (WebPlugIn) |
| - (id)objectForWebScript; |
| - (void *)pluginScriptableObject; |
| @end |
| |
| using namespace KJS; |
| using namespace Bindings; |
| |
| using namespace KIO; |
| |
| namespace WebCore { |
| |
| using namespace EventNames; |
| using namespace HTMLNames; |
| |
| NSEvent *MacFrame::_currentEvent = nil; |
| |
| bool FrameView::isFrameView() const |
| { |
| return true; |
| } |
| |
| MacFrame::MacFrame(Page* page, RenderPart* ownerRenderer) |
| : Frame(page, ownerRenderer) |
| , _bridge(nil) |
| , _mouseDownView(nil) |
| , _sendingEventToSubview(false) |
| , _mouseDownMayStartDrag(false) |
| , _mouseDownMayStartSelect(false) |
| , _activationEventNumber(0) |
| , _formValuesAboutToBeSubmitted(nil) |
| , _formAboutToBeSubmitted(nil) |
| , _bindingRoot(0) |
| , _windowScriptObject(0) |
| , _windowScriptNPObject(0) |
| { |
| mutableInstances().prepend(this); |
| |
| d->m_extension = new BrowserExtensionMac(this); |
| } |
| |
| MacFrame::~MacFrame() |
| { |
| setView(0); |
| mutableInstances().remove(this); |
| freeClipboard(); |
| clearRecordedFormValues(); |
| |
| [_bridge clearFrame]; |
| KWQRelease(_bridge); |
| _bridge = nil; |
| } |
| |
| void MacFrame::freeClipboard() |
| { |
| if (_dragClipboard) |
| _dragClipboard->setAccessPolicy(KWQClipboard::Numb); |
| } |
| |
| bool MacFrame::openURL(const KURL &url) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| // FIXME: The lack of args here to get the reload flag from |
| // indicates a problem in how we use Frame::processObjectRequest, |
| // where we are opening the URL before the args are set up. |
| [_bridge loadURL:url.getNSURL() |
| referrer:[_bridge referrer] |
| reload:NO |
| userGesture:userGestureHint() |
| target:nil |
| triggeringEvent:nil |
| form:nil |
| formValues:nil]; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return true; |
| } |
| |
| void MacFrame::openURLRequest(const ResourceRequest& request) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| NSString *referrer; |
| DOMString argsReferrer = request.referrer(); |
| if (!argsReferrer.isEmpty()) |
| referrer = argsReferrer; |
| else |
| referrer = [_bridge referrer]; |
| |
| [_bridge loadURL:request.url().getNSURL() |
| referrer:referrer |
| reload:request.reload |
| userGesture:userGestureHint() |
| target:request.frameName.getNSString() |
| triggeringEvent:nil |
| form:nil |
| formValues:nil]; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| |
| // Either get cached regexp or build one that matches any of the labels. |
| // The regexp we build is of the form: (STR1|STR2|STRN) |
| QRegExp *regExpForLabels(NSArray *labels) |
| { |
| // All the ObjC calls in this method are simple array and string |
| // calls which we can assume do not raise exceptions |
| |
| |
| // Parallel arrays that we use to cache regExps. In practice the number of expressions |
| // that the app will use is equal to the number of locales is used in searching. |
| static const unsigned int regExpCacheSize = 4; |
| static NSMutableArray *regExpLabels = nil; |
| static QPtrList <QRegExp> regExps; |
| static QRegExp wordRegExp = QRegExp("\\w"); |
| |
| QRegExp *result; |
| if (!regExpLabels) { |
| regExpLabels = [[NSMutableArray alloc] initWithCapacity:regExpCacheSize]; |
| } |
| unsigned int cacheHit = [regExpLabels indexOfObject:labels]; |
| if (cacheHit != NSNotFound) { |
| result = regExps.at(cacheHit); |
| } else { |
| QString pattern("("); |
| unsigned int numLabels = [labels count]; |
| unsigned int i; |
| for (i = 0; i < numLabels; i++) { |
| QString label = QString::fromNSString((NSString *)[labels objectAtIndex:i]); |
| |
| bool startsWithWordChar = false; |
| bool endsWithWordChar = false; |
| if (label.length() != 0) { |
| startsWithWordChar = wordRegExp.search(label.at(0)) >= 0; |
| endsWithWordChar = wordRegExp.search(label.at(label.length() - 1)) >= 0; |
| } |
| |
| if (i != 0) { |
| pattern.append("|"); |
| } |
| // Search for word boundaries only if label starts/ends with "word characters". |
| // If we always searched for word boundaries, this wouldn't work for languages |
| // such as Japanese. |
| if (startsWithWordChar) { |
| pattern.append("\\b"); |
| } |
| pattern.append(label); |
| if (endsWithWordChar) { |
| pattern.append("\\b"); |
| } |
| } |
| pattern.append(")"); |
| result = new QRegExp(pattern, false); |
| } |
| |
| // add regexp to the cache, making sure it is at the front for LRU ordering |
| if (cacheHit != 0) { |
| if (cacheHit != NSNotFound) { |
| // remove from old spot |
| [regExpLabels removeObjectAtIndex:cacheHit]; |
| regExps.remove(cacheHit); |
| } |
| // add to start |
| [regExpLabels insertObject:labels atIndex:0]; |
| regExps.insert(0, result); |
| // trim if too big |
| if ([regExpLabels count] > regExpCacheSize) { |
| [regExpLabels removeObjectAtIndex:regExpCacheSize]; |
| QRegExp *last = regExps.last(); |
| regExps.removeLast(); |
| delete last; |
| } |
| } |
| return result; |
| } |
| |
| NSString *MacFrame::searchForLabelsAboveCell(QRegExp *regExp, HTMLTableCellElementImpl *cell) |
| { |
| RenderTableCell *cellRenderer = static_cast<RenderTableCell *>(cell->renderer()); |
| |
| if (cellRenderer && cellRenderer->isTableCell()) { |
| RenderTableCell *cellAboveRenderer = cellRenderer->table()->cellAbove(cellRenderer); |
| |
| if (cellAboveRenderer) { |
| HTMLTableCellElementImpl *aboveCell = |
| static_cast<HTMLTableCellElementImpl *>(cellAboveRenderer->element()); |
| |
| if (aboveCell) { |
| // search within the above cell we found for a match |
| for (NodeImpl *n = aboveCell->firstChild(); n; n = n->traverseNextNode(aboveCell)) { |
| if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) |
| { |
| // For each text chunk, run the regexp |
| QString nodeString = n->nodeValue().qstring(); |
| int pos = regExp->searchRev(nodeString); |
| if (pos >= 0) { |
| return nodeString.mid(pos, regExp->matchedLength()).getNSString(); |
| } |
| } |
| } |
| } |
| } |
| } |
| // Any reason in practice to search all cells in that are above cell? |
| return nil; |
| } |
| |
| NSString *MacFrame::searchForLabelsBeforeElement(NSArray *labels, ElementImpl *element) |
| { |
| QRegExp *regExp = regExpForLabels(labels); |
| // We stop searching after we've seen this many chars |
| const unsigned int charsSearchedThreshold = 500; |
| // This is the absolute max we search. We allow a little more slop than |
| // charsSearchedThreshold, to make it more likely that we'll search whole nodes. |
| const unsigned int maxCharsSearched = 600; |
| // If the starting element is within a table, the cell that contains it |
| HTMLTableCellElementImpl *startingTableCell = 0; |
| bool searchedCellAbove = false; |
| |
| // walk backwards in the node tree, until another element, or form, or end of tree |
| int unsigned lengthSearched = 0; |
| NodeImpl *n; |
| for (n = element->traversePreviousNode(); |
| n && lengthSearched < charsSearchedThreshold; |
| n = n->traversePreviousNode()) |
| { |
| if (n->hasTagName(formTag) |
| || (n->isHTMLElement() |
| && static_cast<HTMLElementImpl *>(n)->isGenericFormElement())) |
| { |
| // We hit another form element or the start of the form - bail out |
| break; |
| } else if (n->hasTagName(tdTag) && !startingTableCell) { |
| startingTableCell = static_cast<HTMLTableCellElementImpl *>(n); |
| } else if (n->hasTagName(trTag) && startingTableCell) { |
| NSString *result = searchForLabelsAboveCell(regExp, startingTableCell); |
| if (result) { |
| return result; |
| } |
| searchedCellAbove = true; |
| } else if (n->isTextNode() && n->renderer() && n->renderer()->style()->visibility() == VISIBLE) |
| { |
| // For each text chunk, run the regexp |
| QString nodeString = n->nodeValue().qstring(); |
| // add 100 for slop, to make it more likely that we'll search whole nodes |
| if (lengthSearched + nodeString.length() > maxCharsSearched) { |
| nodeString = nodeString.right(charsSearchedThreshold - lengthSearched); |
| } |
| int pos = regExp->searchRev(nodeString); |
| if (pos >= 0) { |
| return nodeString.mid(pos, regExp->matchedLength()).getNSString(); |
| } else { |
| lengthSearched += nodeString.length(); |
| } |
| } |
| } |
| |
| // If we started in a cell, but bailed because we found the start of the form or the |
| // previous element, we still might need to search the row above us for a label. |
| if (startingTableCell && !searchedCellAbove) { |
| return searchForLabelsAboveCell(regExp, startingTableCell); |
| } else { |
| return nil; |
| } |
| } |
| |
| NSString *MacFrame::matchLabelsAgainstElement(NSArray *labels, ElementImpl *element) |
| { |
| QString name = element->getAttribute(nameAttr).qstring(); |
| // Make numbers and _'s in field names behave like word boundaries, e.g., "address2" |
| name.replace(QRegExp("[[:digit:]]"), " "); |
| name.replace('_', ' '); |
| |
| QRegExp *regExp = regExpForLabels(labels); |
| // Use the largest match we can find in the whole name string |
| int pos; |
| int length; |
| int bestPos = -1; |
| int bestLength = -1; |
| int start = 0; |
| do { |
| pos = regExp->search(name, start); |
| if (pos != -1) { |
| length = regExp->matchedLength(); |
| if (length >= bestLength) { |
| bestPos = pos; |
| bestLength = length; |
| } |
| start = pos+1; |
| } |
| } while (pos != -1); |
| |
| if (bestPos != -1) { |
| return name.mid(bestPos, bestLength).getNSString(); |
| } |
| return nil; |
| } |
| |
| // Searches from the beginning of the document if nothing is selected. |
| bool MacFrame::findString(NSString *string, bool forward, bool caseFlag, bool wrapFlag) |
| { |
| QString target = QString::fromNSString(string); |
| if (target.isEmpty()) { |
| return false; |
| } |
| |
| // Initially search from the start (if forward) or end (if backward) of the selection, and search to edge of document. |
| RefPtr<RangeImpl> searchRange(rangeOfContents(document())); |
| if (selection().start().node()) { |
| if (forward) { |
| setStart(searchRange.get(), VisiblePosition(selection().start(), selection().affinity())); |
| } else { |
| setEnd(searchRange.get(), VisiblePosition(selection().end(), selection().affinity())); |
| } |
| } |
| RefPtr<RangeImpl> resultRange(findPlainText(searchRange.get(), target, forward, caseFlag)); |
| |
| // If we re-found the (non-empty) selected range, then search again starting just past the selected range. |
| if (selection().start().node() && *resultRange == *selection().toRange()) { |
| searchRange = rangeOfContents(document()); |
| if (forward) { |
| setStart(searchRange.get(), VisiblePosition(selection().end(), selection().affinity())); |
| } else { |
| setEnd(searchRange.get(), VisiblePosition(selection().start(), selection().affinity())); |
| } |
| resultRange = findPlainText(searchRange.get(), target, forward, caseFlag); |
| } |
| |
| int exception = 0; |
| |
| // if we didn't find anything and we're wrapping, search again in the entire document (this will |
| // redundantly re-search the area already searched in some cases). |
| if (resultRange->collapsed(exception) && wrapFlag) { |
| searchRange = rangeOfContents(document()); |
| resultRange = findPlainText(searchRange.get(), target, forward, caseFlag); |
| // We used to return false here if we ended up with the same range that we started with |
| // (e.g., the selection was already the only instance of this text). But we decided that |
| // this should be a success case instead, so we'll just fall through in that case. |
| } |
| |
| if (resultRange->collapsed(exception)) { |
| return false; |
| } |
| |
| setSelection(SelectionController(resultRange.get(), DOWNSTREAM)); |
| revealSelection(); |
| return true; |
| } |
| |
| void MacFrame::clearRecordedFormValues() |
| { |
| // It's safe to assume that our own classes and Foundation data |
| // structures won't raise exceptions in dealloc |
| |
| KWQRelease(_formValuesAboutToBeSubmitted); |
| _formValuesAboutToBeSubmitted = nil; |
| KWQRelease(_formAboutToBeSubmitted); |
| _formAboutToBeSubmitted = nil; |
| } |
| |
| void MacFrame::recordFormValue(const QString &name, const QString &value, HTMLFormElementImpl *element) |
| { |
| // It's safe to assume that our own classes and basic Foundation |
| // data structures won't raise exceptions |
| |
| if (!_formValuesAboutToBeSubmitted) { |
| _formValuesAboutToBeSubmitted = KWQRetainNSRelease([[NSMutableDictionary alloc] init]); |
| ASSERT(!_formAboutToBeSubmitted); |
| _formAboutToBeSubmitted = KWQRetain([DOMElement _elementWithImpl:element]); |
| } else { |
| ASSERT([_formAboutToBeSubmitted _elementImpl] == element); |
| } |
| [_formValuesAboutToBeSubmitted setObject:value.getNSString() forKey:name.getNSString()]; |
| } |
| |
| void MacFrame::submitForm(const ResourceRequest& request) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| // FIXME: We'd like to remove this altogether and fix the multiple form submission issue another way. |
| // We do not want to submit more than one form from the same page, |
| // nor do we want to submit a single form more than once. |
| // This flag prevents these from happening; not sure how other browsers prevent this. |
| // The flag is reset in each time we start handle a new mouse or key down event, and |
| // also in setView since this part may get reused for a page from the back/forward cache. |
| // The form multi-submit logic here is only needed when we are submitting a form that affects this frame. |
| // FIXME: Frame targeting is only one of the ways the submission could end up doing something other |
| // than replacing this frame's content, so this check is flawed. On the other hand, the check is hardly |
| // needed any more now that we reset _submittedFormURL on each mouse or key down event. |
| WebCoreFrameBridge *target = request.frameName.isEmpty() ? _bridge : [_bridge findFrameNamed:request.frameName.getNSString()]; |
| Frame *targetPart = [target impl]; |
| bool willReplaceThisFrame = false; |
| for (Frame *p = this; p; p = p->tree()->parent()) { |
| if (p == targetPart) { |
| willReplaceThisFrame = true; |
| break; |
| } |
| } |
| if (willReplaceThisFrame) { |
| if (_submittedFormURL == request.url()) { |
| return; |
| } |
| _submittedFormURL = request.url(); |
| } |
| |
| if (!request.doPost()) { |
| [_bridge loadURL:request.url().getNSURL() |
| referrer:[_bridge referrer] |
| reload:request.reload |
| userGesture:true |
| target:request.frameName.getNSString() |
| triggeringEvent:_currentEvent |
| form:_formAboutToBeSubmitted |
| formValues:_formValuesAboutToBeSubmitted]; |
| } else { |
| ASSERT(request.contentType().startsWith("Content-Type: ")); |
| [_bridge postWithURL:request.url().getNSURL() |
| referrer:[_bridge referrer] |
| target:request.frameName.getNSString() |
| data:arrayFromFormData(request.postData) |
| contentType:request.contentType().mid(14).getNSString() |
| triggeringEvent:_currentEvent |
| form:_formAboutToBeSubmitted |
| formValues:_formValuesAboutToBeSubmitted]; |
| } |
| clearRecordedFormValues(); |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| void MacFrame::frameDetached() |
| { |
| Frame::frameDetached(); |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| [Mac(this)->bridge() frameDetached]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| void MacFrame::urlSelected(const ResourceRequest& request) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| NSString* referrer; |
| DOMString argsReferrer = request.referrer(); |
| if (!argsReferrer.isEmpty()) |
| referrer = argsReferrer; |
| else |
| referrer = [_bridge referrer]; |
| |
| [_bridge loadURL:request.url().getNSURL() |
| referrer:referrer |
| reload:request.reload |
| userGesture:true |
| target:request.frameName.getNSString() |
| triggeringEvent:_currentEvent |
| form:nil |
| formValues:nil]; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| ObjectContentType MacFrame::objectContentType(const KURL& url, const QString& mimeType) |
| { |
| return (ObjectContentType)[_bridge determineObjectFromMIMEType:mimeType.getNSString() URL:url.getNSURL()]; |
| } |
| |
| |
| Plugin* MacFrame::createPlugin(const KURL& url, const QStringList& paramNames, const QStringList& paramValues, const QString& mimeType) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| return new Plugin(new Widget([_bridge viewForPluginWithURL:url.getNSURL() |
| attributeNames:paramNames.getNSArray() |
| attributeValues:paramValues.getNSArray() |
| MIMEType:mimeType.getNSString()])); |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| return 0; |
| } |
| |
| |
| Frame* MacFrame::createFrame(const KURL& url, const QString& name, RenderPart* renderer, const DOMString& referrer) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| BOOL allowsScrolling = YES; |
| int marginWidth = -1; |
| int marginHeight = -1; |
| if (renderer->element()->hasTagName(frameTag) || renderer->element()->hasTagName(iframeTag)) { |
| HTMLFrameElementImpl *o = static_cast<HTMLFrameElementImpl *>(renderer->element()); |
| allowsScrolling = o->scrollingMode() != ScrollBarAlwaysOff; |
| marginWidth = o->getMarginWidth(); |
| marginHeight = o->getMarginHeight(); |
| } |
| |
| WebCoreFrameBridge *childBridge = [_bridge createChildFrameNamed:name.getNSString() |
| withURL:url.getNSURL() |
| referrer:referrer |
| renderPart:renderer |
| allowsScrolling:allowsScrolling |
| marginWidth:marginWidth |
| marginHeight:marginHeight]; |
| return [childBridge impl]; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| return 0; |
| } |
| |
| void MacFrame::setView(FrameView *view) |
| { |
| // Detach the document now, so any onUnload handlers get run - if |
| // we wait until the view is destroyed, then things won't be |
| // hooked up enough for some JavaScript calls to work. |
| if (d->m_doc && view == 0) |
| d->m_doc->detach(); |
| |
| d->m_view = view; |
| |
| // Delete old PlugIn data structures |
| cleanupPluginRootObjects(); |
| _bindingRoot = 0; |
| KWQRelease(_windowScriptObject); |
| _windowScriptObject = 0; |
| |
| if (_windowScriptNPObject) { |
| // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window |
| // script object properly. |
| // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point. |
| _NPN_DeallocateObject(_windowScriptNPObject); |
| _windowScriptNPObject = 0; |
| } |
| |
| // Only one form submission is allowed per view of a part. |
| // Since this part may be getting reused as a result of being |
| // pulled from the back/forward cache, reset this flag. |
| _submittedFormURL = KURL(); |
| } |
| |
| void MacFrame::setTitle(const DOMString &title) |
| { |
| QString text = title.qstring(); |
| text.replace(QChar('\\'), backslashAsCurrencySymbol()); |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| [_bridge setTitle:text.getNSString()]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| void MacFrame::setStatusBarText(const String& status) |
| { |
| QString text = status.qstring(); |
| text.replace(QChar('\\'), backslashAsCurrencySymbol()); |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| [_bridge setStatusText:text.getNSString()]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| void MacFrame::scheduleClose() |
| { |
| if (!shouldClose()) |
| return; |
| KWQ_BLOCK_EXCEPTIONS; |
| [_bridge closeWindowSoon]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| void MacFrame::unfocusWindow() |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| [_bridge unfocusWindow]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| QString MacFrame::advanceToNextMisspelling(bool startBeforeSelection) |
| { |
| int exception = 0; |
| |
| // The basic approach is to search in two phases - from the selection end to the end of the doc, and |
| // then we wrap and search from the doc start to (approximately) where we started. |
| |
| // Start at the end of the selection, search to edge of document. Starting at the selection end makes |
| // repeated "check spelling" commands work. |
| RefPtr<RangeImpl> searchRange(rangeOfContents(document())); |
| bool startedWithSelection = false; |
| if (selection().start().node()) { |
| startedWithSelection = true; |
| if (startBeforeSelection) { |
| VisiblePosition start(selection().start(), selection().affinity()); |
| // We match AppKit's rule: Start 1 character before the selection. |
| VisiblePosition oneBeforeStart = start.previous(); |
| setStart(searchRange.get(), oneBeforeStart.isNotNull() ? oneBeforeStart : start); |
| } else |
| setStart(searchRange.get(), VisiblePosition(selection().end(), selection().affinity())); |
| } |
| |
| // If we're not in an editable node, try to find one, make that our range to work in |
| NodeImpl *editableNodeImpl = searchRange->startContainer(exception); |
| if (!editableNodeImpl->isContentEditable()) { |
| editableNodeImpl = editableNodeImpl->nextEditable(); |
| if (!editableNodeImpl) { |
| return QString(); |
| } |
| searchRange->setStartBefore(editableNodeImpl, exception); |
| startedWithSelection = false; // won't need to wrap |
| } |
| |
| // topNode defines the whole range we want to operate on |
| NodeImpl *topNode = editableNodeImpl->rootEditableElement(); |
| searchRange->setEndAfter(topNode, exception); |
| |
| // Make sure start of searchRange is not in the middle of a word. Jumping back a char and then |
| // forward by a word happens to do the trick. |
| if (startedWithSelection) { |
| VisiblePosition oneBeforeStart = startVisiblePosition(searchRange.get(), DOWNSTREAM).previous(); |
| if (oneBeforeStart.isNotNull()) { |
| setStart(searchRange.get(), endOfWord(oneBeforeStart)); |
| } // else we were already at the start of the editable node |
| } |
| |
| if (searchRange->collapsed(exception)) |
| return QString(); // nothing to search in |
| |
| // Get the spell checker if it is available |
| NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; |
| if (checker == nil) |
| return QString(); |
| |
| WordAwareIterator it(searchRange.get()); |
| bool wrapped = false; |
| |
| // We go to the end of our first range instead of the start of it, just to be sure |
| // we don't get foiled by any word boundary problems at the start. It means we might |
| // do a tiny bit more searching. |
| NodeImpl *searchEndAfterWrapNode = it.range()->endContainer(exception); |
| int searchEndAfterWrapOffset = it.range()->endOffset(exception); |
| |
| while (1) { |
| if (!it.atEnd()) { // we may be starting at the end of the doc, and already by atEnd |
| const QChar *chars = it.characters(); |
| int len = it.length(); |
| if (len > 1 || !chars[0].isSpace()) { |
| NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:(unichar *)chars length:len freeWhenDone:NO]; |
| NSRange misspelling = [checker checkSpellingOfString:chunk startingAt:0 language:nil wrap:NO inSpellDocumentWithTag:[_bridge spellCheckerDocumentTag] wordCount:NULL]; |
| [chunk release]; |
| if (misspelling.length > 0) { |
| // Build up result range and string. Note the misspelling may span many text nodes, |
| // but the CharIterator insulates us from this complexity |
| RefPtr<RangeImpl> misspellingRange(rangeOfContents(document())); |
| CharacterIterator chars(it.range().get()); |
| chars.advance(misspelling.location); |
| misspellingRange->setStart(chars.range()->startContainer(exception), chars.range()->startOffset(exception), exception); |
| QString result = chars.string(misspelling.length); |
| misspellingRange->setEnd(chars.range()->startContainer(exception), chars.range()->startOffset(exception), exception); |
| |
| setSelection(SelectionController(misspellingRange.get(), DOWNSTREAM)); |
| revealSelection(); |
| // Mark misspelling in document. |
| document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling); |
| return result; |
| } |
| } |
| |
| it.advance(); |
| } |
| if (it.atEnd()) { |
| if (wrapped || !startedWithSelection) { |
| break; // finished the second range, or we did the whole doc with the first range |
| } else { |
| // we've gone from the selection to the end of doc, now wrap around |
| wrapped = YES; |
| searchRange->setStartBefore(topNode, exception); |
| // going until the end of the very first chunk we tested is far enough |
| searchRange->setEnd(searchEndAfterWrapNode, searchEndAfterWrapOffset, exception); |
| it = WordAwareIterator(searchRange.get()); |
| } |
| } |
| } |
| |
| return QString(); |
| } |
| |
| bool MacFrame::wheelEvent(NSEvent *event) |
| { |
| FrameView *v = d->m_view.get(); |
| |
| if (v) { |
| NSEvent *oldCurrentEvent = _currentEvent; |
| _currentEvent = KWQRetain(event); |
| |
| WheelEvent qEvent(event); |
| v->viewportWheelEvent(qEvent); |
| |
| ASSERT(_currentEvent == event); |
| KWQRelease(event); |
| _currentEvent = oldCurrentEvent; |
| |
| if (qEvent.isAccepted()) |
| return true; |
| } |
| |
| // FIXME: The scrolling done here should be done in the default handlers |
| // of the elements rather than here in the part. |
| |
| KWQScrollDirection direction; |
| float multiplier; |
| float deltaX = [event deltaX]; |
| float deltaY = [event deltaY]; |
| if (deltaX < 0) { |
| direction = KWQScrollRight; |
| multiplier = -deltaX; |
| } else if (deltaX > 0) { |
| direction = KWQScrollLeft; |
| multiplier = deltaX; |
| } else if (deltaY < 0) { |
| direction = KWQScrollDown; |
| multiplier = -deltaY; |
| } else if (deltaY > 0) { |
| direction = KWQScrollUp; |
| multiplier = deltaY; |
| } else |
| return false; |
| |
| RenderObject *r = renderer(); |
| if (!r) |
| return false; |
| |
| NSPoint point = [d->m_view->getDocumentView() convertPoint:[event locationInWindow] fromView:nil]; |
| RenderObject::NodeInfo nodeInfo(true, true); |
| r->layer()->hitTest(nodeInfo, (int)point.x, (int)point.y); |
| |
| NodeImpl *node = nodeInfo.innerNode(); |
| if (!node) |
| return false; |
| |
| r = node->renderer(); |
| if (!r) |
| return false; |
| |
| return r->scroll(direction, KWQScrollWheel, multiplier); |
| } |
| |
| void MacFrame::startRedirectionTimer() |
| { |
| stopRedirectionTimer(); |
| |
| Frame::startRedirectionTimer(); |
| |
| // Don't report history navigations, just actual redirection. |
| if (d->m_scheduledRedirection != historyNavigationScheduled) { |
| NSTimeInterval interval = d->m_redirectionTimer.nextFireInterval(); |
| NSDate *fireDate = [[NSDate alloc] initWithTimeIntervalSinceNow:interval]; |
| [_bridge reportClientRedirectToURL:KURL(d->m_redirectURL).getNSURL() |
| delay:d->m_delayRedirect |
| fireDate:fireDate |
| lockHistory:d->m_redirectLockHistory |
| isJavaScriptFormAction:d->m_executingJavaScriptFormAction]; |
| [fireDate release]; |
| } |
| } |
| |
| void MacFrame::stopRedirectionTimer() |
| { |
| bool wasActive = d->m_redirectionTimer.isActive(); |
| |
| Frame::stopRedirectionTimer(); |
| |
| // Don't report history navigations, just actual redirection. |
| if (wasActive && d->m_scheduledRedirection != historyNavigationScheduled) |
| [_bridge reportClientRedirectCancelled:d->m_cancelWithLoadInProgress]; |
| } |
| |
| void MacFrame::redirectionTimerFired(Timer<Frame>* timer) |
| { |
| // Note, despite its name, we must call "redirect cancelled" even when the |
| // redirect has completed successfully. Although that may not have been our |
| // original intent, Safari depends on this behavior at the time of this writing. |
| // See Radar 4432562 and Bugzilla 7058. |
| |
| // Don't report history navigations, just actual redirection. |
| if (d->m_scheduledRedirection != historyNavigationScheduled) |
| [_bridge reportClientRedirectCancelled:d->m_cancelWithLoadInProgress]; |
| |
| Frame::redirectionTimerFired(timer); |
| } |
| |
| QString MacFrame::userAgent() const |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| return QString::fromNSString([_bridge userAgentForURL:url().getNSURL()]); |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return QString(); |
| } |
| |
| QString MacFrame::mimeTypeForFileName(const QString &fileName) const |
| { |
| NSString *ns = fileName.getNSString(); |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| return QString::fromNSString([_bridge MIMETypeForPath:ns]); |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return QString(); |
| } |
| |
| NSView *MacFrame::nextKeyViewInFrame(NodeImpl *node, KWQSelectionDirection direction) |
| { |
| DocumentImpl *doc = document(); |
| if (!doc) |
| return nil; |
| |
| for (;;) { |
| node = direction == KWQSelectingNext |
| ? doc->nextFocusNode(node) : doc->previousFocusNode(node); |
| if (!node) |
| return nil; |
| |
| RenderObject *renderer = node->renderer(); |
| if (renderer->isWidget()) { |
| RenderWidget *renderWidget = static_cast<RenderWidget *>(renderer); |
| Widget *widget = renderWidget->widget(); |
| FrameView *childFrameWidget = widget->isFrameView() ? static_cast<FrameView *>(widget) : 0; |
| NSView *view = nil; |
| if (childFrameWidget) |
| view = Mac(childFrameWidget->frame())->nextKeyViewInFrame(0, direction); |
| else if (widget) |
| view = widget->getView(); |
| if (view) |
| return view; |
| } else |
| static_cast<ElementImpl *>(node)->focus(); |
| [_bridge makeFirstResponder:[_bridge documentView]]; |
| return [_bridge documentView]; |
| } |
| } |
| |
| NSView *MacFrame::nextKeyViewInFrameHierarchy(NodeImpl *node, KWQSelectionDirection direction) |
| { |
| NSView *next = nextKeyViewInFrame(node, direction); |
| if (!next) |
| if (MacFrame *parent = Mac(tree()->parent())) |
| next = parent->nextKeyViewInFrameHierarchy(ownerElement(), direction); |
| |
| // remove focus from currently focused node if we're giving focus to another view |
| if (next && (next != [_bridge documentView])) |
| if (DocumentImpl *doc = document()) |
| doc->setFocusNode(0); |
| |
| return next; |
| } |
| |
| NSView *MacFrame::nextKeyView(NodeImpl *node, KWQSelectionDirection direction) |
| { |
| NSView * next; |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| next = nextKeyViewInFrameHierarchy(node, direction); |
| if (next) |
| return next; |
| |
| // Look at views from the top level part up, looking for a next key view that we can use. |
| |
| next = direction == KWQSelectingNext |
| ? [_bridge nextKeyViewOutsideWebFrameViews] |
| : [_bridge previousKeyViewOutsideWebFrameViews]; |
| |
| if (next) |
| return next; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| // If all else fails, make a loop by starting from 0. |
| return nextKeyViewInFrameHierarchy(0, direction); |
| } |
| |
| NSView *MacFrame::nextKeyViewForWidget(Widget *startingWidget, KWQSelectionDirection direction) |
| { |
| // Use the event filter object to figure out which RenderWidget owns this Widget and get to the DOM. |
| // Then get the next key view in the order determined by the DOM. |
| NodeImpl *node = nodeForWidget(startingWidget); |
| ASSERT(node); |
| return Mac(frameForNode(node))->nextKeyView(node, direction); |
| } |
| |
| bool MacFrame::currentEventIsMouseDownInWidget(Widget *candidate) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| switch ([[NSApp currentEvent] type]) { |
| case NSLeftMouseDown: |
| case NSRightMouseDown: |
| case NSOtherMouseDown: |
| break; |
| default: |
| return NO; |
| } |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| NodeImpl *node = nodeForWidget(candidate); |
| ASSERT(node); |
| return frameForNode(node)->d->m_view->nodeUnderMouse() == node; |
| } |
| |
| bool MacFrame::currentEventIsKeyboardOptionTab() |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| NSEvent *evt = [NSApp currentEvent]; |
| if ([evt type] != NSKeyDown) { |
| return NO; |
| } |
| |
| if (([evt modifierFlags] & NSAlternateKeyMask) == 0) { |
| return NO; |
| } |
| |
| NSString *chars = [evt charactersIgnoringModifiers]; |
| if ([chars length] != 1) |
| return NO; |
| |
| const unichar tabKey = 0x0009; |
| const unichar shiftTabKey = 0x0019; |
| unichar c = [chars characterAtIndex:0]; |
| if (c != tabKey && c != shiftTabKey) |
| return NO; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| return YES; |
| } |
| |
| bool MacFrame::handleKeyboardOptionTabInView(NSView *view) |
| { |
| if (MacFrame::currentEventIsKeyboardOptionTab()) { |
| if (([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) != 0) { |
| [[view window] selectKeyViewPrecedingView:view]; |
| } else { |
| [[view window] selectKeyViewFollowingView:view]; |
| } |
| return YES; |
| } |
| |
| return NO; |
| } |
| |
| bool MacFrame::tabsToLinks() const |
| { |
| if ([_bridge keyboardUIMode] & WebCoreKeyboardAccessTabsToLinks) |
| return !MacFrame::currentEventIsKeyboardOptionTab(); |
| else |
| return MacFrame::currentEventIsKeyboardOptionTab(); |
| } |
| |
| bool MacFrame::tabsToAllControls() const |
| { |
| WebCoreKeyboardUIMode keyboardUIMode = [_bridge keyboardUIMode]; |
| BOOL handlingOptionTab = MacFrame::currentEventIsKeyboardOptionTab(); |
| |
| // If tab-to-links is off, option-tab always highlights all controls |
| if ((keyboardUIMode & WebCoreKeyboardAccessTabsToLinks) == 0 && handlingOptionTab) { |
| return YES; |
| } |
| |
| // If system preferences say to include all controls, we always include all controls |
| if (keyboardUIMode & WebCoreKeyboardAccessFull) { |
| return YES; |
| } |
| |
| // Otherwise tab-to-links includes all controls, unless the sense is flipped via option-tab. |
| if (keyboardUIMode & WebCoreKeyboardAccessTabsToLinks) { |
| return !handlingOptionTab; |
| } |
| |
| return handlingOptionTab; |
| } |
| |
| KJS::Bindings::RootObject *MacFrame::executionContextForDOM() |
| { |
| return bindingRootObject(); |
| } |
| |
| KJS::Bindings::RootObject *MacFrame::bindingRootObject() |
| { |
| assert(d->m_bJScriptEnabled); |
| if (!_bindingRoot) { |
| JSLock lock; |
| _bindingRoot = new KJS::Bindings::RootObject(0); // The root gets deleted by JavaScriptCore. |
| KJS::JSObject *win = KJS::Window::retrieveWindow(this); |
| _bindingRoot->setRootObjectImp (win); |
| _bindingRoot->setInterpreter(jScript()->interpreter()); |
| addPluginRootObject (_bindingRoot); |
| } |
| return _bindingRoot; |
| } |
| |
| WebScriptObject *MacFrame::windowScriptObject() |
| { |
| if (!d->m_bJScriptEnabled) |
| return 0; |
| |
| if (!_windowScriptObject) { |
| KJS::JSLock lock; |
| KJS::JSObject *win = KJS::Window::retrieveWindow(this); |
| _windowScriptObject = KWQRetainNSRelease([[WebScriptObject alloc] _initWithJSObject:win originExecutionContext:bindingRootObject() executionContext:bindingRootObject()]); |
| } |
| |
| return _windowScriptObject; |
| } |
| |
| NPObject *MacFrame::windowScriptNPObject() |
| { |
| if (!_windowScriptNPObject) { |
| if (d->m_bJScriptEnabled) { |
| // JavaScript is enabled, so there is a JavaScript window object. Return an NPObject bound to the window |
| // object. |
| KJS::JSObject *win = KJS::Window::retrieveWindow(this); |
| assert(win); |
| _windowScriptNPObject = _NPN_CreateScriptObject(0, win, bindingRootObject(), bindingRootObject()); |
| } else { |
| // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object. |
| // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object. |
| _windowScriptNPObject = _NPN_CreateNoScriptObject(); |
| } |
| } |
| |
| return _windowScriptNPObject; |
| } |
| |
| void MacFrame::partClearedInBegin() |
| { |
| if (d->m_bJScriptEnabled) |
| [_bridge windowObjectCleared]; |
| } |
| |
| void MacFrame::openURLFromPageCache(KWQPageState *state) |
| { |
| // It's safe to assume none of the KWQPageState methods will raise |
| // exceptions, since KWQPageState is implemented by WebCore and |
| // does not throw |
| |
| DocumentImpl *doc = [state document]; |
| NodeImpl *mousePressNode = [state mousePressNode]; |
| KURL *kurl = [state URL]; |
| SavedProperties *windowProperties = [state windowProperties]; |
| SavedProperties *locationProperties = [state locationProperties]; |
| SavedBuiltins *interpreterBuiltins = [state interpreterBuiltins]; |
| PausedTimeouts *timeouts = [state pausedTimeouts]; |
| |
| cancelRedirection(); |
| |
| // We still have to close the previous part page. |
| closeURL(); |
| |
| d->m_bComplete = false; |
| |
| // Don't re-emit the load event. |
| d->m_bLoadEventEmitted = true; |
| |
| // delete old status bar msg's from kjs (if it _was_ activated on last URL) |
| if (d->m_bJScriptEnabled) { |
| d->m_kjsStatusBarText = QString::null; |
| d->m_kjsDefaultStatusBarText = QString::null; |
| } |
| |
| ASSERT(kurl); |
| |
| d->m_url = *kurl; |
| |
| // 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 |
| // data arrives) (Simon) |
| if (url().protocol().startsWith("http") && !url().host().isEmpty() && url().path().isEmpty()) |
| d->m_url.setPath("/"); |
| |
| // copy to m_workingURL after fixing url() above |
| d->m_workingURL = url(); |
| |
| started(); |
| |
| // -----------begin----------- |
| clear(); |
| |
| doc->setInPageCache(NO); |
| |
| d->m_bCleared = false; |
| d->m_bComplete = false; |
| d->m_bLoadEventEmitted = false; |
| d->m_referrer = url().url(); |
| |
| setView(doc->view()); |
| |
| d->m_doc = doc; |
| d->m_mousePressNode = mousePressNode; |
| d->m_decoder = doc->decoder(); |
| |
| updatePolicyBaseURL(); |
| |
| { // scope the lock |
| JSLock lock; |
| restoreWindowProperties(windowProperties); |
| restoreLocationProperties(locationProperties); |
| restoreInterpreterBuiltins(*interpreterBuiltins); |
| } |
| |
| resumeTimeouts(timeouts); |
| |
| checkCompleted(); |
| } |
| |
| WebCoreFrameBridge *MacFrame::bridgeForWidget(const Widget *widget) |
| { |
| ASSERT_ARG(widget, widget); |
| |
| MacFrame *frame = Mac(frameForWidget(widget)); |
| ASSERT(frame); |
| return frame->bridge(); |
| } |
| |
| NSView *MacFrame::documentViewForNode(NodeImpl *node) |
| { |
| WebCoreFrameBridge *bridge = Mac(frameForNode(node))->bridge(); |
| return [bridge documentView]; |
| } |
| |
| void MacFrame::saveDocumentState() |
| { |
| // Do not save doc state if the page has a password field and a form that would be submitted |
| // via https |
| if (!(d->m_doc && d->m_doc->hasPasswordField() && d->m_doc->hasSecureForm())) { |
| KWQ_BLOCK_EXCEPTIONS; |
| [_bridge saveDocumentState]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| } |
| |
| void MacFrame::restoreDocumentState() |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| [_bridge restoreDocumentState]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| QString MacFrame::incomingReferrer() const |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| return QString::fromNSString([_bridge incomingReferrer]); |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return QString(); |
| } |
| |
| void MacFrame::runJavaScriptAlert(const DOMString& message) |
| { |
| DOMString text = message; |
| text.replace(QChar('\\'), backslashAsCurrencySymbol()); |
| KWQ_BLOCK_EXCEPTIONS; |
| [_bridge runJavaScriptAlertPanelWithMessage:text]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| bool MacFrame::runJavaScriptConfirm(const DOMString& message) |
| { |
| DOMString text = message; |
| text.replace(QChar('\\'), backslashAsCurrencySymbol()); |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| return [_bridge runJavaScriptConfirmPanelWithMessage:text]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return false; |
| } |
| |
| bool MacFrame::runJavaScriptPrompt(const DOMString& prompt, const DOMString& defaultValue, DOMString& result) |
| { |
| DOMString promptText = prompt; |
| promptText.replace(QChar('\\'), backslashAsCurrencySymbol()); |
| DOMString defaultValueText = defaultValue; |
| defaultValueText.replace(QChar('\\'), backslashAsCurrencySymbol()); |
| |
| bool ok; |
| KWQ_BLOCK_EXCEPTIONS; |
| NSString *returnedText = nil; |
| |
| ok = [_bridge runJavaScriptTextInputPanelWithPrompt:prompt |
| defaultText:defaultValue returningText:&returnedText]; |
| |
| if (ok) { |
| result = DOMString(returnedText); |
| result.replace(backslashAsCurrencySymbol(), QChar('\\')); |
| } |
| |
| return ok; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return false; |
| } |
| |
| bool MacFrame::locationbarVisible() |
| { |
| return [_bridge areToolbarsVisible]; |
| } |
| |
| bool MacFrame::menubarVisible() |
| { |
| // The menubar is always on in Mac OS X UI |
| return true; |
| } |
| |
| bool MacFrame::personalbarVisible() |
| { |
| return [_bridge areToolbarsVisible]; |
| } |
| |
| bool MacFrame::statusbarVisible() |
| { |
| return [_bridge isStatusbarVisible]; |
| } |
| |
| bool MacFrame::toolbarVisible() |
| { |
| return [_bridge areToolbarsVisible]; |
| } |
| |
| void MacFrame::addMessageToConsole(const DOMString &message, unsigned lineNumber, const DOMString &sourceURL) |
| { |
| NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys: |
| (NSString *)message, @"message", |
| [NSNumber numberWithInt: lineNumber], @"lineNumber", |
| (NSString *)sourceURL, @"sourceURL", |
| NULL]; |
| [_bridge addMessageToConsole:dictionary]; |
| } |
| |
| void MacFrame::createEmptyDocument() |
| { |
| // Although it's not completely clear from the name of this function, |
| // it does nothing if we already have a document, and just creates an |
| // empty one if we have no document at all. |
| if (!d->m_doc) { |
| KWQ_BLOCK_EXCEPTIONS; |
| [_bridge loadEmptyDocumentSynchronously]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| updateBaseURLForEmptyDocument(); |
| } |
| } |
| |
| bool MacFrame::keyEvent(NSEvent *event) |
| { |
| bool result; |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| ASSERT([event type] == NSKeyDown || [event type] == NSKeyUp); |
| |
| // Check for cases where we are too early for events -- possible unmatched key up |
| // from pressing return in the location bar. |
| DocumentImpl *doc = document(); |
| if (!doc) { |
| return false; |
| } |
| NodeImpl *node = doc->focusNode(); |
| if (!node) { |
| if (doc->isHTMLDocument()) |
| node = doc->body(); |
| else |
| node = doc->documentElement(); |
| if (!node) |
| return false; |
| } |
| |
| if ([event type] == NSKeyDown) { |
| prepareForUserAction(); |
| } |
| |
| NSEvent *oldCurrentEvent = _currentEvent; |
| _currentEvent = KWQRetain(event); |
| |
| KeyEvent qEvent(event); |
| result = !EventTargetNodeCast(node)->dispatchKeyEvent(qEvent); |
| |
| // We want to send both a down and a press for the initial key event. |
| // To get KHTML to do this, we send a second KeyPress with "is repeat" set to true, |
| // which causes it to send a press to the DOM. |
| // That's not a great hack; it would be good to do this in a better way. |
| if ([event type] == NSKeyDown && ![event isARepeat]) { |
| KeyEvent repeatEvent(event, true); |
| if (!EventTargetNodeCast(node)->dispatchKeyEvent(repeatEvent)) |
| result = true; |
| } |
| |
| ASSERT(_currentEvent == event); |
| KWQRelease(event); |
| _currentEvent = oldCurrentEvent; |
| |
| return result; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return false; |
| } |
| |
| void MacFrame::khtmlMousePressEvent(const MouseEventWithHitTestResults& event) |
| { |
| bool singleClick = [_currentEvent clickCount] <= 1; |
| |
| // If we got the event back, that must mean it wasn't prevented, |
| // so it's allowed to start a drag or selection. |
| _mouseDownMayStartSelect = canMouseDownStartSelect(event.innerNode()); |
| |
| // Careful that the drag starting logic stays in sync with eventMayStartDrag() |
| _mouseDownMayStartDrag = singleClick; |
| |
| d->m_mousePressNode = event.innerNode(); |
| |
| if (!passWidgetMouseDownEventToWidget(event, false)) { |
| // We don't do this at the start of mouse down handling (before calling into WebCore), |
| // because we don't want to do it until we know we didn't hit a widget. |
| NSView *view = d->m_view->getDocumentView(); |
| |
| if (singleClick) { |
| KWQ_BLOCK_EXCEPTIONS; |
| if ([_bridge firstResponder] != view) { |
| [_bridge makeFirstResponder:view]; |
| } |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| Frame::khtmlMousePressEvent(event); |
| } |
| } |
| |
| bool MacFrame::passMouseDownEventToWidget(Widget* widget) |
| { |
| // FIXME: this method always returns true |
| |
| if (!widget) { |
| LOG_ERROR("hit a RenderWidget without a corresponding Widget, means a frame is half-constructed"); |
| return true; |
| } |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| NSView *nodeView = widget->getView(); |
| ASSERT(nodeView); |
| ASSERT([nodeView superview]); |
| NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[_currentEvent locationInWindow] fromView:nil]]; |
| if (view == nil) { |
| LOG_ERROR("KHTML says we hit a RenderWidget, but AppKit doesn't agree we hit the corresponding NSView"); |
| return true; |
| } |
| |
| if ([_bridge firstResponder] == view) { |
| // In the case where we just became first responder, we should send the mouseDown: |
| // to the NSTextField, not the NSTextField's editor. This code makes sure that happens. |
| // If we don't do this, we see a flash of selected text when clicking in a text field. |
| if (![_bridge wasFirstResponderAtMouseDownTime:view] && [view isKindOfClass:[NSTextView class]]) { |
| NSView *superview = view; |
| while (superview != nodeView) { |
| superview = [superview superview]; |
| ASSERT(superview); |
| if ([superview isKindOfClass:[NSControl class]]) { |
| NSControl *control = static_cast<NSControl *>(superview); |
| if ([control currentEditor] == view) { |
| view = superview; |
| } |
| break; |
| } |
| } |
| } |
| } else { |
| // Normally [NSWindow sendEvent:] handles setting the first responder. |
| // But in our case, the event was sent to the view representing the entire web page. |
| if ([_currentEvent clickCount] <= 1 && [view acceptsFirstResponder] && [view needsPanelToBecomeKey]) { |
| [_bridge makeFirstResponder:view]; |
| } |
| } |
| |
| // We need to "defer loading" and defer timers while we are tracking the mouse. |
| // That's because we don't want the new page to load while the user is holding the mouse down. |
| |
| BOOL wasDeferringLoading = [_bridge defersLoading]; |
| if (!wasDeferringLoading) |
| [_bridge setDefersLoading:YES]; |
| BOOL wasDeferringTimers = isDeferringTimers(); |
| if (!wasDeferringTimers) |
| setDeferringTimers(true); |
| |
| ASSERT(!_sendingEventToSubview); |
| _sendingEventToSubview = true; |
| [view mouseDown:_currentEvent]; |
| _sendingEventToSubview = false; |
| |
| if (!wasDeferringTimers) |
| setDeferringTimers(false); |
| if (!wasDeferringLoading) |
| [_bridge setDefersLoading:NO]; |
| |
| // Remember which view we sent the event to, so we can direct the release event properly. |
| _mouseDownView = view; |
| _mouseDownWasInSubframe = false; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return true; |
| } |
| |
| bool MacFrame::lastEventIsMouseUp() const |
| { |
| // Many AK widgets run their own event loops and consume events while the mouse is down. |
| // When they finish, currentEvent is the mouseUp that they exited on. We need to update |
| // the khtml state with this mouseUp, which khtml never saw. This method lets us detect |
| // that state. |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| NSEvent *currentEventAfterHandlingMouseDown = [NSApp currentEvent]; |
| if (_currentEvent != currentEventAfterHandlingMouseDown) { |
| if ([currentEventAfterHandlingMouseDown type] == NSLeftMouseUp) { |
| return true; |
| } |
| } |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return false; |
| } |
| |
| // Note that this does the same kind of check as [target isDescendantOf:superview]. |
| // There are two differences: This is a lot slower because it has to walk the whole |
| // tree, and this works in cases where the target has already been deallocated. |
| static bool findViewInSubviews(NSView *superview, NSView *target) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| NSEnumerator *e = [[superview subviews] objectEnumerator]; |
| NSView *subview; |
| while ((subview = [e nextObject])) { |
| if (subview == target || findViewInSubviews(subview, target)) { |
| return true; |
| } |
| } |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return false; |
| } |
| |
| NSView *MacFrame::mouseDownViewIfStillGood() |
| { |
| // Since we have no way of tracking the lifetime of _mouseDownView, we have to assume that |
| // it could be deallocated already. We search for it in our subview tree; if we don't find |
| // it, we set it to nil. |
| NSView *mouseDownView = _mouseDownView; |
| if (!mouseDownView) { |
| return nil; |
| } |
| FrameView *topFrameView = d->m_view.get(); |
| NSView *topView = topFrameView ? topFrameView->getView() : nil; |
| if (!topView || !findViewInSubviews(topView, mouseDownView)) { |
| _mouseDownView = nil; |
| return nil; |
| } |
| return mouseDownView; |
| } |
| |
| bool MacFrame::eventMayStartDrag(NSEvent *event) const |
| { |
| // This is a pre-flight check of whether the event might lead to a drag being started. Be careful |
| // that its logic needs to stay in sync with khtmlMouseMoveEvent() and the way we set |
| // _mouseDownMayStartDrag in khtmlMousePressEvent |
| |
| if ([event type] != NSLeftMouseDown || [event clickCount] != 1) { |
| return false; |
| } |
| |
| BOOL DHTMLFlag, UAFlag; |
| [_bridge allowDHTMLDrag:&DHTMLFlag UADrag:&UAFlag]; |
| if (!DHTMLFlag && !UAFlag) { |
| return false; |
| } |
| |
| NSPoint loc = [event locationInWindow]; |
| int mouseDownX, mouseDownY; |
| d->m_view->viewportToContents((int)loc.x, (int)loc.y, mouseDownX, mouseDownY); |
| RenderObject::NodeInfo nodeInfo(true, false); |
| renderer()->layer()->hitTest(nodeInfo, mouseDownX, mouseDownY); |
| bool srcIsDHTML; |
| return nodeInfo.innerNode()->renderer()->draggableNode(DHTMLFlag, UAFlag, mouseDownX, mouseDownY, srcIsDHTML); |
| } |
| |
| // The link drag hysteresis is much larger than the others because there |
| // needs to be enough space to cancel the link press without starting a link drag, |
| // and because dragging links is rare. |
| const float LinkDragHysteresis = 40.0; |
| const float ImageDragHysteresis = 5.0; |
| const float TextDragHysteresis = 3.0; |
| const float GeneralDragHysteresis = 3.0; |
| const float TextDragDelay = 0.15; |
| |
| bool MacFrame::dragHysteresisExceeded(float dragLocationX, float dragLocationY) const |
| { |
| int dragX, dragY; |
| d->m_view->viewportToContents((int)dragLocationX, (int)dragLocationY, dragX, dragY); |
| float deltaX = fabsf(dragX - _mouseDownX); |
| float deltaY = fabsf(dragY - _mouseDownY); |
| |
| float threshold = GeneralDragHysteresis; |
| if (_dragSrcIsImage) { |
| threshold = ImageDragHysteresis; |
| } else if (_dragSrcIsLink) { |
| threshold = LinkDragHysteresis; |
| } else if (_dragSrcInSelection) { |
| threshold = TextDragHysteresis; |
| } |
| return deltaX >= threshold || deltaY >= threshold; |
| } |
| |
| void MacFrame::khtmlMouseMoveEvent(const MouseEventWithHitTestResults& event) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| if ([_currentEvent type] == NSLeftMouseDragged) { |
| NSView *view = mouseDownViewIfStillGood(); |
| |
| if (view) { |
| _sendingEventToSubview = true; |
| [view mouseDragged:_currentEvent]; |
| _sendingEventToSubview = false; |
| return; |
| } |
| |
| // Careful that the drag starting logic stays in sync with eventMayStartDrag() |
| |
| if (_mouseDownMayStartDrag && !_dragSrc) { |
| BOOL tempFlag1, tempFlag2; |
| [_bridge allowDHTMLDrag:&tempFlag1 UADrag:&tempFlag2]; |
| _dragSrcMayBeDHTML = tempFlag1; |
| _dragSrcMayBeUA = tempFlag2; |
| if (!_dragSrcMayBeDHTML && !_dragSrcMayBeUA) { |
| _mouseDownMayStartDrag = false; // no element is draggable |
| } |
| } |
| |
| if (_mouseDownMayStartDrag && !_dragSrc) { |
| // try to find an element that wants to be dragged |
| RenderObject::NodeInfo nodeInfo(true, false); |
| renderer()->layer()->hitTest(nodeInfo, _mouseDownX, _mouseDownY); |
| NodeImpl *node = nodeInfo.innerNode(); |
| _dragSrc = (node && node->renderer()) ? node->renderer()->draggableNode(_dragSrcMayBeDHTML, _dragSrcMayBeUA, _mouseDownX, _mouseDownY, _dragSrcIsDHTML) : 0; |
| if (!_dragSrc) { |
| _mouseDownMayStartDrag = false; // no element is draggable |
| } else { |
| // remember some facts about this source, while we have a NodeInfo handy |
| node = nodeInfo.URLElement(); |
| _dragSrcIsLink = node && node->isLink(); |
| |
| node = nodeInfo.innerNonSharedNode(); |
| _dragSrcIsImage = node && node->renderer() && node->renderer()->isImage(); |
| |
| _dragSrcInSelection = isPointInsideSelection(_mouseDownX, _mouseDownY); |
| } |
| } |
| |
| // For drags starting in the selection, the user must wait between the mousedown and mousedrag, |
| // or else we bail on the dragging stuff and allow selection to occur |
| if (_mouseDownMayStartDrag && _dragSrcInSelection && [_currentEvent timestamp] - _mouseDownTimestamp < TextDragDelay) { |
| _mouseDownMayStartDrag = false; |
| // ...but if this was the first click in the window, we don't even want to start selection |
| if (_activationEventNumber == [_currentEvent eventNumber]) { |
| _mouseDownMayStartSelect = false; |
| } |
| } |
| |
| if (_mouseDownMayStartDrag) { |
| // We are starting a text/image/url drag, so the cursor should be an arrow |
| d->m_view->setCursor(pointerCursor()); |
| |
| NSPoint dragLocation = [_currentEvent locationInWindow]; |
| if (dragHysteresisExceeded(dragLocation.x, dragLocation.y)) { |
| |
| // Once we're past the hysteresis point, we don't want to treat this gesture as a click |
| d->m_view->invalidateClick(); |
| |
| NSImage *dragImage = nil; // we use these values if WC is out of the loop |
| NSPoint dragLoc = NSZeroPoint; |
| NSDragOperation srcOp = NSDragOperationNone; |
| BOOL wcWrotePasteboard = NO; |
| if (_dragSrcMayBeDHTML) { |
| NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard]; |
| // Must be done before ondragstart adds types and data to the pboard, |
| // also done for security, as it erases data from the last drag |
| [pasteboard declareTypes:[NSArray array] owner:nil]; |
| |
| freeClipboard(); // would only happen if we missed a dragEnd. Do it anyway, just |
| // to make sure it gets numbified |
| _dragClipboard = new KWQClipboard(true, pasteboard, KWQClipboard::Writable, this); |
| |
| // If this is drag of an element, get set up to generate a default image. Otherwise |
| // WebKit will generate the default, the element doesn't override. |
| if (_dragSrcIsDHTML) { |
| int srcX, srcY; |
| _dragSrc->renderer()->absolutePosition(srcX, srcY); |
| _dragClipboard->setDragImageElement(_dragSrc.get(), IntPoint(_mouseDownX - srcX, _mouseDownY - srcY)); |
| } |
| |
| _mouseDownMayStartDrag = dispatchDragSrcEvent(dragstartEvent, m_mouseDown); |
| // Invalidate clipboard here against anymore pasteboard writing for security. The drag |
| // image can still be changed as we drag, but not the pasteboard data. |
| _dragClipboard->setAccessPolicy(KWQClipboard::ImageWritable); |
| |
| if (_mouseDownMayStartDrag) { |
| // gather values from DHTML element, if it set any |
| _dragClipboard->sourceOperation(&srcOp); |
| |
| NSArray *types = [pasteboard types]; |
| wcWrotePasteboard = types && [types count] > 0; |
| |
| if (_dragSrcMayBeDHTML) { |
| dragImage = _dragClipboard->dragNSImage(&dragLoc); |
| } |
| |
| // Yuck, dragSourceMovedTo() can be called as a result of kicking off the drag with |
| // dragImage! Because of that dumb reentrancy, we may think we've not started the |
| // drag when that happens. So we have to assume it's started before we kick it off. |
| _dragClipboard->setDragHasStarted(); |
| } |
| } |
| |
| if (_mouseDownMayStartDrag) { |
| BOOL startedDrag = [_bridge startDraggingImage:dragImage at:dragLoc operation:srcOp event:_currentEvent sourceIsDHTML:_dragSrcIsDHTML DHTMLWroteData:wcWrotePasteboard]; |
| if (!startedDrag && _dragSrcMayBeDHTML) { |
| // WebKit canned the drag at the last minute - we owe _dragSrc a DRAGEND event |
| MouseEvent event; |
| dispatchDragSrcEvent(dragendEvent, event); |
| _mouseDownMayStartDrag = false; |
| } |
| } |
| |
| if (!_mouseDownMayStartDrag) { |
| // something failed to start the drag, cleanup |
| freeClipboard(); |
| _dragSrc = 0; |
| } |
| } |
| |
| // No more default handling (like selection), whether we're past the hysteresis bounds or not |
| return; |
| } |
| if (!_mouseDownMayStartSelect) { |
| return; |
| } |
| |
| // Don't allow dragging or click handling after we've started selecting. |
| _mouseDownMayStartDrag = false; |
| d->m_view->invalidateClick(); |
| |
| NodeImpl* node = event.innerNode(); |
| RenderLayer* layer = 0; |
| if (node && node->renderer()) |
| layer = node->renderer()->enclosingLayer(); |
| |
| // If the selection is contained in a layer that can scroll, that layer should handle the autoscroll |
| // Otherwise, let the bridge handle it so the view can scroll itself. |
| while (layer && !layer->shouldAutoscroll()) |
| layer = layer->parent(); |
| if (layer) |
| handleAutoscroll(layer); |
| else |
| [_bridge handleAutoscrollForMouseDragged:_currentEvent]; |
| |
| } else { |
| // If we allowed the other side of the bridge to handle a drag |
| // last time, then m_bMousePressed might still be set. So we |
| // clear it now to make sure the next move after a drag |
| // doesn't look like a drag. |
| d->m_bMousePressed = false; |
| } |
| |
| Frame::khtmlMouseMoveEvent(event); |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| // Returns whether caller should continue with "the default processing", which is the same as |
| // the event handler NOT setting the return value to false |
| bool MacFrame::dispatchCPPEvent(const AtomicString &eventType, KWQClipboard::AccessPolicy policy) |
| { |
| NodeImpl* target = d->m_selection.start().element(); |
| if (!target && document()) |
| target = document()->body(); |
| if (!target) |
| return true; |
| |
| RefPtr<KWQClipboard> clipboard = new KWQClipboard(false, [NSPasteboard generalPasteboard], (KWQClipboard::AccessPolicy)policy); |
| |
| ExceptionCode ec = 0; |
| RefPtr<EventImpl> evt = new ClipboardEventImpl(eventType, true, true, clipboard.get()); |
| EventTargetNodeCast(target)->dispatchEvent(evt, ec, true); |
| bool noDefaultProcessing = evt->defaultPrevented(); |
| |
| // invalidate clipboard here for security |
| clipboard->setAccessPolicy(KWQClipboard::Numb); |
| |
| return !noDefaultProcessing; |
| } |
| |
| // WinIE uses onbeforecut and onbeforepaste to enables the cut and paste menu items. They |
| // also send onbeforecopy, apparently for symmetry, but it doesn't affect the menu items. |
| // We need to use onbeforecopy as a real menu enabler because we allow elements that are not |
| // normally selectable to implement copy/paste (like divs, or a document body). |
| |
| bool MacFrame::mayCut() |
| { |
| return !dispatchCPPEvent(beforecutEvent, KWQClipboard::Numb); |
| } |
| |
| bool MacFrame::mayCopy() |
| { |
| return !dispatchCPPEvent(beforecopyEvent, KWQClipboard::Numb); |
| } |
| |
| bool MacFrame::mayPaste() |
| { |
| return !dispatchCPPEvent(beforepasteEvent, KWQClipboard::Numb); |
| } |
| |
| bool MacFrame::tryCut() |
| { |
| // Must be done before oncut adds types and data to the pboard, |
| // also done for security, as it erases data from the last copy/paste. |
| [[NSPasteboard generalPasteboard] declareTypes:[NSArray array] owner:nil]; |
| |
| return !dispatchCPPEvent(cutEvent, KWQClipboard::Writable); |
| } |
| |
| bool MacFrame::tryCopy() |
| { |
| // Must be done before oncopy adds types and data to the pboard, |
| // also done for security, as it erases data from the last copy/paste. |
| [[NSPasteboard generalPasteboard] declareTypes:[NSArray array] owner:nil]; |
| |
| return !dispatchCPPEvent(copyEvent, KWQClipboard::Writable); |
| } |
| |
| bool MacFrame::tryPaste() |
| { |
| return !dispatchCPPEvent(pasteEvent, KWQClipboard::Readable); |
| } |
| |
| void MacFrame::khtmlMouseReleaseEvent(const MouseEventWithHitTestResults& event) |
| { |
| NSView *view = mouseDownViewIfStillGood(); |
| if (!view) { |
| // If this was the first click in the window, we don't even want to clear the selection. |
| // This case occurs when the user clicks on a draggable element, since we have to process |
| // the mouse down and drag events to see if we might start a drag. For other first clicks |
| // in a window, we just don't acceptFirstMouse, and the whole down-drag-up sequence gets |
| // ignored upstream of this layer. |
| if (_activationEventNumber != [_currentEvent eventNumber]) |
| Frame::khtmlMouseReleaseEvent(event); |
| return; |
| } |
| stopAutoscrollTimer(); |
| |
| _sendingEventToSubview = true; |
| KWQ_BLOCK_EXCEPTIONS; |
| [view mouseUp:_currentEvent]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| _sendingEventToSubview = false; |
| } |
| |
| bool MacFrame::passSubframeEventToSubframe(MouseEventWithHitTestResults &event) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| switch ([_currentEvent type]) { |
| case NSMouseMoved: { |
| NodeImpl *node = event.innerNode(); |
| if (!node) |
| return false; |
| RenderObject *renderer = node->renderer(); |
| if (!renderer || !renderer->isWidget()) |
| return false; |
| Widget *widget = static_cast<RenderWidget *>(renderer)->widget(); |
| if (!widget || !widget->isFrameView()) |
| return false; |
| Frame *subframePart = static_cast<FrameView *>(widget)->frame(); |
| if (!subframePart) |
| return false; |
| [Mac(subframePart)->bridge() mouseMoved:_currentEvent]; |
| return true; |
| } |
| |
| case NSLeftMouseDown: { |
| NodeImpl *node = event.innerNode(); |
| if (!node) { |
| return false; |
| } |
| RenderObject *renderer = node->renderer(); |
| if (!renderer || !renderer->isWidget()) { |
| return false; |
| } |
| Widget *widget = static_cast<RenderWidget *>(renderer)->widget(); |
| if (!widget || !widget->isFrameView()) |
| return false; |
| if (!passWidgetMouseDownEventToWidget(static_cast<RenderWidget *>(renderer))) { |
| return false; |
| } |
| _mouseDownWasInSubframe = true; |
| return true; |
| } |
| case NSLeftMouseUp: { |
| if (!_mouseDownWasInSubframe) { |
| return false; |
| } |
| NSView *view = mouseDownViewIfStillGood(); |
| if (!view) { |
| return false; |
| } |
| ASSERT(!_sendingEventToSubview); |
| _sendingEventToSubview = true; |
| [view mouseUp:_currentEvent]; |
| _sendingEventToSubview = false; |
| return true; |
| } |
| case NSLeftMouseDragged: { |
| if (!_mouseDownWasInSubframe) { |
| return false; |
| } |
| NSView *view = mouseDownViewIfStillGood(); |
| if (!view) { |
| return false; |
| } |
| ASSERT(!_sendingEventToSubview); |
| _sendingEventToSubview = true; |
| [view mouseDragged:_currentEvent]; |
| _sendingEventToSubview = false; |
| return true; |
| } |
| default: |
| return false; |
| } |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return false; |
| } |
| |
| bool MacFrame::passWheelEventToChildWidget(NodeImpl *node) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| if ([_currentEvent type] != NSScrollWheel || _sendingEventToSubview || !node) |
| return false; |
| else { |
| RenderObject *renderer = node->renderer(); |
| if (!renderer || !renderer->isWidget()) |
| return false; |
| Widget *widget = static_cast<RenderWidget *>(renderer)->widget(); |
| if (!widget) |
| return false; |
| |
| NSView *nodeView = widget->getView(); |
| ASSERT(nodeView); |
| ASSERT([nodeView superview]); |
| NSView *view = [nodeView hitTest:[[nodeView superview] convertPoint:[_currentEvent locationInWindow] fromView:nil]]; |
| |
| ASSERT(view); |
| _sendingEventToSubview = true; |
| [view scrollWheel:_currentEvent]; |
| _sendingEventToSubview = false; |
| return true; |
| } |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| return false; |
| } |
| |
| void MacFrame::mouseDown(NSEvent *event) |
| { |
| FrameView *v = d->m_view.get(); |
| if (!v || _sendingEventToSubview) |
| return; |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| prepareForUserAction(); |
| |
| _mouseDownView = nil; |
| _dragSrc = 0; |
| |
| NSEvent *oldCurrentEvent = _currentEvent; |
| _currentEvent = KWQRetain(event); |
| m_mouseDown = MouseEvent(event); |
| NSPoint loc = [event locationInWindow]; |
| d->m_view->viewportToContents((int)loc.x, (int)loc.y, _mouseDownX, _mouseDownY); |
| _mouseDownTimestamp = [event timestamp]; |
| |
| _mouseDownMayStartDrag = false; |
| _mouseDownMayStartSelect = false; |
| |
| v->viewportMousePressEvent(event); |
| |
| ASSERT(_currentEvent == event); |
| KWQRelease(event); |
| _currentEvent = oldCurrentEvent; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| void MacFrame::mouseDragged(NSEvent *event) |
| { |
| FrameView *v = d->m_view.get(); |
| if (!v || _sendingEventToSubview) { |
| return; |
| } |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| NSEvent *oldCurrentEvent = _currentEvent; |
| _currentEvent = KWQRetain(event); |
| |
| v->viewportMouseMoveEvent(event); |
| |
| ASSERT(_currentEvent == event); |
| KWQRelease(event); |
| _currentEvent = oldCurrentEvent; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| void MacFrame::mouseUp(NSEvent *event) |
| { |
| FrameView *v = d->m_view.get(); |
| if (!v || _sendingEventToSubview) |
| return; |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| NSEvent *oldCurrentEvent = _currentEvent; |
| _currentEvent = KWQRetain(event); |
| |
| // Our behavior here is a little different that Qt. Qt always sends |
| // a mouse release event, even for a double click. To correct problems |
| // in khtml's DOM click event handling we do not send a release here |
| // for a double click. Instead we send that event from FrameView's |
| // viewportMouseDoubleClickEvent. Note also that the third click of |
| // a triple click is treated as a single click, but the fourth is then |
| // treated as another double click. Hence the "% 2" below. |
| int clickCount = [event clickCount]; |
| if (clickCount > 0 && clickCount % 2 == 0) |
| v->viewportMouseDoubleClickEvent(event); |
| else |
| v->viewportMouseReleaseEvent(event); |
| |
| ASSERT(_currentEvent == event); |
| KWQRelease(event); |
| _currentEvent = oldCurrentEvent; |
| |
| _mouseDownView = nil; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| /* |
| A hack for the benefit of AK's PopUpButton, which uses the Carbon menu manager, which thus |
| eats all subsequent events after it is starts its modal tracking loop. After the interaction |
| is done, this routine is used to fix things up. When a mouse down started us tracking in |
| the widget, we post a fake mouse up to balance the mouse down we started with. When a |
| key down started us tracking in the widget, we post a fake key up to balance things out. |
| In addition, we post a fake mouseMoved to get the cursor in sync with whatever we happen to |
| be over after the tracking is done. |
| */ |
| void MacFrame::sendFakeEventsAfterWidgetTracking(NSEvent *initiatingEvent) |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| _sendingEventToSubview = false; |
| int eventType = [initiatingEvent type]; |
| if (eventType == NSLeftMouseDown || eventType == NSKeyDown) { |
| NSEvent *fakeEvent = nil; |
| if (eventType == NSLeftMouseDown) { |
| fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp |
| location:[initiatingEvent locationInWindow] |
| modifierFlags:[initiatingEvent modifierFlags] |
| timestamp:[initiatingEvent timestamp] |
| windowNumber:[initiatingEvent windowNumber] |
| context:[initiatingEvent context] |
| eventNumber:[initiatingEvent eventNumber] |
| clickCount:[initiatingEvent clickCount] |
| pressure:[initiatingEvent pressure]]; |
| |
| mouseUp(fakeEvent); |
| } |
| else { // eventType == NSKeyDown |
| fakeEvent = [NSEvent keyEventWithType:NSKeyUp |
| location:[initiatingEvent locationInWindow] |
| modifierFlags:[initiatingEvent modifierFlags] |
| timestamp:[initiatingEvent timestamp] |
| windowNumber:[initiatingEvent windowNumber] |
| context:[initiatingEvent context] |
| characters:[initiatingEvent characters] |
| charactersIgnoringModifiers:[initiatingEvent charactersIgnoringModifiers] |
| isARepeat:[initiatingEvent isARepeat] |
| keyCode:[initiatingEvent keyCode]]; |
| keyEvent(fakeEvent); |
| } |
| // FIXME: We should really get the current modifierFlags here, but there's no way to poll |
| // them in Cocoa, and because the event stream was stolen by the Carbon menu code we have |
| // no up-to-date cache of them anywhere. |
| fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved |
| location:[[_bridge window] convertScreenToBase:[NSEvent mouseLocation]] |
| modifierFlags:[initiatingEvent modifierFlags] |
| timestamp:[initiatingEvent timestamp] |
| windowNumber:[initiatingEvent windowNumber] |
| context:[initiatingEvent context] |
| eventNumber:0 |
| clickCount:0 |
| pressure:0]; |
| mouseMoved(fakeEvent); |
| } |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| void MacFrame::mouseMoved(NSEvent *event) |
| { |
| FrameView *v = d->m_view.get(); |
| // Reject a mouse moved if the button is down - screws up tracking during autoscroll |
| // These happen because WebKit sometimes has to fake up moved events. |
| if (!v || d->m_bMousePressed || _sendingEventToSubview) |
| return; |
| |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| NSEvent *oldCurrentEvent = _currentEvent; |
| _currentEvent = KWQRetain(event); |
| |
| v->viewportMouseMoveEvent(event); |
| |
| ASSERT(_currentEvent == event); |
| KWQRelease(event); |
| _currentEvent = oldCurrentEvent; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| } |
| |
| // Called as we walk up the element chain for nodes with CSS property -khtml-user-drag == auto |
| bool MacFrame::shouldDragAutoNode(NodeImpl* node, int x, int y) const |
| { |
| // We assume that WebKit only cares about dragging things that can be leaf nodes (text, images, urls). |
| // This saves a bunch of expensive calls (creating WC and WK element dicts) as we walk farther up |
| // the node hierarchy, and we also don't have to cook up a way to ask WK about non-leaf nodes |
| // (since right now WK just hit-tests using a cached lastMouseDown). |
| if (!node->hasChildNodes() && d->m_view) { |
| int windowX, windowY; |
| d->m_view->contentsToViewport(x, y, windowX, windowY); |
| NSPoint eventLoc = {windowX, windowY}; |
| return [_bridge mayStartDragAtEventLocation:eventLoc]; |
| } else |
| return NO; |
| } |
| |
| bool MacFrame::sendContextMenuEvent(NSEvent *event) |
| { |
| DocumentImpl* doc = d->m_doc.get(); |
| FrameView* v = d->m_view.get(); |
| if (!doc || !v) |
| return false; |
| |
| bool swallowEvent; |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| NSEvent *oldCurrentEvent = _currentEvent; |
| _currentEvent = KWQRetain(event); |
| |
| MouseEvent mouseEvent(event); |
| |
| int xm, ym; |
| v->viewportToContents(mouseEvent.x(), mouseEvent.y(), xm, ym); |
| |
| MouseEventWithHitTestResults mev = doc->prepareMouseEvent(false, true, false, xm, ym, mouseEvent); |
| |
| swallowEvent = v->dispatchMouseEvent(contextmenuEvent, mev.innerNode(), true, 0, mouseEvent, true); |
| if (!swallowEvent && !isPointInsideSelection(xm, ym) && |
| ([_bridge selectWordBeforeMenuEvent] || [_bridge isEditable] || mev.innerNode()->isContentEditable())) { |
| selectClosestWordFromMouseEvent(mouseEvent, mev.innerNode(), xm, ym); |
| } |
| |
| ASSERT(_currentEvent == event); |
| KWQRelease(event); |
| _currentEvent = oldCurrentEvent; |
| |
| return swallowEvent; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return false; |
| } |
| |
| struct ListItemInfo { |
| unsigned start; |
| unsigned end; |
| }; |
| |
| NSFileWrapper *MacFrame::fileWrapperForElement(ElementImpl *e) |
| { |
| NSFileWrapper *wrapper = nil; |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| const AtomicString& attr = e->getAttribute(srcAttr); |
| if (!attr.isEmpty()) { |
| NSURL *URL = completeURL(attr.qstring()).getNSURL(); |
| wrapper = [_bridge fileWrapperForURL:URL]; |
| } |
| if (!wrapper) { |
| RenderImage *renderer = static_cast<RenderImage *>(e->renderer()); |
| if (renderer->cachedImage() && !renderer->cachedImage()->isErrorImage()) { |
| wrapper = [[NSFileWrapper alloc] initRegularFileWithContents:(NSData*)(renderer->cachedImage()->image()->getTIFFRepresentation())]; |
| [wrapper setPreferredFilename:@"image.tiff"]; |
| [wrapper autorelease]; |
| } |
| } |
| |
| return wrapper; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return nil; |
| } |
| |
| static ElementImpl *listParent(ElementImpl *item) |
| { |
| while (!item->hasTagName(ulTag) && !item->hasTagName(olTag)) { |
| item = static_cast<ElementImpl *>(item->parentNode()); |
| if (!item) |
| break; |
| } |
| return item; |
| } |
| |
| static NodeImpl* isTextFirstInListItem(NodeImpl *e) |
| { |
| if (!e->isTextNode()) |
| return 0; |
| NodeImpl* par = e->parentNode(); |
| while (par) { |
| if (par->firstChild() != e) |
| return 0; |
| if (par->hasTagName(liTag)) |
| return par; |
| e = par; |
| par = par->parentNode(); |
| } |
| return 0; |
| } |
| |
| // FIXME: This collosal function needs to be refactored into maintainable smaller bits. |
| |
| #define BULLET_CHAR 0x2022 |
| #define SQUARE_CHAR 0x25AA |
| #define CIRCLE_CHAR 0x25E6 |
| |
| NSAttributedString *MacFrame::attributedString(NodeImpl *_start, int startOffset, NodeImpl *endNode, int endOffset) |
| { |
| ListItemInfo info; |
| NSMutableAttributedString *result; |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| NodeImpl * _startNode = _start; |
| |
| if (_startNode == nil) { |
| return nil; |
| } |
| |
| result = [[[NSMutableAttributedString alloc] init] autorelease]; |
| |
| bool hasNewLine = true; |
| bool addedSpace = true; |
| NSAttributedString *pendingStyledSpace = nil; |
| bool hasParagraphBreak = true; |
| const ElementImpl *linkStartNode = 0; |
| unsigned linkStartLocation = 0; |
| QPtrList<ElementImpl> listItems; |
| QValueList<ListItemInfo> listItemLocations; |
| float maxMarkerWidth = 0; |
| |
| NodeImpl *n = _startNode; |
| |
| // If the first item is the entire text of a list item, use the list item node as the start of the |
| // selection, not the text node. The user's intent was probably to select the list. |
| if (n->isTextNode() && startOffset == 0) { |
| NodeImpl *startListNode = isTextFirstInListItem(_startNode); |
| if (startListNode){ |
| _startNode = startListNode; |
| n = _startNode; |
| } |
| } |
| |
| while (n) { |
| RenderObject *renderer = n->renderer(); |
| if (renderer) { |
| RenderStyle *style = renderer->style(); |
| NSFont *font = style->font().getNSFont(); |
| bool needSpace = pendingStyledSpace != nil; |
| if (n->isTextNode()) { |
| if (hasNewLine) { |
| addedSpace = true; |
| needSpace = false; |
| [pendingStyledSpace release]; |
| pendingStyledSpace = nil; |
| hasNewLine = false; |
| } |
| QString text; |
| QString str = n->nodeValue().qstring(); |
| int start = (n == _startNode) ? startOffset : -1; |
| int end = (n == endNode) ? endOffset : -1; |
| if (renderer->isText()) { |
| if (!style->collapseWhiteSpace()) { |
| if (needSpace && !addedSpace) { |
| if (text.isEmpty() && linkStartLocation == [result length]) { |
| ++linkStartLocation; |
| } |
| [result appendAttributedString:pendingStyledSpace]; |
| } |
| int runStart = (start == -1) ? 0 : start; |
| int runEnd = (end == -1) ? str.length() : end; |
| text += str.mid(runStart, runEnd-runStart); |
| [pendingStyledSpace release]; |
| pendingStyledSpace = nil; |
| addedSpace = str[runEnd-1].direction() == QChar::DirWS; |
| } |
| else { |
| RenderText* textObj = static_cast<RenderText*>(renderer); |
| if (!textObj->firstTextBox() && str.length() > 0 && !addedSpace) { |
| // We have no runs, but we do have a length. This means we must be |
| // whitespace that collapsed away at the end of a line. |
| text += ' '; |
| addedSpace = true; |
| } |
| else { |
| addedSpace = false; |
| for (InlineTextBox* box = textObj->firstTextBox(); box; box = box->nextTextBox()) { |
| int runStart = (start == -1) ? box->m_start : start; |
| int runEnd = (end == -1) ? box->m_start + box->m_len : end; |
| runEnd = kMin(runEnd, box->m_start + box->m_len); |
| if (runStart >= box->m_start && |
| runStart < box->m_start + box->m_len) { |
| if (box == textObj->firstTextBox() && box->m_start == runStart && runStart > 0) { |
| needSpace = true; // collapsed space at the start |
| } |
| if (needSpace && !addedSpace) { |
| if (pendingStyledSpace != nil) { |
| if (text.isEmpty() && linkStartLocation == [result length]) { |
| ++linkStartLocation; |
| } |
| [result appendAttributedString:pendingStyledSpace]; |
| } else { |
| text += ' '; |
| } |
| } |
| QString runText = str.mid(runStart, runEnd - runStart); |
| runText.replace('\n', ' '); |
| text += runText; |
| int nextRunStart = box->nextTextBox() ? box->nextTextBox()->m_start : str.length(); // collapsed space between runs or at the end |
| needSpace = nextRunStart > runEnd; |
| [pendingStyledSpace release]; |
| pendingStyledSpace = nil; |
| addedSpace = str[runEnd-1].direction() == QChar::DirWS; |
| start = -1; |
| } |
| if (end != -1 && runEnd >= end) |
| break; |
| } |
| } |
| } |
| } |
| |
| text.replace(QChar('\\'), renderer->backslashAsCurrencySymbol()); |
| |
| if (text.length() > 0 || needSpace) { |
| NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init]; |
| [attrs setObject:font forKey:NSFontAttributeName]; |
| if (style && style->color().isValid() && style->color().alpha() != 0) |
| [attrs setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName]; |
| if (style && style->backgroundColor().isValid() && style->backgroundColor().alpha() != 0) |
| [attrs setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName]; |
| |
| if (text.length() > 0) { |
| hasParagraphBreak = false; |
| NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString() attributes:attrs]; |
| [result appendAttributedString: partialString]; |
| [partialString release]; |
| } |
| |
| if (needSpace) { |
| [pendingStyledSpace release]; |
| pendingStyledSpace = [[NSAttributedString alloc] initWithString:@" " attributes:attrs]; |
| } |
| |
| [attrs release]; |
| } |
| } else { |
| // This is our simple HTML -> ASCII transformation: |
| QString text; |
| if (n->hasTagName(aTag)) { |
| // Note the start of the <a> element. We will add the NSLinkAttributeName |
| // attribute to the attributed string when navigating to the next sibling |
| // of this node. |
| linkStartLocation = [result length]; |
| linkStartNode = static_cast<ElementImpl*>(n); |
| } else if (n->hasTagName(brTag)) { |
| text += "\n"; |
| hasNewLine = true; |
| } else if (n->hasTagName(liTag)) { |
| QString listText; |
| ElementImpl *itemParent = listParent(static_cast<ElementImpl *>(n)); |
| |
| if (!hasNewLine) |
| listText += '\n'; |
| hasNewLine = true; |
| |
| listItems.append(static_cast<ElementImpl*>(n)); |
| info.start = [result length]; |
| info.end = 0; |
| listItemLocations.append (info); |
| |
| listText += '\t'; |
| if (itemParent && renderer->isListItem()) { |
| RenderListItem *listRenderer = static_cast<RenderListItem*>(renderer); |
| |
| maxMarkerWidth = MAX([font pointSize], maxMarkerWidth); |
| switch(style->listStyleType()) { |
| case khtml::DISC: |
| listText += ((QChar)BULLET_CHAR); |
| break; |
| case khtml::CIRCLE: |
| listText += ((QChar)CIRCLE_CHAR); |
| break; |
| case khtml::SQUARE: |
| listText += ((QChar)SQUARE_CHAR); |
| break; |
| case khtml::LNONE: |
| break; |
| default: |
| QString marker = listRenderer->markerStringValue(); |
| listText += marker; |
| // Use AppKit metrics. Will be rendered by AppKit. |
| float markerWidth = [marker.getNSString() sizeWithAttributes:[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]].width; |
| maxMarkerWidth = MAX(markerWidth, maxMarkerWidth); |
| } |
| |
| listText += ' '; |
| listText += '\t'; |
| |
| NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init]; |
| [attrs setObject:font forKey:NSFontAttributeName]; |
| if (style && style->color().isValid()) |
| [attrs setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName]; |
| if (style && style->backgroundColor().isValid()) |
| [attrs setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName]; |
| |
| NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:listText.getNSString() attributes:attrs]; |
| [attrs release]; |
| [result appendAttributedString: partialString]; |
| [partialString release]; |
| } |
| } else if (n->hasTagName(olTag) || n->hasTagName(ulTag)) { |
| if (!hasNewLine) |
| text += "\n"; |
| hasNewLine = true; |
| } else if (n->hasTagName(tdTag) || |
| n->hasTagName(thTag) || |
| n->hasTagName(hrTag) || |
| n->hasTagName(ddTag) || |
| n->hasTagName(dlTag) || |
| n->hasTagName(dtTag) || |
| n->hasTagName(preTag) || |
| n->hasTagName(blockquoteTag) || |
| n->hasTagName(divTag)) { |
| if (!hasNewLine) |
| text += '\n'; |
| hasNewLine = true; |
| } else if (n->hasTagName(pTag) || |
| n->hasTagName(trTag) || |
| n->hasTagName(h1Tag) || |
| n->hasTagName(h2Tag) || |
| n->hasTagName(h3Tag) || |
| n->hasTagName(h4Tag) || |
| n->hasTagName(h5Tag) || |
| n->hasTagName(h6Tag)) { |
| if (!hasNewLine) |
| text += '\n'; |
| |
| // In certain cases, emit a paragraph break. |
| int bottomMargin = renderer->collapsedMarginBottom(); |
| int fontSize = style->fontDescription().computedPixelSize(); |
| if (bottomMargin * 2 >= fontSize) { |
| if (!hasParagraphBreak) { |
| text += '\n'; |
| hasParagraphBreak = true; |
| } |
| } |
| |
| hasNewLine = true; |
| } |
| else if (n->hasTagName(imgTag)) { |
| if (pendingStyledSpace != nil) { |
| if (linkStartLocation == [result length]) { |
| ++linkStartLocation; |
| } |
| [result appendAttributedString:pendingStyledSpace]; |
| [pendingStyledSpace release]; |
| pendingStyledSpace = nil; |
| } |
| NSFileWrapper *fileWrapper = fileWrapperForElement(static_cast<ElementImpl *>(n)); |
| NSTextAttachment *attachment = [[NSTextAttachment alloc] initWithFileWrapper:fileWrapper]; |
| NSAttributedString *iString = [NSAttributedString attributedStringWithAttachment:attachment]; |
| [result appendAttributedString: iString]; |
| [attachment release]; |
| } |
| |
| NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()]; |
| [result appendAttributedString: partialString]; |
| [partialString release]; |
| } |
| } |
| |
| if (n == endNode) |
| break; |
| |
| NodeImpl *next = n->firstChild(); |
| if (!next) { |
| next = n->nextSibling(); |
| } |
| |
| while (!next && n->parentNode()) { |
| QString text; |
| n = n->parentNode(); |
| if (n == endNode) |
| break; |
| next = n->nextSibling(); |
| |
| if (n->hasTagName(aTag)) { |
| // End of a <a> element. Create an attributed string NSLinkAttributeName attribute |
| // for the range of the link. Note that we create the attributed string from the DOM, which |
| // will have corrected any illegally nested <a> elements. |
| if (linkStartNode && n == linkStartNode) { |
| DOMString href = parseURL(linkStartNode->getAttribute(hrefAttr)); |
| KURL kURL = Mac(linkStartNode->getDocument()->frame())->completeURL(href.qstring()); |
| |
| NSURL *URL = kURL.getNSURL(); |
| NSRange tempRange = { linkStartLocation, [result length]-linkStartLocation }; // workaround for 4213314 |
| [result addAttribute:NSLinkAttributeName value:URL range:tempRange]; |
| linkStartNode = 0; |
| } |
| } |
| else if (n->hasTagName(olTag) || n->hasTagName(ulTag)) { |
| if (!hasNewLine) |
| text += '\n'; |
| hasNewLine = true; |
| } else if (n->hasTagName(liTag)) { |
| |
| int i, count = listItems.count(); |
| for (i = 0; i < count; i++){ |
| if (listItems.at(i) == n){ |
| listItemLocations[i].end = [result length]; |
| break; |
| } |
| } |
| if (!hasNewLine) |
| text += '\n'; |
| hasNewLine = true; |
| } else if (n->hasTagName(tdTag) || |
| n->hasTagName(thTag) || |
| n->hasTagName(hrTag) || |
| n->hasTagName(ddTag) || |
| n->hasTagName(dlTag) || |
| n->hasTagName(dtTag) || |
| n->hasTagName(preTag) || |
| n->hasTagName(blockquoteTag) || |
| n->hasTagName(divTag)) { |
| if (!hasNewLine) |
| text += '\n'; |
| hasNewLine = true; |
| } else if (n->hasTagName(pTag) || |
| n->hasTagName(trTag) || |
| n->hasTagName(h1Tag) || |
| n->hasTagName(h2Tag) || |
| n->hasTagName(h3Tag) || |
| n->hasTagName(h4Tag) || |
| n->hasTagName(h5Tag) || |
| n->hasTagName(h6Tag)) { |
| if (!hasNewLine) |
| text += '\n'; |
| // An extra newline is needed at the start, not the end, of these types of tags, |
| // so don't add another here. |
| hasNewLine = true; |
| } |
| |
| NSAttributedString *partialString = [[NSAttributedString alloc] initWithString:text.getNSString()]; |
| [result appendAttributedString:partialString]; |
| [partialString release]; |
| } |
| |
| n = next; |
| } |
| |
| [pendingStyledSpace release]; |
| |
| // Apply paragraph styles from outside in. This ensures that nested lists correctly |
| // override their parent's paragraph style. |
| { |
| unsigned i, count = listItems.count(); |
| ElementImpl *e; |
| |
| #ifdef POSITION_LIST |
| NodeImpl *containingBlock; |
| int containingBlockX, containingBlockY; |
| |
| // Determine the position of the outermost containing block. All paragraph |
| // styles and tabs should be relative to this position. So, the horizontal position of |
| // each item in the list (in the resulting attributed string) will be relative to position |
| // of the outermost containing block. |
| if (count > 0){ |
| containingBlock = _startNode; |
| while (containingBlock->renderer()->isInline()){ |
| containingBlock = containingBlock->parentNode(); |
| } |
| containingBlock->renderer()->absolutePosition(containingBlockX, containingBlockY); |
| } |
| #endif |
| |
| for (i = 0; i < count; i++){ |
| e = listItems.at(i); |
| info = listItemLocations[i]; |
| |
| if (info.end < info.start) |
| info.end = [result length]; |
| |
| RenderObject *r = e->renderer(); |
| RenderStyle *style = r->style(); |
| |
| int rx; |
| NSFont *font = style->font().getNSFont(); |
| float pointSize = [font pointSize]; |
| |
| #ifdef POSITION_LIST |
| int ry; |
| r->absolutePosition(rx, ry); |
| rx -= containingBlockX; |
| |
| // Ensure that the text is indented at least enough to allow for the markers. |
| rx = MAX(rx, (int)maxMarkerWidth); |
| #else |
| rx = (int)MAX(maxMarkerWidth, pointSize); |
| #endif |
| |
| // The bullet text will be right aligned at the first tab marker, followed |
| // by a space, followed by the list item text. The space is arbitrarily |
| // picked as pointSize*2/3. The space on the first line of the text item |
| // is established by a left aligned tab, on subsequent lines it's established |
| // by the head indent. |
| NSMutableParagraphStyle *mps = [[NSMutableParagraphStyle alloc] init]; |
| [mps setFirstLineHeadIndent: 0]; |
| [mps setHeadIndent: rx]; |
| [mps setTabStops:[NSArray arrayWithObjects: |
| [[[NSTextTab alloc] initWithType:NSRightTabStopType location:rx-(pointSize*2/3)] autorelease], |
| [[[NSTextTab alloc] initWithType:NSLeftTabStopType location:rx] autorelease], |
| nil]]; |
| NSRange tempRange = { info.start, info.end-info.start }; // workaround for 4213314 |
| [result addAttribute:NSParagraphStyleAttributeName value:mps range:tempRange]; |
| [mps release]; |
| } |
| } |
| |
| return result; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return nil; |
| } |
| |
| NSImage *MacFrame::imageFromRect(NSRect rect) const |
| { |
| NSView *view = d->m_view->getDocumentView(); |
| if (!view) |
| return nil; |
| |
| NSImage *resultImage; |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| NSRect bounds = [view bounds]; |
| resultImage = [[[NSImage alloc] initWithSize:rect.size] autorelease]; |
| |
| if (rect.size.width != 0 && rect.size.height != 0) { |
| [resultImage setFlipped:YES]; |
| [resultImage lockFocus]; |
| |
| CGContextRef context = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; |
| |
| CGContextSaveGState(context); |
| CGContextTranslateCTM(context, bounds.origin.x - rect.origin.x, bounds.origin.y - rect.origin.y); |
| [view drawRect:rect]; |
| CGContextRestoreGState(context); |
| |
| [resultImage unlockFocus]; |
| [resultImage setFlipped:NO]; |
| } |
| |
| return resultImage; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return nil; |
| } |
| |
| NSImage *MacFrame::selectionImage() const |
| { |
| _drawSelectionOnly = true; // invoke special drawing mode |
| NSImage *result = imageFromRect(visibleSelectionRect()); |
| _drawSelectionOnly = false; |
| return result; |
| } |
| |
| NSImage *MacFrame::snapshotDragImage(NodeImpl *node, NSRect *imageRect, NSRect *elementRect) const |
| { |
| RenderObject *renderer = node->renderer(); |
| if (!renderer) { |
| return nil; |
| } |
| |
| renderer->updateDragState(true); // mark dragged nodes (so they pick up the right CSS) |
| d->m_doc->updateLayout(); // forces style recalc - needed since changing the drag state might |
| // imply new styles, plus JS could have changed other things |
| IntRect topLevelRect; |
| NSRect paintingRect = renderer->paintingRootRect(topLevelRect); |
| |
| _elementToDraw = node; // invoke special sub-tree drawing mode |
| NSImage *result = imageFromRect(paintingRect); |
| renderer->updateDragState(false); |
| d->m_doc->updateLayout(); |
| _elementToDraw = 0; |
| |
| if (elementRect) { |
| *elementRect = topLevelRect; |
| } |
| if (imageRect) { |
| *imageRect = paintingRect; |
| } |
| return result; |
| } |
| |
| NSFont *MacFrame::fontForSelection(bool *hasMultipleFonts) const |
| { |
| if (hasMultipleFonts) |
| *hasMultipleFonts = false; |
| |
| if (!d->m_selection.isRange()) { |
| NodeImpl *nodeToRemove; |
| RenderStyle *style = styleForSelectionStart(nodeToRemove); // sets nodeToRemove |
| |
| NSFont *result = 0; |
| if (style) |
| result = style->font().getNSFont(); |
| |
| if (nodeToRemove) { |
| ExceptionCode ec; |
| nodeToRemove->remove(ec); |
| ASSERT(ec == 0); |
| } |
| |
| return result; |
| } |
| |
| NSFont *font = nil; |
| |
| RefPtr<RangeImpl> range = d->m_selection.toRange(); |
| NodeImpl *startNode = range->editingStartPosition().node(); |
| if (startNode != nil) { |
| NodeImpl *pastEnd = range->pastEndNode(); |
| // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one |
| // unreproducible case where this didn't happen, so check for nil also. |
| for (NodeImpl *n = startNode; n && n != pastEnd; n = n->traverseNextNode()) { |
| RenderObject *renderer = n->renderer(); |
| if (!renderer) |
| continue; |
| // FIXME: Are there any node types that have renderers, but that we should be skipping? |
| NSFont *f = renderer->style()->font().getNSFont(); |
| if (font == nil) { |
| font = f; |
| if (!hasMultipleFonts) |
| break; |
| } else if (font != f) { |
| *hasMultipleFonts = true; |
| break; |
| } |
| } |
| } |
| |
| return font; |
| } |
| |
| NSDictionary *MacFrame::fontAttributesForSelectionStart() const |
| { |
| NodeImpl *nodeToRemove; |
| RenderStyle *style = styleForSelectionStart(nodeToRemove); |
| if (!style) |
| return nil; |
| |
| NSMutableDictionary *result = [NSMutableDictionary dictionary]; |
| |
| if (style->backgroundColor().isValid() && style->backgroundColor().alpha() != 0) |
| [result setObject:nsColor(style->backgroundColor()) forKey:NSBackgroundColorAttributeName]; |
| |
| if (style->font().getNSFont()) |
| [result setObject:style->font().getNSFont() forKey:NSFontAttributeName]; |
| |
| if (style->color().isValid() && style->color() != Color::black) |
| [result setObject:nsColor(style->color()) forKey:NSForegroundColorAttributeName]; |
| |
| ShadowData *shadow = style->textShadow(); |
| if (shadow) { |
| NSShadow *s = [[NSShadow alloc] init]; |
| [s setShadowOffset:NSMakeSize(shadow->x, shadow->y)]; |
| [s setShadowBlurRadius:shadow->blur]; |
| [s setShadowColor:nsColor(shadow->color)]; |
| [result setObject:s forKey:NSShadowAttributeName]; |
| } |
| |
| int decoration = style->textDecorationsInEffect(); |
| if (decoration & khtml::LINE_THROUGH) |
| [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName]; |
| |
| int superscriptInt = 0; |
| switch (style->verticalAlign()) { |
| case khtml::BASELINE: |
| case khtml::BOTTOM: |
| case khtml::BASELINE_MIDDLE: |
| case khtml::LENGTH: |
| case khtml::MIDDLE: |
| case khtml::TEXT_BOTTOM: |
| case khtml::TEXT_TOP: |
| case khtml::TOP: |
| break; |
| case khtml::SUB: |
| superscriptInt = -1; |
| break; |
| case khtml::SUPER: |
| superscriptInt = 1; |
| break; |
| } |
| if (superscriptInt) |
| [result setObject:[NSNumber numberWithInt:superscriptInt] forKey:NSSuperscriptAttributeName]; |
| |
| if (decoration & khtml::UNDERLINE) |
| [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName]; |
| |
| if (nodeToRemove) { |
| ExceptionCode ec = 0; |
| nodeToRemove->remove(ec); |
| ASSERT(ec == 0); |
| } |
| |
| return result; |
| } |
| |
| NSWritingDirection MacFrame::baseWritingDirectionForSelectionStart() const |
| { |
| NSWritingDirection result = NSWritingDirectionLeftToRight; |
| |
| Position pos = VisiblePosition(d->m_selection.start(), d->m_selection.affinity()).deepEquivalent(); |
| NodeImpl *node = pos.node(); |
| if (!node || !node->renderer() || !node->renderer()->containingBlock()) |
| return result; |
| RenderStyle *style = node->renderer()->containingBlock()->style(); |
| if (!style) |
| return result; |
| |
| switch (style->direction()) { |
| case khtml::LTR: |
| result = NSWritingDirectionLeftToRight; |
| break; |
| case khtml::RTL: |
| result = NSWritingDirectionRightToLeft; |
| break; |
| } |
| |
| return result; |
| } |
| |
| void MacFrame::tokenizerProcessedData() |
| { |
| if (d->m_doc) { |
| checkCompleted(); |
| } |
| [_bridge tokenizerProcessedData]; |
| } |
| |
| void MacFrame::setBridge(WebCoreFrameBridge *bridge) |
| { |
| if (_bridge == bridge) |
| return; |
| |
| KWQRetain(bridge); |
| KWQRelease(_bridge); |
| _bridge = bridge; |
| } |
| |
| QString MacFrame::overrideMediaType() const |
| { |
| NSString *overrideType = [_bridge overrideMediaType]; |
| if (overrideType) |
| return QString::fromNSString(overrideType); |
| return QString(); |
| } |
| |
| void MacFrame::setDisplaysWithFocusAttributes(bool flag) |
| { |
| if (d->m_isFocused == flag) |
| return; |
| |
| Frame::setDisplaysWithFocusAttributes(flag); |
| DocumentImpl *doc = document(); |
| // Mac Specific: Changing the tint of controls from clear to aqua/graphite and vice versa. We |
| // do a "fake" paint. When the theme gets a paint call, it can then do an invalidate. |
| if (doc && d->m_view && d->m_view->getDocumentView() && theme()->supportsControlTints() && renderer()) { |
| doc->updateLayout(); // Ensure layout is up to date. |
| IntRect visibleRect(enclosingIntRect(d->m_view->visibleContentRect())); |
| GraphicsContext p; |
| p.setUpdatingControlTints(true); |
| paint(&p, visibleRect); |
| } |
| } |
| |
| NSColor *MacFrame::bodyBackgroundColor() const |
| { |
| if (document() && document()->body() && document()->body()->renderer()) { |
| Color bgColor = document()->body()->renderer()->style()->backgroundColor(); |
| if (bgColor.isValid()) |
| return nsColor(bgColor); |
| } |
| return nil; |
| } |
| |
| WebCoreKeyboardUIMode MacFrame::keyboardUIMode() const |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| return [_bridge keyboardUIMode]; |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return WebCoreKeyboardAccessDefault; |
| } |
| |
| void MacFrame::didTellBridgeAboutLoad(const DOM::DOMString& URL) |
| { |
| urlsBridgeKnowsAbout.add(URL.impl()); |
| } |
| |
| bool MacFrame::haveToldBridgeAboutLoad(const DOM::DOMString& URL) |
| { |
| return urlsBridgeKnowsAbout.contains(URL.impl()); |
| } |
| |
| void MacFrame::clear() |
| { |
| urlsBridgeKnowsAbout.clear(); |
| setMarkedTextRange(0, nil, nil); |
| Frame::clear(); |
| } |
| |
| void MacFrame::print() |
| { |
| [Mac(this)->_bridge print]; |
| } |
| |
| KJS::Bindings::Instance *MacFrame::getAppletInstanceForWidget(Widget *widget) |
| { |
| NSView *aView = widget->getView(); |
| if (!aView) |
| return 0; |
| jobject applet; |
| |
| // Get a pointer to the actual Java applet instance. |
| if ([_bridge respondsToSelector:@selector(getAppletInView:)]) |
| applet = [_bridge getAppletInView:aView]; |
| else |
| applet = [_bridge pollForAppletInView:aView]; |
| |
| if (applet) { |
| // Wrap the Java instance in a language neutral binding and hand |
| // off ownership to the APPLET element. |
| KJS::Bindings::RootObject *executionContext = KJS::Bindings::RootObject::findRootObjectForNativeHandleFunction ()(aView); |
| KJS::Bindings::Instance *instance = KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::JavaLanguage, applet, executionContext); |
| return instance; |
| } |
| |
| return 0; |
| } |
| |
| static KJS::Bindings::Instance *getInstanceForView(NSView *aView) |
| { |
| if ([aView respondsToSelector:@selector(objectForWebScript)]){ |
| id object = [aView objectForWebScript]; |
| if (object) { |
| KJS::Bindings::RootObject *executionContext = KJS::Bindings::RootObject::findRootObjectForNativeHandleFunction ()(aView); |
| return KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::ObjectiveCLanguage, object, executionContext); |
| } |
| } |
| else if ([aView respondsToSelector:@selector(pluginScriptableObject)]){ |
| void *object = [aView pluginScriptableObject]; |
| if (object) { |
| KJS::Bindings::RootObject *executionContext = KJS::Bindings::RootObject::findRootObjectForNativeHandleFunction ()(aView); |
| return KJS::Bindings::Instance::createBindingForLanguageInstance (KJS::Bindings::Instance::CLanguage, object, executionContext); |
| } |
| } |
| return 0; |
| } |
| |
| KJS::Bindings::Instance *MacFrame::getEmbedInstanceForWidget(Widget *widget) |
| { |
| return getInstanceForView(widget->getView()); |
| } |
| |
| KJS::Bindings::Instance *MacFrame::getObjectInstanceForWidget(Widget *widget) |
| { |
| return getInstanceForView(widget->getView()); |
| } |
| |
| void MacFrame::addPluginRootObject(const KJS::Bindings::RootObject *root) |
| { |
| rootObjects.append (root); |
| } |
| |
| void MacFrame::cleanupPluginRootObjects() |
| { |
| JSLock lock; |
| |
| KJS::Bindings::RootObject *root; |
| while ((root = rootObjects.getLast())) { |
| root->removeAllNativeReferences(); |
| rootObjects.removeLast(); |
| } |
| } |
| |
| void MacFrame::registerCommandForUndoOrRedo(const EditCommandPtr &cmd, bool isRedo) |
| { |
| ASSERT(cmd.get()); |
| KWQEditCommand *kwq = [KWQEditCommand commandWithEditCommand:cmd.get()]; |
| NSUndoManager *undoManager = [_bridge undoManager]; |
| [undoManager registerUndoWithTarget:_bridge selector:(isRedo ? @selector(redoEditing:) : @selector(undoEditing:)) object:kwq]; |
| NSString *actionName = [_bridge nameForUndoAction:static_cast<WebUndoAction>(cmd.editingAction())]; |
| if (actionName != nil) { |
| [undoManager setActionName:actionName]; |
| } |
| _haveUndoRedoOperations = YES; |
| } |
| |
| void MacFrame::registerCommandForUndo(const EditCommandPtr &cmd) |
| { |
| registerCommandForUndoOrRedo(cmd, NO); |
| } |
| |
| void MacFrame::registerCommandForRedo(const EditCommandPtr &cmd) |
| { |
| registerCommandForUndoOrRedo(cmd, YES); |
| } |
| |
| void MacFrame::clearUndoRedoOperations() |
| { |
| if (_haveUndoRedoOperations) { |
| [[_bridge undoManager] removeAllActionsWithTarget:_bridge]; |
| _haveUndoRedoOperations = NO; |
| } |
| } |
| |
| void MacFrame::issueUndoCommand() |
| { |
| if (canUndo()) |
| [[_bridge undoManager] undo]; |
| } |
| |
| void MacFrame::issueRedoCommand() |
| { |
| if (canRedo()) |
| [[_bridge undoManager] redo]; |
| } |
| |
| void MacFrame::issueCutCommand() |
| { |
| [_bridge issueCutCommand]; |
| } |
| |
| void MacFrame::issueCopyCommand() |
| { |
| [_bridge issueCopyCommand]; |
| } |
| |
| void MacFrame::issuePasteCommand() |
| { |
| [_bridge issuePasteCommand]; |
| } |
| |
| void MacFrame::issuePasteAndMatchStyleCommand() |
| { |
| [_bridge issuePasteAndMatchStyleCommand]; |
| } |
| |
| void MacFrame::issueTransposeCommand() |
| { |
| [_bridge issueTransposeCommand]; |
| } |
| |
| bool MacFrame::canUndo() const |
| { |
| return [[Mac(this)->_bridge undoManager] canUndo]; |
| } |
| |
| bool MacFrame::canRedo() const |
| { |
| return [[Mac(this)->_bridge undoManager] canRedo]; |
| } |
| |
| bool MacFrame::canPaste() const |
| { |
| return [Mac(this)->_bridge canPaste]; |
| } |
| |
| void MacFrame::markMisspellingsInAdjacentWords(const VisiblePosition &p) |
| { |
| if (![_bridge isContinuousSpellCheckingEnabled]) |
| return; |
| markMisspellings(SelectionController(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary))); |
| } |
| |
| void MacFrame::markMisspellings(const SelectionController &selection) |
| { |
| // This function is called with a selection already expanded to word boundaries. |
| // Might be nice to assert that here. |
| |
| if (![_bridge isContinuousSpellCheckingEnabled]) |
| return; |
| |
| RefPtr<RangeImpl> searchRange(selection.toRange()); |
| if (!searchRange || searchRange->isDetached()) |
| return; |
| |
| // If we're not in an editable node, bail. |
| int exception = 0; |
| NodeImpl *editableNodeImpl = searchRange->startContainer(exception); |
| if (!editableNodeImpl->isContentEditable()) |
| return; |
| |
| // Get the spell checker if it is available |
| NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; |
| if (checker == nil) |
| return; |
| |
| WordAwareIterator it(searchRange.get()); |
| |
| while (!it.atEnd()) { // we may be starting at the end of the doc, and already by atEnd |
| const QChar *chars = it.characters(); |
| int len = it.length(); |
| if (len > 1 || !chars[0].isSpace()) { |
| NSString *chunk = [[NSString alloc] initWithCharactersNoCopy:(unichar *)chars length:len freeWhenDone:NO]; |
| int startIndex = 0; |
| // Loop over the chunk to find each misspelling in it. |
| while (startIndex < len) { |
| NSRange misspelling = [checker checkSpellingOfString:chunk startingAt:startIndex language:nil wrap:NO inSpellDocumentWithTag:[_bridge spellCheckerDocumentTag] wordCount:NULL]; |
| if (misspelling.length == 0) { |
| break; |
| } |
| else { |
| // Build up result range and string. Note the misspelling may span many text nodes, |
| // but the CharIterator insulates us from this complexity |
| RefPtr<RangeImpl> misspellingRange(rangeOfContents(document())); |
| CharacterIterator chars(it.range().get()); |
| chars.advance(misspelling.location); |
| misspellingRange->setStart(chars.range()->startContainer(exception), chars.range()->startOffset(exception), exception); |
| chars.advance(misspelling.length); |
| misspellingRange->setEnd(chars.range()->startContainer(exception), chars.range()->startOffset(exception), exception); |
| // Mark misspelling in document. |
| document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling); |
| startIndex = misspelling.location + misspelling.length; |
| } |
| } |
| [chunk release]; |
| } |
| |
| it.advance(); |
| } |
| } |
| |
| void MacFrame::respondToChangedSelection(const SelectionController &oldSelection, bool closeTyping) |
| { |
| if (document()) { |
| if ([_bridge isContinuousSpellCheckingEnabled]) { |
| SelectionController oldAdjacentWords = SelectionController(); |
| |
| // If this is a change in selection resulting from a delete operation, oldSelection may no longer |
| // be in the document. |
| if (oldSelection.start().node() && oldSelection.start().node()->inDocument()) { |
| VisiblePosition oldStart(oldSelection.start(), oldSelection.affinity()); |
| oldAdjacentWords = SelectionController(startOfWord(oldStart, LeftWordIfOnBoundary), endOfWord(oldStart, RightWordIfOnBoundary)); |
| } |
| |
| VisiblePosition newStart(selection().start(), selection().affinity()); |
| SelectionController newAdjacentWords(startOfWord(newStart, LeftWordIfOnBoundary), endOfWord(newStart, RightWordIfOnBoundary)); |
| |
| if (oldAdjacentWords != newAdjacentWords) { |
| // Mark misspellings in the portion that was previously unmarked because of |
| // the proximity of the start of the selection. We only spell check words in |
| // the vicinity of the start of the old selection because the spelling checker |
| // is not fast enough to do a lot of spelling checking implicitly. This matches |
| // AppKit. This function is really the only code that knows that rule. The |
| // markMisspellings function is prepared to handler larger ranges. |
| |
| // When typing we check spelling elsewhere, so don't redo it here. |
| if (closeTyping) { |
| markMisspellings(oldAdjacentWords); |
| } |
| |
| // This only erases a marker in the first word of the selection. |
| // Perhaps peculiar, but it matches AppKit. |
| document()->removeMarkers(newAdjacentWords.toRange().get(), DocumentMarker::Spelling); |
| } |
| } else { |
| // When continuous spell checking is off, no markers appear after the selection changes. |
| document()->removeMarkers(DocumentMarker::Spelling); |
| } |
| } |
| |
| [_bridge respondToChangedSelection]; |
| } |
| |
| bool MacFrame::shouldChangeSelection(const SelectionController &oldSelection, const SelectionController &newSelection, khtml::EAffinity affinity, bool stillSelecting) const |
| { |
| return [_bridge shouldChangeSelectedDOMRange:[DOMRange _rangeWithImpl:oldSelection.toRange().get()] |
| toDOMRange:[DOMRange _rangeWithImpl:newSelection.toRange().get()] |
| affinity:static_cast<NSSelectionAffinity>(affinity) |
| stillSelecting:stillSelecting]; |
| } |
| |
| void MacFrame::respondToChangedContents() |
| { |
| if (KWQAccObjectCache::accessibilityEnabled()) |
| renderer()->document()->getAccObjectCache()->postNotificationToTopWebArea(renderer(), "AXValueChanged"); |
| [_bridge respondToChangedContents]; |
| } |
| |
| bool MacFrame::isContentEditable() const |
| { |
| return Frame::isContentEditable() || [_bridge isEditable]; |
| } |
| |
| bool MacFrame::shouldBeginEditing(const RangeImpl *range) const |
| { |
| ASSERT(range); |
| return [_bridge shouldBeginEditing:[DOMRange _rangeWithImpl:const_cast<RangeImpl *>(range)]]; |
| } |
| |
| bool MacFrame::shouldEndEditing(const RangeImpl *range) const |
| { |
| ASSERT(range); |
| return [_bridge shouldEndEditing:[DOMRange _rangeWithImpl:const_cast<RangeImpl *>(range)]]; |
| } |
| |
| void MacFrame::didBeginEditing() const |
| { |
| [_bridge didBeginEditing]; |
| } |
| |
| void MacFrame::didEndEditing() const |
| { |
| [_bridge didEndEditing]; |
| } |
| |
| static QValueList<MarkedTextUnderline> convertAttributesToUnderlines(const RangeImpl *markedTextRange, NSArray *attributes, NSArray *ranges) |
| { |
| QValueList<MarkedTextUnderline> result; |
| |
| int exception = 0; |
| int baseOffset = markedTextRange->startOffset(exception); |
| |
| unsigned length = [attributes count]; |
| ASSERT([ranges count] == length); |
| |
| for (unsigned i = 0; i < length; i++) { |
| NSNumber *style = [[attributes objectAtIndex:i] objectForKey:NSUnderlineStyleAttributeName]; |
| if (!style) |
| continue; |
| NSRange range = [[ranges objectAtIndex:i] rangeValue]; |
| NSColor *color = [[attributes objectAtIndex:i] objectForKey:NSUnderlineColorAttributeName]; |
| Color qColor = Color::black; |
| if (color) { |
| NSColor* deviceColor = [color colorUsingColorSpaceName:NSDeviceRGBColorSpace]; |
| qColor = Color(makeRGBA((int)(255 * [deviceColor redComponent]), |
| (int)(255 * [deviceColor blueComponent]), |
| (int)(255 * [deviceColor greenComponent]), |
| (int)(255 * [deviceColor alphaComponent]))); |
| } |
| |
| result.append(MarkedTextUnderline(range.location + baseOffset, |
| range.location + baseOffset + range.length, |
| qColor, |
| [style intValue] > 1)); |
| } |
| |
| return result; |
| } |
| |
| void MacFrame::setMarkedTextRange(const RangeImpl *range, NSArray *attributes, NSArray *ranges) |
| { |
| int exception = 0; |
| |
| ASSERT(!range || range->startContainer(exception) == range->endContainer(exception)); |
| ASSERT(!range || range->collapsed(exception) || range->startContainer(exception)->isTextNode()); |
| |
| if (attributes == nil) { |
| m_markedTextUsesUnderlines = false; |
| m_markedTextUnderlines.clear(); |
| } else { |
| m_markedTextUsesUnderlines = true; |
| m_markedTextUnderlines = convertAttributesToUnderlines(range, attributes, ranges); |
| } |
| |
| if (m_markedTextRange.get() && document() && m_markedTextRange->startContainer(exception)->renderer()) |
| m_markedTextRange->startContainer(exception)->renderer()->repaint(); |
| |
| if ( range && range->collapsed(exception) ) { |
| m_markedTextRange = 0; |
| } else { |
| m_markedTextRange = const_cast<RangeImpl *>(range); |
| } |
| |
| if (m_markedTextRange.get() && document() && m_markedTextRange->startContainer(exception)->renderer()) { |
| m_markedTextRange->startContainer(exception)->renderer()->repaint(); |
| } |
| } |
| |
| bool MacFrame::canGoBackOrForward(int distance) const |
| { |
| return [_bridge canGoBackOrForward:distance]; |
| } |
| |
| void MacFrame::didFirstLayout() |
| { |
| [_bridge didFirstLayout]; |
| } |
| |
| NSMutableDictionary *MacFrame::dashboardRegionsDictionary() |
| { |
| DocumentImpl *doc = document(); |
| if (!doc) { |
| return nil; |
| } |
| |
| const QValueList<DashboardRegionValue> regions = doc->dashboardRegions(); |
| uint i, count = regions.count(); |
| |
| // Convert the QValueList<DashboardRegionValue> into a NSDictionary of WebDashboardRegions |
| NSMutableDictionary *webRegions = [[[NSMutableDictionary alloc] initWithCapacity:count] autorelease]; |
| for (i = 0; i < count; i++) { |
| DashboardRegionValue region = regions[i]; |
| |
| if (region.type == StyleDashboardRegion::None) |
| continue; |
| |
| NSRect clip; |
| clip.origin.x = region.clip.x(); |
| clip.origin.y = region.clip.y(); |
| clip.size.width = region.clip.width(); |
| clip.size.height = region.clip.height(); |
| NSRect rect; |
| rect.origin.x = region.bounds.x(); |
| rect.origin.y = region.bounds.y(); |
| rect.size.width = region.bounds.width(); |
| rect.size.height = region.bounds.height(); |
| NSString *label = region.label.getNSString(); |
| WebDashboardRegionType type = WebDashboardRegionTypeNone; |
| if (region.type == StyleDashboardRegion::Circle) |
| type = WebDashboardRegionTypeCircle; |
| else if (region.type == StyleDashboardRegion::Rectangle) |
| type = WebDashboardRegionTypeRectangle; |
| NSMutableArray *regionValues = [webRegions objectForKey:label]; |
| if (!regionValues) { |
| regionValues = [NSMutableArray array]; |
| [webRegions setObject:regionValues forKey:label]; |
| } |
| |
| WebDashboardRegion *webRegion = [[[WebDashboardRegion alloc] initWithRect:rect clip:clip type:type] autorelease]; |
| [regionValues addObject:webRegion]; |
| } |
| |
| return webRegions; |
| } |
| |
| void MacFrame::dashboardRegionsChanged() |
| { |
| NSMutableDictionary *webRegions = dashboardRegionsDictionary(); |
| [_bridge dashboardRegionsChanged:webRegions]; |
| } |
| |
| bool MacFrame::isCharacterSmartReplaceExempt(const QChar &c, bool isPreviousChar) |
| { |
| return [_bridge isCharacterSmartReplaceExempt:c.unicode() isPreviousCharacter:isPreviousChar]; |
| } |
| |
| void MacFrame::handledOnloadEvents() |
| { |
| [_bridge handledOnloadEvents]; |
| } |
| |
| bool MacFrame::shouldClose() |
| { |
| KWQ_BLOCK_EXCEPTIONS; |
| |
| if (![_bridge canRunBeforeUnloadConfirmPanel]) |
| return true; |
| |
| RefPtr<DocumentImpl> doc = document(); |
| if (!doc) |
| return true; |
| HTMLElementImpl* body = doc->body(); |
| if (!body) |
| return true; |
| |
| RefPtr<BeforeUnloadEventImpl> event = new BeforeUnloadEventImpl; |
| event->setTarget(doc.get()); |
| doc->handleWindowEvent(event.get(), false); |
| |
| if (!event->defaultPrevented() && doc) |
| doc->defaultEventHandler(event.get()); |
| if (event->result().isNull()) |
| return true; |
| |
| QString text = event->result().qstring(); |
| text.replace(QChar('\\'), backslashAsCurrencySymbol()); |
| |
| return [_bridge runBeforeUnloadConfirmPanelWithMessage:text.getNSString()]; |
| |
| KWQ_UNBLOCK_EXCEPTIONS; |
| |
| return true; |
| } |
| |
| void MacFrame::dragSourceMovedTo(const MouseEvent& event) |
| { |
| if (_dragSrc && _dragSrcMayBeDHTML) { |
| // for now we don't care if event handler cancels default behavior, since there is none |
| dispatchDragSrcEvent(dragEvent, event); |
| } |
| } |
| |
| void MacFrame::dragSourceEndedAt(const MouseEvent& event, NSDragOperation operation) |
| { |
| if (_dragSrc && _dragSrcMayBeDHTML) { |
| _dragClipboard->setDestinationOperation(operation); |
| // for now we don't care if event handler cancels default behavior, since there is none |
| dispatchDragSrcEvent(dragendEvent, event); |
| } |
| freeClipboard(); |
| _dragSrc = 0; |
| } |
| |
| // returns if we should continue "default processing", i.e., whether eventhandler canceled |
| bool MacFrame::dispatchDragSrcEvent(const AtomicString &eventType, const MouseEvent& event) const |
| { |
| bool noDefaultProc = d->m_view->dispatchDragEvent(eventType, _dragSrc.get(), event, _dragClipboard.get()); |
| return !noDefaultProc; |
| } |
| |
| void MacFrame::detachFromView() |
| { |
| setView(0); |
| } |
| |
| } |