WebCore:
authordarin <darin@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 7 Sep 2004 00:25:38 +0000 (00:25 +0000)
committerdarin <darin@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 7 Sep 2004 00:25:38 +0000 (00:25 +0000)
        Reviewed by John.

        - fixed <rdar://problem/3512066> REGRESSION (Mail): Select All highlights only part of the content, though all is copied
        - fixed <rdar://problem/3157025> REGRESSION (Mail): Should select paragraph instead of line on triple-click
        - fixed <rdar://problem/3735048> REGRESSION (Mail): HTML editing must select newline on triple click
        - fixed <rdar://problem/3788872> REGRESSION (Mail): ctrl-a emacs key binding does not work (move to start of paragraph)
        - fixed <rdar://problem/3788881> REGRESSION (Mail): ctrl-e emacs key binding does not work (move to end of paragraph)
        - fixed <rdar://problem/3789931> REGRESSION (Mail): typing style lost when you backspace
        - added code to select inserted text to help WebKit implement yankAndSelect:
        - did some clean-up on editing commands code

        * khtml/khtml_part.cpp:
        (KHTMLPart::handleMousePressEventTripleClick): Expand to a paragraph, not a line.
        (KHTMLPart::selectAll): Remove a lot of unneeded code and just expand to document using Selection.
        (KHTMLPart::selectionHasStyle): Remove check that limited us to only HTML elements, and add
        a check for nil. Together, these two changes fix a few cases I ran into during testing.

        * khtml/rendering/render_canvas.cpp: (RenderCanvas::setSelection): Fix test that was checking if
        the old selection spanned multiple objects when it really should have checked whether the new
        new selection spans multiple objects. This caused the select all redraw bug.

        * khtml/editing/htmlediting.h: Fixed indenting. Remove command IDs, replacing them with a couple
        of specific type checking methods, and SharedCommandImpl, using EditCommandImpl directly instead.
        Got rid of virtual functions, since the command objects have no data members other than the ones
        inherited from SharedPtr. Removed explicit copy constructors and destructors. Removed unneeded
        empty constructors. Removed inline directives that were having no effect. Added some boolean
        selectInsertedText parameter to some commands. Made almost all member functions const, because
        the const here applies to the command object which is a smart pointer, not the pointed-to
        command implementation object.
        * khtml/editing/htmlediting.cpp:
        (khtml::EditCommand::EditCommand): Changed since SharedPtr is now a pointer to EditCommandImpl.
        (khtml::EditCommand::operator=): Added. By defining this explicitly, we don't need the class
        that we are pointing to defined in the header, which lets us get rid of SharedCommandImpl.
        (khtml::EditCommand::isInputTextCommand): Added.
        (khtml::EditCommand::isTypingCommand): Added.
        (khtml::EditCommand::setParent): Updated since EditCommandImpl.setParent takes an impl pointer now.
        (khtml::EditCommand::handle): Removed, since it's identical to get().
        (khtml::InputTextCommand::input): Added selectInsertedText parameter.
        (khtml::TypingCommand::TypingCommand): Ditto.
        (khtml::TypingCommand::insertText): Ditto.
        (khtml::TypingCommand::isOpenForMoreTypingCommand): Use isTypingCommand instead of commandID.
        (khtml::TypingCommand::closeTyping): Change parameter type to const reference.

        * khtml/editing/htmlediting_impl.h: Made StyleChange functions be const member functions.
        Changed parent() and setParent() to take and return EditCommandImpl pointers and be inlined.
        Added virtual isInputTextCommand, isTypingCommand, and preservesTypingStyle functions.
        Removed commandID functions and unneeded explicit destructors. Removed unneeded doApply
        function declaration in CompositeEditCommandImpl. Changed type of applyTypingStyle to return
        a node, not necessarily an element. Removed private execute function from InputTextCommandImpl.
        Added selectInsertedText to InputTextCommandImpl's input function and TypingCommandImpl's constructor
        and insertText function.
        * khtml/editing/htmlediting_impl.cpp:
        (khtml::StyleChange::StyleChange): Don't bother initializing the booleans since init handles that.
        (khtml::StyleChange::init): Added code to strip whitespace, and tweaked how the function does its job.
        (khtml::StyleChange::currentlyHasStyle): Added a check for null value, which can happen for properties
        where we don't have computed style implemented yet.
        (khtml::EditCommandImpl::EditCommandImpl): Updated since there is no SharedCommandImpl any more.
        (khtml::EditCommandImpl::apply): Call new preservesTypingStyle function rather than checking
        the command ID. This preserves typing style when deleting with the keyboard.
        (khtml::EditCommandImpl::setStartingSelection): Changed since parent is now a EditCommandImpl.
        Also rewrote to use for loop so it's simpler-looking.
        (khtml::EditCommandImpl::setEndingSelection): Ditto.
        (khtml::EditCommandImpl::setTypingStyle): Ditto.
        (khtml::EditCommandImpl::preservesTypingStyle): Added. Returns false.
        (khtml::EditCommandImpl::isInputTextCommand): Added. Returns false.
        (khtml::EditCommandImpl::isTypingCommand): Added. Returns false.
        (khtml::CompositeEditCommandImpl::applyTypingStyle): Changed this function to return a node rather
        than an element. Also change it so it returns the child as-is if the style change has nothing in it.
        (khtml::AppendNodeCommandImpl::~AppendNodeCommandImpl): Removed unneeded null checks.
        (khtml::DeleteSelectionCommandImpl::preservesTypingStyle): Added. Returns true.
        (khtml::DeleteTextCommandImpl::~DeleteTextCommandImpl): Removed unneeded null checks.
        (khtml::InputTextCommandImpl::input): Renamed execute function to input and got rid of the additional
        level of indirection, since it was the only caller. Added selectInsertedText parameter and changed
        the code so it will respect it.
        (khtml::InputTextCommandImpl::isInputTextCommand): Added. Returns true.
        (khtml::InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl): Removed unneeded null checks.
        (khtml::JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl): Ditto.
        (khtml::MoveSelectionCommandImpl::MoveSelectionCommandImpl): Hold a reference to the fragment
        while the command exists.
        (khtml::MoveSelectionCommandImpl::~MoveSelectionCommandImpl): Release the fragment.
        (khtml::RemoveNodeCommandImpl::~RemoveNodeCommandImpl): Remove unneeded null checks.
        (khtml::RemoveNodeCommandImpl::doUnapply): Take advantage of defined behavior of insertBefore when
        the node to insert before is 0 (means the same thing as appendChild).
        (khtml::RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl):
        Remove unneeded null check.
        (khtml::ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl): Hold a reference to the fragment
        while the command exists.
        (khtml::ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl): Release the fragment.
        (khtml::SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl): Remove unneeded null check.
        (khtml::SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl): Remove unneeded null check.
        (khtml::TypingCommandImpl::TypingCommandImpl): Added selectInsertedText parameter.
        (khtml::TypingCommandImpl::doApply): Pass along selectInsertedText parameter.
        (khtml::TypingCommandImpl::insertText): Ditto.
        (khtml::TypingCommandImpl::preservesTypingStyle): Added. Returns true for DeleteKey.
        (khtml::TypingCommandImpl::isTypingCommand): Added. Returns true.

        * khtml/editing/jsediting.cpp: Add a cut at implementing the underline command. May not work
        perfectly yet because text-decoration is not inherited the way, say, font-weight is.

        * khtml/xml/dom_selection.h: Removed some unneeded friend declarations for comparison operators
        that use only public members. Added PARAGRAPH_BOUNDARY.
        * khtml/xml/dom_selection.cpp:
        (DOM::Selection::modifyExtendingRightForward): Implemented paragraph boundary case, simplified line
        boundary case (by changing parameter types and function names, not the algorithm), and made
        paragraph case fall into line case for now.
        (DOM::Selection::modifyMovingRightForward): Ditto.
        (DOM::Selection::modifyExtendingLeftBackward): Ditto.
        (DOM::Selection::modifyMovingLeftBackward): Ditto.
        (DOM::Selection::validate): Implemented paragraph case and paragraph boundary case, simplified the
        line case as above and also fixed the document case.
        (DOM::startOfFirstRunAt): Changed to use DOM::Position instead of node
        offset pairs. Also renamed and got rid of separate bool to indicate "not found".
        (DOM::endOfLastRunAt): Ditto.
        (DOM::selectionForLine): Ditto.

        * khtml/xml/dom_position.h: Added startParagraphBoundary and endParagraphBoundary.
        Also used enums instead of bools in a couple of places. Removed some unneeded friend
        declarations for comparison operators that use only public members.
        * khtml/xml/dom_position.cpp:
        (DOM::Position::Position): Coding style tweak.
        (DOM::Position::startParagraphBoundary): Added.
        (DOM::Position::endParagraphBoundary): Added.
        (DOM::Position::upstream): Use enum parameter instead of bool.
        (DOM::Position::downstream): Ditto.
        (DOM::Position::rendersOnSameLine): Removed unused function. Noticed some backwards logic
        here, and removed it rather than fixing it.

        * khtml/xml/dom_nodeimpl.h: Added traversePreviousNodePostOrder.
        * khtml/xml/dom_nodeimpl.cpp:
        (NodeImpl::traversePreviousNodePostOrder): Added. For some uses, this function is more analogous
        to traverseNextNode in reverse than traversePreviousNode, which does a backwards pre-order traversal.

        * kwq/WebCoreBridge.h: Replaced fontForCurrentPosition method with fontForSelection:, added
        selectInsertedText parameter to insertText method, and added selectionStartHasStyle: method.
        Add WebSelectToParagraphBoundary.
        * kwq/WebCoreBridge.mm:
        (-[WebCoreBridge setSelectedDOMRange:affinity:]): Added workaround for bug where isRenderedContent
        returns false for <br> elements at the ends of lines.
        (-[WebCoreBridge insertText:selectInsertedText:]): Added selectInsertedText parameter, passing it
        along to TypingCommand::insertText.
        (-[WebCoreBridge selectionStartHasStyle:]): Added. Calls code in the part that does the real work.
        (-[WebCoreBridge fontForSelection:]): Ditto.

        * kwq/KWQKHTMLPart.h: Renamed fontForCurrentPosition to fontForSelection, and added a feature
        where it tells us whether there are multiple fonts in the selection or not.
        * kwq/KWQKHTMLPart.mm:
        (KWQKHTMLPart::fontForSelection): Rewrote this to be a bit simpler, and added code to detect
        whether there are multiple fonts in the selection.
        (KWQKHTMLPart::didTellBridgeAboutLoad): Use a global character rather than a bad pointer here.
        Better not to rely on undefined behavior.
        (KWQKHTMLPart::documentFragmentWithText): Use lowercase HTML. I believe this works better when
        the document is XML, and we should probably visit all callers and change them.
        (KWQKHTMLPart::registerCommandForUndo): Call get() instead of handle().
        (KWQKHTMLPart::registerCommandForRedo): Call get() instead of handle().

        * khtml/misc/shared.h: Added a private, non-implemented copy constructor and assignment
        operator to Shared<> and TreeShared<> to prevent copying reference counts by accident.
        * khtml/rendering/render_style.h: Get rid of Shared<BorderData>, since it's not used that way.
        This makes BorderData smaller, and allows it to compile with the change to Shared<>.
        * khtml/xml/dom_docimpl.h: Remove unneeded copy constructor on DocumentFragmentImpl,
        which ran afoul of the change to Shared<> and was unused.
        * khtml/xml/dom_docimpl.cpp: Ditto.
        * khtml/xml/dom_stringimpl.cpp:
        (DOM::DOMStringImpl::empty): Changed code around so it doesn't run afoul of the change to
        Shared<>. The old code was copying a DOMStringImpl.
        (DOM::DOMStringImpl::ascii): Added code to null-terminate the string buffer.

WebKit:

        Reviewed by John.

        - fixed <rdar://problem/3696542> REGRESSION (Mail): Editable WebKit doesn't support underline yet (in the iChat profile window, at least)
        - fixed <rdar://problem/3780249> REGRESSION (Mail): copy style/paste style doesn't work in HTML editing in Mail
        - fixed <rdar://problem/3788857> REGRESSION (Mail): Home and End keys don't work in message composer
        - fixed <rdar://problem/3788884> REGRESSION (Mail): ctrl-d emacs key binding does not work (delete forward)
        - fixed <rdar://problem/3788890> REGRESSION (Mail): ctrl-k emacs key binding does not work (delete to end of paragraph)
        - fixed <rdar://problem/3788899> REGRESSION (Mail): ctrl-y emacs key binding does not work (yank)
        - fixed <rdar://problem/3788901> REGRESSION (Mail): ctrl-o emacs key binding does not work (insert newline in front of insertion point)
        - fixed <rdar://problem/3788908> REGRESSION (Mail): ctrl-left-arrow emacs key binding does not work (move to beginning of line)
        - fixed <rdar://problem/3788913> REGRESSION (Mail): ctrl-right-arrow emacs key binding does not work (move to end of line)
        - implemented a first cut at other attribute changes from Text Panel besides underline (bugs?)
        - dealt with a couple of FIXMEs in WebHTMLView.m
        - updated list of not-yet-implemented methods in WebHTMLView.m
        - fixed many deletion operations to call the correct editing delegate methods

        * WebView.subproj/WebFrameViewPrivate.h: Remove _scrollToTopLeft and _scrollToBottomLeft.
        No one was calling them anyway, so they should really have been marked internal and not private.
        * WebView.subproj/WebFrameView.m:
        (-[WebFrameView scrollToBeginningOfDocument:]): Renamed _scrollToTopLeft to this, so the home key
        would start working with the key bindings machinery.
        (-[WebFrameView scrollToEndOfDocument:]): Same thing, for end key.
        (-[WebFrameView keyDown:]): Update for name changes, and also make sure we don't try to grab
        control-arrow keys here (probably not necessary, but good anyway).

        * WebView.subproj/WebHTMLViewInternal.h: Added keyDownEvent field, and startNewKillRingSequence
        and nextResponderDisabledOnce flags.
        * WebView.subproj/WebHTMLView.m:
        Rearrange declarations at the top of the file so that external things are up with
        the #import directives and things inside this file are declared below.
        (-[WebHTMLView _shouldReplaceSelectionWithText:givenAction:]): Ditto.
        (-[WebHTMLView _calculatePrintHeight]): Moved up into the "internal to file" category.
        (-[WebHTMLView _updateTextSizeMultiplier]): Ditto.
        (-[WebHTMLView _selectedRange]): Added.
        (-[WebHTMLView _openLinkFromMenu:]): Left this method lying around even though I deleted the
        other APPKIT_CODE_FOR_REFERENCE in case this shows up in the context menu we are now sharing
        with the AppKit. Chris will look at this later, and he can delete it then.
        (+[WebHTMLView initialize]): Call _NSInitializeKillRing.
        (-[WebHTMLView _documentRange]): Added.
        (-[WebHTMLView string]): Call the bridge to get the plain text rather than making an attributed
        string and then getting the text from there.
        (-[WebHTMLView becomeFirstResponder]): Set startNewKillRingSequence flag, so that new deletions
        will create a new kill ring entry.
        (-[WebHTMLView moveToBeginningOfDocument:]): Use backward direction instead of left direction.
        (-[WebHTMLView moveToBeginningOfDocumentAndModifySelection:]): Ditto.
        (-[WebHTMLView moveToBeginningOfLine:]): Ditto.
        (-[WebHTMLView moveToBeginningOfLineAndModifySelection:]): Ditto.
        (-[WebHTMLView moveToBeginningOfParagraph:]): Ditto, also use WebSelectToParagraphBoundary.
        (-[WebHTMLView moveToBeginningOfParagraphAndModifySelection:]): Ditto.
        (-[WebHTMLView moveToEndOfDocument:]): Use forward direction instead of right direction.
        (-[WebHTMLView moveToEndOfDocumentAndModifySelection:]): Ditto.
        (-[WebHTMLView moveToEndOfLine:]): Ditto.
        (-[WebHTMLView moveToEndOfLineAndModifySelection:]): Ditto.
        (-[WebHTMLView moveToEndOfParagraph:]): Ditto, also use WebSelectToParagraphBoundary.
        (-[WebHTMLView moveToEndOfParagraphAndModifySelection:]): Ditto.
        (-[WebHTMLView _shouldDeleteRange:]): Added.
        (-[WebHTMLView _deleteRange:preflight:killRing:prepend:]): Added.
        (-[WebHTMLView delete:]): Changed to call new _deleteRange method.
        (-[WebHTMLView cut:]): Changed to preflight property and call new _deleteRange method.
        (-[WebHTMLView _selectionFontAttributes]): Added.
        (-[WebHTMLView _selectionFontAttributesAsRTF]): Added.
        (-[WebHTMLView _fontAttributesFromFontPasteboard]): Added.
        (-[WebHTMLView _emptyStyle]): Added.
        (-[WebHTMLView _styleFromFontAttributes:]): Added.
        (-[WebHTMLView _applyStyleToSelection:]): Added.
        (-[WebHTMLView copyFont:]): Implemented.
        (-[WebHTMLView pasteFont:]): Implemented.
        (-[WebHTMLView _originalFontA]): Added.
        (-[WebHTMLView _originalFontB]): Added.
        (-[WebHTMLView _addToStyle:fontA:fontB:]): Added. Has code from the method that figures out
        what the font manager is doing for changeFont:, now needed for changeAttribute: too.
        (-[WebHTMLView _styleFromFontManagerOperation]): Renamed and now calls shared methods.
        (-[WebHTMLView changeFont:]): Call shared method, still does the same thing.
        (-[WebHTMLView _colorAsString:]): Added. Has code from the method we were using with the
        color panel before.
        (-[WebHTMLView _shadowAsString:]): Added.
        (-[WebHTMLView _styleForAttributeChange:]): Added.
        (-[WebHTMLView changeAttributes:]): Implemented.
        (-[WebHTMLView _styleFromColorPanelWithSelector:]): Renamed and now calls shared methods.
        (-[WebHTMLView _changeCSSColorUsingSelector:inRange:]): Call method by new name.
        (-[WebHTMLView changeDocumentBackgroundColor:]): Call method by new name.
        (-[WebHTMLView changeColor:]): Changed around a bit; still doesn't work yet.
        (-[WebHTMLView _alignSelectionUsingCSSValue:]): Call shared methods.
        (-[WebHTMLView indent:]): Removed, since NSTextView doesn't implement this method. Added to list
        of methods to possibly implement later in the file.
        (-[WebHTMLView insertTab:]): Call insertText: to save code and so we get WebViewInsertActionTyped
        instead of WebViewInsertActionPasted.
        (-[WebHTMLView changeCaseOfLetter:]): Removed, since NSTextView doesn't implement this method.
        Added to list of methods to possibly implement later in the file.
        (-[WebHTMLView _deleteWithDirection:granularity:killRing:]): Added.
        (-[WebHTMLView deleteForward:]): Implemented. This makes Control-D work.
        (-[WebHTMLView deleteBackwardByDecomposingPreviousCharacter:]): Implemented by just calling
        deleteBackward for now; probably better than doing nothing.
        (-[WebHTMLView deleteWordForward:]): Changed to call new _delete method above. Fixes things
        so that we delete the selection if there is one, get the appropriate delegate calls, handle
        the kill ring properly, and don't do any selection if we can't delete.
        (-[WebHTMLView deleteWordBackward:]): Ditto.
        (-[WebHTMLView deleteToBeginningOfLine:]): Ditto.
        (-[WebHTMLView deleteToEndOfLine:]): Ditto.
        (-[WebHTMLView deleteToBeginningOfParagraph:]): Ditto.
        (-[WebHTMLView deleteToEndOfParagraph:]): Ditto. Added additional behavior needed since this
        is bound to Control-K, so it's not really just delete to end of paragraph.
        (-[WebHTMLView insertNewlineIgnoringFieldEditor:]): Added. Calls insertNewline:.
        (-[WebHTMLView insertTabIgnoringFieldEditor:]): Added. Calls insertTab:.
        (-[WebHTMLView subscript:]): Added.
        (-[WebHTMLView superscript:]): Added.
        (-[WebHTMLView unscript:]): Added.
        (-[WebHTMLView underline:]): Added.
        (-[WebHTMLView yank:]): Added.
        (-[WebHTMLView yankAndSelect:]): Added. Calls _insertText.
        (-[WebHTMLView _arrowKeyDownEventSelectorIfPreprocessing:]): Added. Part of workaround for
        control-arrow key trouble.
        (-[WebHTMLView respondsToSelector:]): Added. More of workaround.
        (-[WebHTMLView nextResponder:]): Added. More of workaround.
        (-[WebHTMLView _selectionChanged]): Set startNewKillRingSequence flag, so that new deletions
        will create a new kill ring entry.
        (-[WebHTMLView _updateFontPanel]): Remove a bunch of code here that wasn't working very well
        because it walked a DOM range incorrectly, and instead use the new method that does all the
        right stuff on the other side of the bridge.
        (-[WebHTMLView _insertText:selectInsertedText:]): Added new helper method for use by both
        insertText and yankAndSelect, with most of the guts of insertText and one additional parameter.
        (-[WebHTMLView insertText:]): Call the new _insertText.

        * WebView.subproj/WebView.m: Use macros to make the forwarding from WebView more terse.
        Updated the list to include a few methods it didn't before.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@7464 268f45cc-cd09-0410-ab3c-d52691b4dbfc

34 files changed:
WebCore/ChangeLog-2005-08-23
WebCore/khtml/editing/SelectionController.cpp
WebCore/khtml/editing/SelectionController.h
WebCore/khtml/editing/htmlediting.cpp
WebCore/khtml/editing/htmlediting.h
WebCore/khtml/editing/htmlediting_impl.cpp
WebCore/khtml/editing/htmlediting_impl.h
WebCore/khtml/editing/jsediting.cpp
WebCore/khtml/editing/selection.cpp
WebCore/khtml/editing/selection.h
WebCore/khtml/khtml_part.cpp
WebCore/khtml/misc/shared.h
WebCore/khtml/rendering/render_canvas.cpp
WebCore/khtml/rendering/render_style.h
WebCore/khtml/xml/dom_docimpl.cpp
WebCore/khtml/xml/dom_docimpl.h
WebCore/khtml/xml/dom_nodeimpl.cpp
WebCore/khtml/xml/dom_nodeimpl.h
WebCore/khtml/xml/dom_position.cpp
WebCore/khtml/xml/dom_position.h
WebCore/khtml/xml/dom_selection.cpp
WebCore/khtml/xml/dom_selection.h
WebCore/khtml/xml/dom_stringimpl.cpp
WebCore/kwq/KWQKHTMLPart.h
WebCore/kwq/KWQKHTMLPart.mm
WebCore/kwq/WebCoreBridge.h
WebCore/kwq/WebCoreBridge.mm
WebKit/ChangeLog
WebKit/WebView.subproj/WebFrameView.m
WebKit/WebView.subproj/WebFrameViewInternal.h
WebKit/WebView.subproj/WebFrameViewPrivate.h
WebKit/WebView.subproj/WebHTMLView.m
WebKit/WebView.subproj/WebHTMLViewInternal.h
WebKit/WebView.subproj/WebView.m

index 61278f6fce41d829ccb0250f8aa9e13338c459f8..e33ca449dad2672939f9e10d3239b1e3f709ed25 100644 (file)
@@ -1,3 +1,172 @@
+2004-09-06  Darin Adler  <darin@apple.com>
+
+        Reviewed by John.
+
+        - fixed <rdar://problem/3512066> REGRESSION (Mail): Select All highlights only part of the content, though all is copied
+        - fixed <rdar://problem/3157025> REGRESSION (Mail): Should select paragraph instead of line on triple-click
+        - fixed <rdar://problem/3735048> REGRESSION (Mail): HTML editing must select newline on triple click
+        - fixed <rdar://problem/3788872> REGRESSION (Mail): ctrl-a emacs key binding does not work (move to start of paragraph)
+        - fixed <rdar://problem/3788881> REGRESSION (Mail): ctrl-e emacs key binding does not work (move to end of paragraph)
+        - fixed <rdar://problem/3789931> REGRESSION (Mail): typing style lost when you backspace
+        - added code to select inserted text to help WebKit implement yankAndSelect:
+        - did some clean-up on editing commands code
+        
+        * khtml/khtml_part.cpp:
+        (KHTMLPart::handleMousePressEventTripleClick): Expand to a paragraph, not a line.
+        (KHTMLPart::selectAll): Remove a lot of unneeded code and just expand to document using Selection.
+        (KHTMLPart::selectionHasStyle): Remove check that limited us to only HTML elements, and add
+        a check for nil. Together, these two changes fix a few cases I ran into during testing.
+
+        * khtml/rendering/render_canvas.cpp: (RenderCanvas::setSelection): Fix test that was checking if
+        the old selection spanned multiple objects when it really should have checked whether the new
+        new selection spans multiple objects. This caused the select all redraw bug.
+
+        * khtml/editing/htmlediting.h: Fixed indenting. Remove command IDs, replacing them with a couple
+        of specific type checking methods, and SharedCommandImpl, using EditCommandImpl directly instead.
+        Got rid of virtual functions, since the command objects have no data members other than the ones
+        inherited from SharedPtr. Removed explicit copy constructors and destructors. Removed unneeded
+        empty constructors. Removed inline directives that were having no effect. Added some boolean
+        selectInsertedText parameter to some commands. Made almost all member functions const, because
+        the const here applies to the command object which is a smart pointer, not the pointed-to
+        command implementation object.
+        * khtml/editing/htmlediting.cpp:
+        (khtml::EditCommand::EditCommand): Changed since SharedPtr is now a pointer to EditCommandImpl.
+        (khtml::EditCommand::operator=): Added. By defining this explicitly, we don't need the class
+        that we are pointing to defined in the header, which lets us get rid of SharedCommandImpl.
+        (khtml::EditCommand::isInputTextCommand): Added.
+        (khtml::EditCommand::isTypingCommand): Added.
+        (khtml::EditCommand::setParent): Updated since EditCommandImpl.setParent takes an impl pointer now.
+        (khtml::EditCommand::handle): Removed, since it's identical to get().
+        (khtml::InputTextCommand::input): Added selectInsertedText parameter.
+        (khtml::TypingCommand::TypingCommand): Ditto.
+        (khtml::TypingCommand::insertText): Ditto.
+        (khtml::TypingCommand::isOpenForMoreTypingCommand): Use isTypingCommand instead of commandID.
+        (khtml::TypingCommand::closeTyping): Change parameter type to const reference.
+
+        * khtml/editing/htmlediting_impl.h: Made StyleChange functions be const member functions.
+        Changed parent() and setParent() to take and return EditCommandImpl pointers and be inlined.
+        Added virtual isInputTextCommand, isTypingCommand, and preservesTypingStyle functions.
+        Removed commandID functions and unneeded explicit destructors. Removed unneeded doApply
+        function declaration in CompositeEditCommandImpl. Changed type of applyTypingStyle to return
+        a node, not necessarily an element. Removed private execute function from InputTextCommandImpl.
+        Added selectInsertedText to InputTextCommandImpl's input function and TypingCommandImpl's constructor
+        and insertText function.
+        * khtml/editing/htmlediting_impl.cpp:
+        (khtml::StyleChange::StyleChange): Don't bother initializing the booleans since init handles that.
+        (khtml::StyleChange::init): Added code to strip whitespace, and tweaked how the function does its job.
+        (khtml::StyleChange::currentlyHasStyle): Added a check for null value, which can happen for properties
+        where we don't have computed style implemented yet.
+        (khtml::EditCommandImpl::EditCommandImpl): Updated since there is no SharedCommandImpl any more.
+        (khtml::EditCommandImpl::apply): Call new preservesTypingStyle function rather than checking
+        the command ID. This preserves typing style when deleting with the keyboard.
+        (khtml::EditCommandImpl::setStartingSelection): Changed since parent is now a EditCommandImpl.
+        Also rewrote to use for loop so it's simpler-looking.
+        (khtml::EditCommandImpl::setEndingSelection): Ditto.
+        (khtml::EditCommandImpl::setTypingStyle): Ditto.
+        (khtml::EditCommandImpl::preservesTypingStyle): Added. Returns false.
+        (khtml::EditCommandImpl::isInputTextCommand): Added. Returns false.
+        (khtml::EditCommandImpl::isTypingCommand): Added. Returns false.
+        (khtml::CompositeEditCommandImpl::applyTypingStyle): Changed this function to return a node rather
+        than an element. Also change it so it returns the child as-is if the style change has nothing in it.
+        (khtml::AppendNodeCommandImpl::~AppendNodeCommandImpl): Removed unneeded null checks.
+        (khtml::DeleteSelectionCommandImpl::preservesTypingStyle): Added. Returns true.
+        (khtml::DeleteTextCommandImpl::~DeleteTextCommandImpl): Removed unneeded null checks.
+        (khtml::InputTextCommandImpl::input): Renamed execute function to input and got rid of the additional
+        level of indirection, since it was the only caller. Added selectInsertedText parameter and changed
+        the code so it will respect it.
+        (khtml::InputTextCommandImpl::isInputTextCommand): Added. Returns true.
+        (khtml::InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl): Removed unneeded null checks.
+        (khtml::JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl): Ditto.
+        (khtml::MoveSelectionCommandImpl::MoveSelectionCommandImpl): Hold a reference to the fragment
+        while the command exists.
+        (khtml::MoveSelectionCommandImpl::~MoveSelectionCommandImpl): Release the fragment.
+        (khtml::RemoveNodeCommandImpl::~RemoveNodeCommandImpl): Remove unneeded null checks.
+        (khtml::RemoveNodeCommandImpl::doUnapply): Take advantage of defined behavior of insertBefore when
+        the node to insert before is 0 (means the same thing as appendChild).
+        (khtml::RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl):
+        Remove unneeded null check.
+        (khtml::ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl): Hold a reference to the fragment
+        while the command exists.
+        (khtml::ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl): Release the fragment.
+        (khtml::SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl): Remove unneeded null check.
+        (khtml::SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl): Remove unneeded null check.
+        (khtml::TypingCommandImpl::TypingCommandImpl): Added selectInsertedText parameter.
+        (khtml::TypingCommandImpl::doApply): Pass along selectInsertedText parameter.
+        (khtml::TypingCommandImpl::insertText): Ditto.
+        (khtml::TypingCommandImpl::preservesTypingStyle): Added. Returns true for DeleteKey.
+        (khtml::TypingCommandImpl::isTypingCommand): Added. Returns true.
+
+        * khtml/editing/jsediting.cpp: Add a cut at implementing the underline command. May not work
+        perfectly yet because text-decoration is not inherited the way, say, font-weight is.
+
+        * khtml/xml/dom_selection.h: Removed some unneeded friend declarations for comparison operators
+        that use only public members. Added PARAGRAPH_BOUNDARY.
+        * khtml/xml/dom_selection.cpp:
+        (DOM::Selection::modifyExtendingRightForward): Implemented paragraph boundary case, simplified line
+        boundary case (by changing parameter types and function names, not the algorithm), and made
+        paragraph case fall into line case for now.
+        (DOM::Selection::modifyMovingRightForward): Ditto.
+        (DOM::Selection::modifyExtendingLeftBackward): Ditto.
+        (DOM::Selection::modifyMovingLeftBackward): Ditto.
+        (DOM::Selection::validate): Implemented paragraph case and paragraph boundary case, simplified the
+        line case as above and also fixed the document case.
+        (DOM::startOfFirstRunAt): Changed to use DOM::Position instead of node
+        offset pairs. Also renamed and got rid of separate bool to indicate "not found".
+        (DOM::endOfLastRunAt): Ditto.
+        (DOM::selectionForLine): Ditto.
+
+        * khtml/xml/dom_position.h: Added startParagraphBoundary and endParagraphBoundary.
+        Also used enums instead of bools in a couple of places. Removed some unneeded friend
+        declarations for comparison operators that use only public members.
+        * khtml/xml/dom_position.cpp:
+        (DOM::Position::Position): Coding style tweak.
+        (DOM::Position::startParagraphBoundary): Added.
+        (DOM::Position::endParagraphBoundary): Added.
+        (DOM::Position::upstream): Use enum parameter instead of bool.
+        (DOM::Position::downstream): Ditto.
+        (DOM::Position::rendersOnSameLine): Removed unused function. Noticed some backwards logic
+        here, and removed it rather than fixing it.
+
+        * khtml/xml/dom_nodeimpl.h: Added traversePreviousNodePostOrder.
+        * khtml/xml/dom_nodeimpl.cpp:
+        (NodeImpl::traversePreviousNodePostOrder): Added. For some uses, this function is more analogous
+        to traverseNextNode in reverse than traversePreviousNode, which does a backwards pre-order traversal.
+
+        * kwq/WebCoreBridge.h: Replaced fontForCurrentPosition method with fontForSelection:, added
+        selectInsertedText parameter to insertText method, and added selectionStartHasStyle: method.
+        Add WebSelectToParagraphBoundary.
+        * kwq/WebCoreBridge.mm:
+        (-[WebCoreBridge setSelectedDOMRange:affinity:]): Added workaround for bug where isRenderedContent
+        returns false for <br> elements at the ends of lines.
+        (-[WebCoreBridge insertText:selectInsertedText:]): Added selectInsertedText parameter, passing it
+        along to TypingCommand::insertText.
+        (-[WebCoreBridge selectionStartHasStyle:]): Added. Calls code in the part that does the real work.
+        (-[WebCoreBridge fontForSelection:]): Ditto.
+
+        * kwq/KWQKHTMLPart.h: Renamed fontForCurrentPosition to fontForSelection, and added a feature
+        where it tells us whether there are multiple fonts in the selection or not.
+        * kwq/KWQKHTMLPart.mm:
+        (KWQKHTMLPart::fontForSelection): Rewrote this to be a bit simpler, and added code to detect
+        whether there are multiple fonts in the selection.
+        (KWQKHTMLPart::didTellBridgeAboutLoad): Use a global character rather than a bad pointer here.
+        Better not to rely on undefined behavior.
+        (KWQKHTMLPart::documentFragmentWithText): Use lowercase HTML. I believe this works better when
+        the document is XML, and we should probably visit all callers and change them.
+        (KWQKHTMLPart::registerCommandForUndo): Call get() instead of handle().
+        (KWQKHTMLPart::registerCommandForRedo): Call get() instead of handle().
+
+        * khtml/misc/shared.h: Added a private, non-implemented copy constructor and assignment
+        operator to Shared<> and TreeShared<> to prevent copying reference counts by accident.
+        * khtml/rendering/render_style.h: Get rid of Shared<BorderData>, since it's not used that way.
+        This makes BorderData smaller, and allows it to compile with the change to Shared<>.
+        * khtml/xml/dom_docimpl.h: Remove unneeded copy constructor on DocumentFragmentImpl,
+        which ran afoul of the change to Shared<> and was unused.
+        * khtml/xml/dom_docimpl.cpp: Ditto.
+        * khtml/xml/dom_stringimpl.cpp:
+        (DOM::DOMStringImpl::empty): Changed code around so it doesn't run afoul of the change to
+        Shared<>. The old code was copying a DOMStringImpl.
+        (DOM::DOMStringImpl::ascii): Added code to null-terminate the string buffer.
+
 2004-09-06  Darin Adler  <darin@apple.com>
 
         Reviewed by John.
index 75bba6361a22c8c9896a1c331bd26036489ed183..17a8182534c9e38527e1994a0c97d30ca16b1cb7 100644 (file)
@@ -58,9 +58,7 @@ using khtml::RenderText;
 
 namespace DOM {
 
-static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset);
-static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset);
-static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection);
+static Selection selectionForLine(const Position &position);
 
 static inline Position &emptyPosition()
 {
@@ -208,23 +206,22 @@ Position Selection::modifyExtendingRightForward(ETextGranularity granularity)
         case WORD:
             pos = pos.nextWordPosition();
             break;
+        case PARAGRAPH:
+            // "Next paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = pos.nextLinePosition(xPosForVerticalArrowNavigation(EXTENT));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT: {
             ElementImpl *elem = start().node()->getDocument()->documentElement();
             pos = Position(elem, elem->childNodeCount());
             break;
         }
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(end().node(), end().offset(), selection);
-            pos = selection.end();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(end()).end();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = end().endParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -243,23 +240,22 @@ Position Selection::modifyMovingRightForward(ETextGranularity granularity)
         case WORD:
             pos = extent().nextWordPosition();
             break;
+        case PARAGRAPH:
+            // "Next paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = end().nextLinePosition(xPosForVerticalArrowNavigation(END, state() == RANGE));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT: {
             ElementImpl *elem = start().node()->getDocument()->documentElement();
             pos = Position(elem, elem->childNodeCount());
             break;
         }
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(end().node(), end().offset(), selection);
-            pos = selection.end();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(end()).end();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = end().endParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -278,21 +274,20 @@ Position Selection::modifyExtendingLeftBackward(ETextGranularity granularity)
         case WORD:
             pos = pos.previousWordPosition();
             break;
+        case PARAGRAPH:
+            // "Previous paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = pos.previousLinePosition(xPosForVerticalArrowNavigation(EXTENT));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT:
             pos = Position(start().node()->getDocument()->documentElement(), 0);
             break;
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(start().node(), start().offset(), selection);
-            pos = selection.start();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(start()).start();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = start().startParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -311,21 +306,20 @@ Position Selection::modifyMovingLeftBackward(ETextGranularity granularity)
         case WORD:
             pos = extent().previousWordPosition();
             break;
+        case PARAGRAPH:
+            // "Previous paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = start().previousLinePosition(xPosForVerticalArrowNavigation(START, state() == RANGE));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT:
             pos = Position(start().node()->getDocument()->documentElement(), 0);
             break;
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(start().node(), start().offset(), selection);
-            pos = selection.start();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(start()).start();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = start().startParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -653,6 +647,7 @@ void Selection::validate(ETextGranularity granularity)
                 assignStartAndEnd(extent(), base());
             break;
         case WORD: {
+            // FIXME: This doesn't handle words that cross node boundaries.
             int baseStartOffset = base().offset();
             int baseEndOffset = base().offset();
             int extentStartOffset = extent().offset();
@@ -683,36 +678,47 @@ void Selection::validate(ETextGranularity granularity)
         case LINE_BOUNDARY: {
             Selection baseSelection = *this;
             Selection extentSelection = *this;
-            if (base().notEmpty() && (base().node()->nodeType() == Node::TEXT_NODE || base().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
-                if (startAndEndLineNodesIncludingNode(base().node(), base().offset(), baseSelection)) {
-                    assignStart(Position(baseSelection.base().node(), baseSelection.base().offset()));
-                    assignEnd(Position(baseSelection.extent().node(), baseSelection.extent().offset()));
-                }
+            Selection baseLine = selectionForLine(base());
+            if (baseLine.notEmpty()) {
+                baseSelection = baseLine;
             }
-            if (extent().notEmpty() && (extent().node()->nodeType() == Node::TEXT_NODE || extent().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
-                if (startAndEndLineNodesIncludingNode(extent().node(), extent().offset(), extentSelection)) {
-                    assignStart(Position(extentSelection.base().node(), extentSelection.base().offset()));
-                    assignEnd(Position(extentSelection.extent().node(), extentSelection.extent().offset()));
-                }
+            Selection extentLine = selectionForLine(extent());
+            if (extentLine.notEmpty()) {
+                extentSelection = extentLine;
             }
             if (m_baseIsStart) {
                 assignStart(baseSelection.start());
                 assignEnd(extentSelection.end());
-            }
-            else {
+            } else {
                 assignStart(extentSelection.start());
                 assignEnd(baseSelection.end());
             }
+            break;
         }
         case PARAGRAPH:
-            // not implemented
+            if (m_baseIsStart) {
+                assignStart(base().startParagraphBoundary());
+                assignEnd(extent().endParagraphBoundary(IncludeLineBreak));
+            } else {
+                assignStart(extent().startParagraphBoundary());
+                assignEnd(base().endParagraphBoundary(IncludeLineBreak));
+            }
             break;
         case DOCUMENT: {
-            NodeImpl *topNode = start().node()->getDocument()->documentElement();
-            assignStart(Position(topNode, 0));
-            assignEnd(Position(topNode, 1));
+            NodeImpl *de = start().node()->getDocument()->documentElement();
+            assignStart(Position(de, 0).equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
+            assignEnd(Position(de, de->childNodeCount()).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
             break;
         }
+        case PARAGRAPH_BOUNDARY:
+            if (m_baseIsStart) {
+                assignStart(base().startParagraphBoundary());
+                assignEnd(extent().endParagraphBoundary());
+            } else {
+                assignStart(extent().startParagraphBoundary());
+                assignEnd(base().endParagraphBoundary());
+            }
+            break;
     }
 
     // adjust the state
@@ -823,104 +829,94 @@ bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2) const
     return result;
 }
 
-static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset)
+static Position startOfFirstRunAt(RenderObject *renderNode, int y)
 {
     for (RenderObject *n = renderNode; n; n = n->nextSibling()) {
         if (n->isText()) {
-            RenderText *textRenderer = static_cast<khtml::RenderText *>(n);
-            for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
-                if (box->m_y == y) {
-                    startNode = textRenderer->element();
-                    startOffset = box->m_start;
-                    return true;
-                }
-            }
+            RenderText *textRenderer = static_cast<RenderText *>(n);
+            for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox())
+                if (box->m_y == y)
+                    return Position(textRenderer->element(), box->m_start);
         }
         
-        if (firstRunAt(n->firstChild(), y, startNode, startOffset)) {
-            return true;
-        }
+        Position position = startOfFirstRunAt(n->firstChild(), y);
+        if (position.notEmpty())
+            return position;
     }
     
-    return false;
+    return Position();
 }
 
-static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset)
+static Position endOfLastRunAt(RenderObject *renderNode, int y)
 {
     RenderObject *n = renderNode;
-    if (!n) {
-        return false;
-    }
-    RenderObject *next;
-    while ((next = n->nextSibling())) {
-        n = next;
-    }
+    if (!n)
+        return Position();
+    if (RenderObject *parent = n->parent())
+        n = parent->lastChild();
     
     while (1) {
-        if (lastRunAt(n->firstChild(), y, endNode, endOffset)) {
-            return true;
-        }
-    
+        Position position = endOfLastRunAt(n->firstChild(), y);
+        if (position.notEmpty())
+            return position;
+        
         if (n->isText()) {
-            RenderText *textRenderer =  static_cast<khtml::RenderText *>(n);
-            for (InlineTextBox* box = textRenderer->lastTextBox(); box; box = box->prevTextBox()) {
-                if (box->m_y == y) {
-                    endNode = textRenderer->element();
-                    endOffset = box->m_start + box->m_len;
-                    return true;
-                }
-            }
+            RenderText *textRenderer = static_cast<RenderText *>(n);
+            for (InlineTextBox* box = textRenderer->lastTextBox(); box; box = box->prevTextBox())
+                if (box->m_y == y)
+                    return Position(textRenderer->element(), box->m_start + box->m_len);
         }
         
-        if (n == renderNode) {
-            return false;
-        }
+        if (n == renderNode)
+            return Position();
         
         n = n->previousSibling();
     }
 }
 
-static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection)
+static Selection selectionForLine(const Position &position)
 {
-    if (node && (node->nodeType() == Node::TEXT_NODE || node->nodeType() == Node::CDATA_SECTION_NODE)) {
-        int pos;
-        int selectionPointY;
-        RenderText *renderer = static_cast<RenderText *>(node->renderer());
-        InlineTextBox * run = renderer->findNextInlineTextBox( offset, pos );
-        DOMString t = node->nodeValue();
-        
-        if (!run)
-            return false;
-            
-        selectionPointY = run->m_y;
-        
-        // Go up to first non-inline element.
-        khtml::RenderObject *renderNode = renderer;
-        while (renderNode && renderNode->isInline())
-            renderNode = renderNode->parent();
-        
-        renderNode = renderNode->firstChild();
-        
-        NodeImpl *startNode = 0;
-        NodeImpl *endNode = 0;
-        long startOffset;
-        long endOffset;
+    NodeImpl *node = position.node();
+
+    if (!node)
+        return Selection();
+
+    switch (node->nodeType()) {
+        case Node::TEXT_NODE:
+        case Node::CDATA_SECTION_NODE:
+            break;
+        default:
+            return Selection();
+    }
+
+    RenderText *renderer = static_cast<RenderText *>(node->renderer());
+
+    int pos;
+    InlineTextBox *run = renderer->findNextInlineTextBox(position.offset(), pos);
+    if (!run)
+        return Selection();
         
-        // Look for all the first child in the block that is on the same line
-        // as the selection point.
-        if (!firstRunAt (renderNode, selectionPointY, startNode, startOffset))
-            return false;
+    int selectionPointY = run->m_y;
     
-        // Look for all the last child in the block that is on the same line
-        // as the selection point.
-        if (!lastRunAt (renderNode, selectionPointY, endNode, endOffset))
-            return false;
-        
-        selection.moveTo(Position(startNode, startOffset), Position(endNode, endOffset));
-        
-        return true;
-    }
-    return false;
+    // Go up to first non-inline element.
+    RenderObject *renderNode = renderer;
+    while (renderNode && renderNode->isInline())
+        renderNode = renderNode->parent();
+    renderNode = renderNode->firstChild();
+    
+    // Look for all the first child in the block that is on the same line
+    // as the selection point.
+    Position start = startOfFirstRunAt(renderNode, selectionPointY);
+    if (start.isEmpty())
+        return Selection();
+
+    // Look for all the last child in the block that is on the same line
+    // as the selection point.
+    Position end = endOfLastRunAt(renderNode, selectionPointY);
+    if (end.isEmpty())
+        return Selection();
+    
+    return Selection(start, end);
 }
 
 void Selection::debugRenderer(RenderObject *r, bool selected) const
index b5f81705c11aa07f9cf8ac22b5617f28ce84f380..c38bd9d88ea660920fdb808aa4b3eadbc0adc49a 100644 (file)
@@ -48,7 +48,7 @@ public:
     enum EState { NONE, CARET, RANGE };
     enum EAlter { MOVE, EXTEND };
     enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT };
-    enum ETextGranularity { CHARACTER, WORD, LINE, PARAGRAPH, DOCUMENT, LINE_BOUNDARY };
+    enum ETextGranularity { CHARACTER, WORD, LINE, PARAGRAPH, DOCUMENT, LINE_BOUNDARY, PARAGRAPH_BOUNDARY };
 
     Selection();
     Selection(const Range &);
@@ -100,9 +100,6 @@ public:
     Selection &operator=(const Range &r) { moveTo(r); return *this; }
     Selection &operator=(const Position &r) { moveTo(r); return *this; }
     
-    friend bool operator==(const Selection &a, const Selection &b);
-    friend bool operator!=(const Selection &a, const Selection &b);
-    
     friend class KHTMLPart;
 
 #ifndef NDEBUG
index fb440355488ff07dd8c5fc0c382a67ab7482a263..7c97d201b6488d9cd67832229ab355d3f66770c8 100644 (file)
@@ -36,7 +36,6 @@
 
 #if APPLE_CHANGES
 #include "KWQAssertions.h"
-#include "KWQLogging.h"
 #endif
 
 using DOM::CSSStyleDeclarationImpl;
@@ -50,11 +49,7 @@ using DOM::Selection;
 using DOM::TextImpl;
 
 #if !APPLE_CHANGES
-#define ASSERT(assertion) ((void)0)
-#define ASSERT_WITH_MESSAGE(assertion, formatAndArgs...) ((void)0)
-#define ASSERT_NOT_REACHED() ((void)0)
-#define LOG(channel, formatAndArgs...) ((void)0)
-#define ERROR(formatAndArgs...) ((void)0)
+#define ASSERT(assertion) assert(assertion)
 #endif
 
 #define IF_IMPL_NULL_RETURN_ARG(arg) do { \
@@ -71,15 +66,15 @@ namespace khtml {
 //------------------------------------------------------------------------------------------
 // EditCommand
 
-EditCommand::EditCommand() : SharedPtr<SharedCommandImpl>()
+EditCommand::EditCommand()
 {
 }
 
-EditCommand::EditCommand(EditCommandImpl *impl) : SharedPtr<SharedCommandImpl>(impl)
+EditCommand::EditCommand(EditCommandImpl *impl) : SharedPtr<EditCommandImpl>(impl)
 {
 }
 
-EditCommand::EditCommand(const EditCommand &o) : SharedPtr<SharedCommandImpl>(o.get())
+EditCommand::EditCommand(const EditCommand &o) : SharedPtr<EditCommandImpl>(o)
 {
 }
 
@@ -87,10 +82,10 @@ EditCommand::~EditCommand()
 {
 }
 
-int EditCommand::commandID() const
+EditCommand &EditCommand::operator=(const EditCommand &c)
 {
-    IF_IMPL_NULL_RETURN_ARG(0);        
-    return get()->commandID();
+    static_cast<SharedPtr<EditCommandImpl> &>(*this) = c;
+    return *this;
 }
 
 bool EditCommand::isCompositeStep() const
@@ -99,29 +94,31 @@ bool EditCommand::isCompositeStep() const
     return get()->isCompositeStep();
 }
 
-bool EditCommand::isNull() const
+bool EditCommand::isInputTextCommand() const
 {
-    return get() == 0;
+    IF_IMPL_NULL_RETURN_ARG(false);        
+    return get()->isInputTextCommand();
 }
 
-bool EditCommand::notNull() const
+bool EditCommand::isTypingCommand() const
 {
-    return !isNull();
+    IF_IMPL_NULL_RETURN_ARG(false);        
+    return get()->isTypingCommand();
 }
 
-void EditCommand::apply()
+void EditCommand::apply() const
 {
     IF_IMPL_NULL_RETURN;
     get()->apply();
 }
 
-void EditCommand::unapply()
+void EditCommand::unapply() const
 {
     IF_IMPL_NULL_RETURN;
     get()->unapply();
 }
 
-void EditCommand::reapply()
+void EditCommand::reapply() const
 {
     IF_IMPL_NULL_RETURN;
     get()->reapply();
@@ -145,13 +142,13 @@ Selection EditCommand::endingSelection() const
     return get()->endingSelection();
 }
 
-void EditCommand::setStartingSelection(const Selection &s)
+void EditCommand::setStartingSelection(const Selection &s) const
 {
     IF_IMPL_NULL_RETURN;
     get()->setStartingSelection(s);
 }
 
-void EditCommand::setEndingSelection(const Selection &s)
+void EditCommand::setEndingSelection(const Selection &s) const
 {
     IF_IMPL_NULL_RETURN;
     get()->setEndingSelection(s);
@@ -163,7 +160,7 @@ CSSStyleDeclarationImpl *EditCommand::typingStyle() const
     return get()->typingStyle();
 }
 
-void EditCommand::setTypingStyle(CSSStyleDeclarationImpl *style)
+void EditCommand::setTypingStyle(CSSStyleDeclarationImpl *style) const
 {
     IF_IMPL_NULL_RETURN;
     get()->setTypingStyle(style);
@@ -175,15 +172,10 @@ EditCommand EditCommand::parent() const
     return get()->parent();
 }
 
-void EditCommand::setParent(const EditCommand &cmd)
+void EditCommand::setParent(const EditCommand &cmd) const
 {
     IF_IMPL_NULL_RETURN;
-    get()->setParent(cmd);
-}
-
-EditCommandImpl *EditCommand::handle() const
-{
-    return static_cast<EditCommandImpl *>(get());
+    get()->setParent(cmd.get());
 }
 
 EditCommand &EditCommand::emptyCommand()
@@ -195,23 +187,10 @@ EditCommand &EditCommand::emptyCommand()
 //------------------------------------------------------------------------------------------
 // CompositeEditCommand
 
-CompositeEditCommand::CompositeEditCommand() : EditCommand() 
-{
-}
-
 CompositeEditCommand::CompositeEditCommand(CompositeEditCommandImpl *impl) : EditCommand(impl) 
 {
 }
 
-CompositeEditCommand::CompositeEditCommand(const CompositeEditCommand &o) 
-    : EditCommand(o.impl()) 
-{
-}
-
-CompositeEditCommand::~CompositeEditCommand()
-{
-}
-
 CompositeEditCommandImpl *CompositeEditCommand::impl() const
 {
     return static_cast<CompositeEditCommandImpl *>(get());
@@ -227,10 +206,6 @@ AppendNodeCommand::AppendNodeCommand(DocumentImpl *document, NodeImpl *appendChi
 {
 }
 
-AppendNodeCommand::~AppendNodeCommand()
-{
-}
-
 AppendNodeCommandImpl *AppendNodeCommand::impl() const
 {
     return static_cast<AppendNodeCommandImpl *>(get());
@@ -256,10 +231,6 @@ ApplyStyleCommand::ApplyStyleCommand(DocumentImpl *document, CSSStyleDeclaration
 {
 }
 
-ApplyStyleCommand::~ApplyStyleCommand()
-{
-}
-
 ApplyStyleCommandImpl *ApplyStyleCommand::impl() const
 {
     return static_cast<ApplyStyleCommandImpl *>(get());
@@ -284,10 +255,6 @@ DeleteSelectionCommand::DeleteSelectionCommand(DocumentImpl *document, const Sel
 {
 }
 
-DeleteSelectionCommand::~DeleteSelectionCommand()
-{
-}
-       
 DeleteSelectionCommandImpl *DeleteSelectionCommand::impl() const
 {
     return static_cast<DeleteSelectionCommandImpl *>(get());
@@ -301,15 +268,6 @@ DeleteTextCommand::DeleteTextCommand(DocumentImpl *document, TextImpl *node, lon
 {
 }
 
-DeleteTextCommand::DeleteTextCommand(const DeleteTextCommand &o)
-    : EditCommand(o.impl())
-{
-}
-
-DeleteTextCommand::~DeleteTextCommand()
-{
-}
-
 DeleteTextCommandImpl *DeleteTextCommand::impl() const
 {
     return static_cast<DeleteTextCommandImpl *>(get());
@@ -341,10 +299,6 @@ InputNewlineCommand::InputNewlineCommand(DocumentImpl *document)
 {
 }
 
-InputNewlineCommand::~InputNewlineCommand() 
-{
-}
-
 InputNewlineCommandImpl *InputNewlineCommand::impl() const
 {
     return static_cast<InputNewlineCommandImpl *>(get());
@@ -358,10 +312,6 @@ InputTextCommand::InputTextCommand(DocumentImpl *document)
 {
 }
 
-InputTextCommand::~InputTextCommand() 
-{
-}
-
 InputTextCommandImpl *InputTextCommand::impl() const
 {
     return static_cast<InputTextCommandImpl *>(get());
@@ -373,10 +323,10 @@ void InputTextCommand::deleteCharacter()
     impl()->deleteCharacter();
 }
 
-void InputTextCommand::input(const DOM::DOMString &text)
+void InputTextCommand::input(const DOM::DOMString &text, bool selectInsertedText)
 {
     IF_IMPL_NULL_RETURN;
-    impl()->input(text);
+    impl()->input(text, selectInsertedText);
 }
 
 unsigned long InputTextCommand::charactersAdded() const
@@ -388,24 +338,11 @@ unsigned long InputTextCommand::charactersAdded() const
 //------------------------------------------------------------------------------------------
 // InsertNodeBeforeCommand
 
-InsertNodeBeforeCommand::InsertNodeBeforeCommand() : EditCommand()
-{
-}
-
 InsertNodeBeforeCommand::InsertNodeBeforeCommand(DocumentImpl *document, NodeImpl *insertChild, NodeImpl *refChild)
     : EditCommand(new InsertNodeBeforeCommandImpl(document, insertChild, refChild))
 {
 }
 
-InsertNodeBeforeCommand::InsertNodeBeforeCommand(const InsertNodeBeforeCommand &o)
-    : EditCommand(new InsertNodeBeforeCommandImpl(o.document(), o.insertChild(), o.refChild()))
-{
-}
-
-InsertNodeBeforeCommand::~InsertNodeBeforeCommand()
-{
-}
-
 InsertNodeBeforeCommandImpl *InsertNodeBeforeCommand::impl() const
 {
     return static_cast<InsertNodeBeforeCommandImpl *>(get());
@@ -431,10 +368,6 @@ InsertTextCommand::InsertTextCommand(DocumentImpl *document, TextImpl *node, lon
 {
 }
 
-InsertTextCommand::~InsertTextCommand()
-{
-}
-
 InsertTextCommandImpl *InsertTextCommand::impl() const
 {
     return static_cast<InsertTextCommandImpl *>(get());
@@ -466,10 +399,6 @@ JoinTextNodesCommand::JoinTextNodesCommand(DocumentImpl *document, TextImpl *tex
 {
 }
 
-JoinTextNodesCommand::~JoinTextNodesCommand()
-{
-}
-
 JoinTextNodesCommandImpl *JoinTextNodesCommand::impl() const
 {
     return static_cast<JoinTextNodesCommandImpl *>(get());
@@ -495,10 +424,6 @@ ReplaceSelectionCommand::ReplaceSelectionCommand(DocumentImpl *document, DOM::Do
 {
 }
 
-ReplaceSelectionCommand::~ReplaceSelectionCommand() 
-{
-}
-
 ReplaceSelectionCommandImpl *ReplaceSelectionCommand::impl() const
 {
     return static_cast<ReplaceSelectionCommandImpl *>(get());
@@ -508,11 +433,7 @@ ReplaceSelectionCommandImpl *ReplaceSelectionCommand::impl() const
 // MoveSelectionCommand
 
 MoveSelectionCommand::MoveSelectionCommand(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position) 
-: CompositeEditCommand(new MoveSelectionCommandImpl(document, fragment, position))
-{
-}
-
-MoveSelectionCommand::~MoveSelectionCommand() 
+    : CompositeEditCommand(new MoveSelectionCommandImpl(document, fragment, position))
 {
 }
 
@@ -529,10 +450,6 @@ RemoveCSSPropertyCommand::RemoveCSSPropertyCommand(DocumentImpl *document, CSSSt
 {
 }
 
-RemoveCSSPropertyCommand::~RemoveCSSPropertyCommand()
-{
-}
-
 RemoveCSSPropertyCommandImpl *RemoveCSSPropertyCommand::impl() const
 {
     return static_cast<RemoveCSSPropertyCommandImpl *>(get());
@@ -558,10 +475,6 @@ RemoveNodeAttributeCommand::RemoveNodeAttributeCommand(DocumentImpl *document, E
 {
 }
 
-RemoveNodeAttributeCommand::~RemoveNodeAttributeCommand()
-{
-}
-
 RemoveNodeAttributeCommandImpl *RemoveNodeAttributeCommand::impl() const
 {
     return static_cast<RemoveNodeAttributeCommandImpl *>(get());
@@ -587,10 +500,6 @@ RemoveNodeCommand::RemoveNodeCommand(DocumentImpl *document, NodeImpl *node)
 {
 }
 
-RemoveNodeCommand::~RemoveNodeCommand()
-{
-}
-
 RemoveNodeCommandImpl *RemoveNodeCommand::impl() const
 {
     return static_cast<RemoveNodeCommandImpl *>(get());
@@ -610,10 +519,6 @@ RemoveNodePreservingChildrenCommand::RemoveNodePreservingChildrenCommand(Documen
 {
 }
 
-RemoveNodePreservingChildrenCommand::~RemoveNodePreservingChildrenCommand()
-{
-}
-
 RemoveNodePreservingChildrenCommandImpl *RemoveNodePreservingChildrenCommand::impl() const
 {
     return static_cast<RemoveNodePreservingChildrenCommandImpl *>(get());
@@ -633,10 +538,6 @@ SetNodeAttributeCommand::SetNodeAttributeCommand(DocumentImpl *document, Element
 {
 }
 
-SetNodeAttributeCommand::~SetNodeAttributeCommand()
-{
-}
-
 SetNodeAttributeCommandImpl *SetNodeAttributeCommand::impl() const
 {
     return static_cast<SetNodeAttributeCommandImpl *>(get());
@@ -668,10 +569,6 @@ SplitTextNodeCommand::SplitTextNodeCommand(DocumentImpl *document, TextImpl *tex
 {
 }
 
-SplitTextNodeCommand::~SplitTextNodeCommand()
-{
-}
-
 SplitTextNodeCommandImpl *SplitTextNodeCommand::impl() const
 {
     return static_cast<SplitTextNodeCommandImpl *>(get());
@@ -692,12 +589,8 @@ long SplitTextNodeCommand::offset() const
 //------------------------------------------------------------------------------------------
 // TypingCommand
 
-TypingCommand::TypingCommand(DocumentImpl *document, ETypingCommand commandType, const DOM::DOMString &textToInsert) 
-    : CompositeEditCommand(new TypingCommandImpl(document, commandType, textToInsert))
-{
-}
-
-TypingCommand::~TypingCommand() 
+TypingCommand::TypingCommand(DocumentImpl *d, ETypingCommand t, const DOM::DOMString &text, bool s) 
+    : CompositeEditCommand(new TypingCommandImpl(d, t, text, s))
 {
 }
 
@@ -706,7 +599,7 @@ TypingCommandImpl *TypingCommand::impl() const
     return static_cast<TypingCommandImpl *>(get());
 }
 
-void TypingCommand::insertText(DocumentImpl *document, const DOMString &text)
+void TypingCommand::insertText(DocumentImpl *document, const DOMString &text, bool selectInsertedText)
 {
     ASSERT(document);
     
@@ -715,10 +608,10 @@ void TypingCommand::insertText(DocumentImpl *document, const DOMString &text)
     
     EditCommand lastEditCommand = part->lastEditCommand();
     if (isOpenForMoreTypingCommand(lastEditCommand)) {
-        static_cast<TypingCommand &>(lastEditCommand).insertText(text);
+        static_cast<TypingCommand &>(lastEditCommand).insertText(text, selectInsertedText);
     }
     else {
-        TypingCommand typingCommand(document, InsertText, text);
+        TypingCommand typingCommand(document, InsertText, text, selectInsertedText);
         typingCommand.apply();
     }
 }
@@ -759,17 +652,17 @@ void TypingCommand::deleteKeyPressed(DocumentImpl *document)
 
 bool TypingCommand::isOpenForMoreTypingCommand(const EditCommand &cmd)
 {
-    return cmd.commandID() == TypingCommandID && 
+    return cmd.isTypingCommand() &&
         static_cast<const TypingCommand &>(cmd).openForMoreTyping();
 }
 
-void TypingCommand::closeTyping(EditCommand cmd)
+void TypingCommand::closeTyping(const EditCommand &cmd)
 {
     if (isOpenForMoreTypingCommand(cmd))
-        static_cast<TypingCommand &>(cmd).closeTyping();
+        static_cast<const TypingCommand &>(cmd).closeTyping();
 }
 
-void TypingCommand::closeTyping()
+void TypingCommand::closeTyping() const
 {
     IF_IMPL_NULL_RETURN;
     return impl()->closeTyping();
@@ -781,19 +674,19 @@ bool TypingCommand::openForMoreTyping() const
     return impl()->openForMoreTyping();
 }
 
-void TypingCommand::insertText(const DOMString &text)
+void TypingCommand::insertText(const DOMString &text, bool selectInsertedText) const
 {
     IF_IMPL_NULL_RETURN;
-    impl()->insertText(text);
+    impl()->insertText(text, selectInsertedText);
 }
 
-void TypingCommand::insertNewline()
+void TypingCommand::insertNewline() const
 {
     IF_IMPL_NULL_RETURN;
     impl()->insertNewline();
 }
 
-void TypingCommand::deleteKeyPressed()
+void TypingCommand::deleteKeyPressed() const
 {
     IF_IMPL_NULL_RETURN;
     impl()->deleteKeyPressed();
index a37e3520ec4b92d8b76dfcd96e98d083f0f717b4..f7ab11837a1ab904cd28ece39b6bb412568377cb 100644 (file)
@@ -68,99 +68,43 @@ class SetNodeAttributeCommandImpl;
 class SplitTextNodeCommandImpl;
 class TypingCommandImpl;
 
-//------------------------------------------------------------------------------------------
-// Constants
-
-enum ECommandID { 
-    EditCommandID, // leave the base class first, others in alpha order
-    AppendNodeCommandID,
-    ApplyStyleCommandID,
-    CompositeEditCommandID,
-    DeleteSelectionCommandID,
-    DeleteTextCommandID,
-    InputNewlineCommandID,
-    InputTextCommandID,
-    InsertNodeBeforeCommandID,
-    InsertTextCommandID,
-    JoinTextNodesCommandID,
-    MoveSelectionCommandID,
-    ReplaceSelectionCommandID,
-    RemoveCSSPropertyCommandID,
-    RemoveNodeAttributeCommandID,
-    RemoveNodeCommandID,
-    RemoveNodePreservingChildrenCommandID,
-    SetNodeAttributeCommandID,
-    SplitTextNodeCommandID,
-    TypingCommandID,
-};
-
-//------------------------------------------------------------------------------------------
-// SharedCommandImpl
-
-class SharedCommandImpl : public Shared<SharedCommandImpl>
-{
-public:
-       SharedCommandImpl() {}
-       virtual ~SharedCommandImpl() {}
-
-    virtual int commandID() const = 0;
-    virtual bool isCompositeStep() const = 0;
-
-       virtual void apply() = 0;       
-       virtual void unapply() = 0;
-       virtual void reapply() = 0;
-
-    virtual DOM::DocumentImpl * const document() const = 0;
-
-    virtual DOM::Selection startingSelection() const = 0;
-    virtual DOM::Selection endingSelection() const = 0;
-
-    virtual void setStartingSelection(const DOM::Selection &s) = 0;
-    virtual void setEndingSelection(const DOM::Selection &s) = 0;
-
-    virtual DOM::CSSStyleDeclarationImpl *typingStyle() const = 0;
-    virtual void setTypingStyle(DOM::CSSStyleDeclarationImpl *) = 0;
-
-    virtual EditCommand parent() const = 0;
-    virtual void setParent(const EditCommand &) = 0;
-};
-
 //------------------------------------------------------------------------------------------
 // EditCommand
 
-class EditCommand : public SharedPtr<SharedCommandImpl>
+class EditCommand : public SharedPtr<EditCommandImpl>
 {
 public:
-       EditCommand();
-       EditCommand(EditCommandImpl *);
-       EditCommand(const EditCommand &);
-       virtual ~EditCommand();
+    EditCommand();
+    EditCommand(EditCommandImpl *);
+    EditCommand(const EditCommand &);
+    ~EditCommand();
+
+    EditCommand &operator=(const EditCommand &);
 
-    int commandID() const;
     bool isCompositeStep() const;
-    bool isNull() const;
-    bool notNull() const;
 
-       void apply();   
-       void unapply();
-       void reapply();
+    void apply() const;
+    void unapply() const;
+    void reapply() const;
 
     DOM::DocumentImpl * const document() const;
 
     DOM::Selection startingSelection() const;
     DOM::Selection endingSelection() const;
 
-    void setStartingSelection(const DOM::Selection &s);
-    void setEndingSelection(const DOM::Selection &s);
+    void setStartingSelection(const DOM::Selection &s) const;
+    void setEndingSelection(const DOM::Selection &s) const;
 
     DOM::CSSStyleDeclarationImpl *typingStyle() const;
-    void setTypingStyle(DOM::CSSStyleDeclarationImpl *);
+    void setTypingStyle(DOM::CSSStyleDeclarationImpl *) const;
 
     EditCommand parent() const;
-    void setParent(const EditCommand &);
+    void setParent(const EditCommand &) const;
+
+    bool isInputTextCommand() const;
+    bool isInputNewlineCommand() const;
+    bool isTypingCommand() const;
 
-    EditCommandImpl *handle() const;
-    
     static EditCommand &emptyCommand();
 };
 
@@ -169,14 +113,11 @@ public:
 
 class CompositeEditCommand : public EditCommand
 {
-public:
-       CompositeEditCommand();
-       CompositeEditCommand(CompositeEditCommandImpl *);
-       CompositeEditCommand(const CompositeEditCommand &);
-       virtual ~CompositeEditCommand();
+protected:
+    CompositeEditCommand(CompositeEditCommandImpl *);
 
 private:
-    inline CompositeEditCommandImpl *impl() const;
+    CompositeEditCommandImpl *impl() const;
 };
 
 //==========================================================================================
@@ -188,13 +129,12 @@ class AppendNodeCommand : public EditCommand
 {
 public:
     AppendNodeCommand(DOM::DocumentImpl *, DOM::NodeImpl *appendChild, DOM::NodeImpl *parentNode);
-       virtual ~AppendNodeCommand();
 
     DOM::NodeImpl *appendChild() const;
     DOM::NodeImpl *parentNode() const;
     
 private:
-    inline AppendNodeCommandImpl *impl() const;
+    AppendNodeCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -203,13 +143,12 @@ private:
 class ApplyStyleCommand : public CompositeEditCommand
 {
 public:
-       ApplyStyleCommand(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *);
-       virtual ~ApplyStyleCommand();
+    ApplyStyleCommand(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *);
 
     DOM::CSSStyleDeclarationImpl *style() const;
 
 private:
-    inline ApplyStyleCommandImpl *impl() const;
+    ApplyStyleCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -218,12 +157,11 @@ private:
 class DeleteSelectionCommand : public CompositeEditCommand
 {
 public:
-       DeleteSelectionCommand(DOM::DocumentImpl *document);
-       DeleteSelectionCommand(DOM::DocumentImpl *document, const DOM::Selection &selection);
-       virtual ~DeleteSelectionCommand();
+    DeleteSelectionCommand(DOM::DocumentImpl *document);
+    DeleteSelectionCommand(DOM::DocumentImpl *document, const DOM::Selection &selection);
 
 private:
-    inline DeleteSelectionCommandImpl *impl() const;
+    DeleteSelectionCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -232,16 +170,14 @@ private:
 class DeleteTextCommand : public EditCommand
 {
 public:
-       DeleteTextCommand(DOM::DocumentImpl *document, DOM::TextImpl *, long offset, long count);
-       DeleteTextCommand(const DeleteTextCommand &);
-       virtual ~DeleteTextCommand();
+    DeleteTextCommand(DOM::DocumentImpl *document, DOM::TextImpl *, long offset, long count);
 
     DOM::TextImpl *node() const;
     long offset() const;
     long count() const;
 
 private:
-    inline DeleteTextCommandImpl *impl() const;
+    DeleteTextCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -251,10 +187,9 @@ class InputNewlineCommand : public CompositeEditCommand
 {
 public:
     InputNewlineCommand(DOM::DocumentImpl *document);
-    virtual ~InputNewlineCommand();
 
 private:
-    inline InputNewlineCommandImpl *impl() const;
+    InputNewlineCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -264,15 +199,14 @@ class InputTextCommand : public CompositeEditCommand
 {
 public:
     InputTextCommand(DOM::DocumentImpl *document);
-    virtual ~InputTextCommand();
 
     void deleteCharacter();
-    void input(const DOM::DOMString &text);
+    void input(const DOM::DOMString &text, bool selectInsertedText = false);
 
     unsigned long charactersAdded() const;
 
 private:
-    inline InputTextCommandImpl *impl() const;
+    InputTextCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -281,16 +215,13 @@ private:
 class InsertNodeBeforeCommand : public EditCommand
 {
 public:
-    InsertNodeBeforeCommand();
     InsertNodeBeforeCommand(DOM::DocumentImpl *, DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
-    InsertNodeBeforeCommand(const InsertNodeBeforeCommand &);
-    virtual ~InsertNodeBeforeCommand();
 
     DOM::NodeImpl *insertChild() const;
     DOM::NodeImpl *refChild() const;
     
 private:
-    inline InsertNodeBeforeCommandImpl *impl() const;
+    InsertNodeBeforeCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -299,15 +230,14 @@ private:
 class InsertTextCommand : public EditCommand
 {
 public:
-       InsertTextCommand(DOM::DocumentImpl *document, DOM::TextImpl *, long, const DOM::DOMString &);
-       virtual ~InsertTextCommand();
+    InsertTextCommand(DOM::DocumentImpl *document, DOM::TextImpl *, long, const DOM::DOMString &);
 
     DOM::TextImpl *node() const;
     long offset() const;
     DOM::DOMString text() const;
 
 private:
-    inline InsertTextCommandImpl *impl() const;
+    InsertTextCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -316,14 +246,13 @@ private:
 class JoinTextNodesCommand : public EditCommand
 {
 public:
-       JoinTextNodesCommand(DOM::DocumentImpl *, DOM::TextImpl *, DOM::TextImpl *);
-       virtual ~JoinTextNodesCommand();
+    JoinTextNodesCommand(DOM::DocumentImpl *, DOM::TextImpl *, DOM::TextImpl *);
 
     DOM::TextImpl *firstNode() const;
     DOM::TextImpl *secondNode() const;
     
 private:
-    inline JoinTextNodesCommandImpl *impl() const;
+    JoinTextNodesCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -333,10 +262,9 @@ class ReplaceSelectionCommand : public CompositeEditCommand
 {
 public:
     ReplaceSelectionCommand(DOM::DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement=true);
-    virtual ~ReplaceSelectionCommand();
 
 private:
-    inline ReplaceSelectionCommandImpl *impl() const;
+    ReplaceSelectionCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -346,10 +274,9 @@ class MoveSelectionCommand : public CompositeEditCommand
 {
 public:
     MoveSelectionCommand(DOM::DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position);
-    virtual ~MoveSelectionCommand();
     
 private:
-        inline MoveSelectionCommandImpl *impl() const;
+    MoveSelectionCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -358,14 +285,13 @@ private:
 class RemoveCSSPropertyCommand : public EditCommand
 {
 public:
-       RemoveCSSPropertyCommand(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *, int property);
-       virtual ~RemoveCSSPropertyCommand();
+    RemoveCSSPropertyCommand(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *, int property);
 
     DOM::CSSStyleDeclarationImpl *styleDeclaration() const;
     int property() const;
     
 private:
-    inline RemoveCSSPropertyCommandImpl *impl() const;
+    RemoveCSSPropertyCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -374,14 +300,13 @@ private:
 class RemoveNodeAttributeCommand : public EditCommand
 {
 public:
-       RemoveNodeAttributeCommand(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute);
-       virtual ~RemoveNodeAttributeCommand();
+    RemoveNodeAttributeCommand(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute);
 
     DOM::ElementImpl *element() const;
     DOM::NodeImpl::Id attribute() const;
     
 private:
-    inline RemoveNodeAttributeCommandImpl *impl() const;
+    RemoveNodeAttributeCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -390,13 +315,12 @@ private:
 class RemoveNodeCommand : public EditCommand
 {
 public:
-       RemoveNodeCommand(DOM::DocumentImpl *, DOM::NodeImpl *node);
-       virtual ~RemoveNodeCommand();
+    RemoveNodeCommand(DOM::DocumentImpl *, DOM::NodeImpl *node);
 
     DOM::NodeImpl *node() const;
     
 private:
-    inline RemoveNodeCommandImpl *impl() const;
+    RemoveNodeCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -406,12 +330,11 @@ class RemoveNodePreservingChildrenCommand : public CompositeEditCommand
 {
 public:
     RemoveNodePreservingChildrenCommand(DOM::DocumentImpl *document, DOM::NodeImpl *node);
-    virtual ~RemoveNodePreservingChildrenCommand();
 
     DOM::NodeImpl *node() const;
 
 private:
-    inline RemoveNodePreservingChildrenCommandImpl *impl() const;
+    RemoveNodePreservingChildrenCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -420,15 +343,14 @@ private:
 class SetNodeAttributeCommand : public EditCommand
 {
 public:
-       SetNodeAttributeCommand(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute, const DOM::DOMString &value);
-       virtual ~SetNodeAttributeCommand();
+    SetNodeAttributeCommand(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute, const DOM::DOMString &value);
 
     DOM::ElementImpl *element() const;
     DOM::NodeImpl::Id attribute() const;
     DOM::DOMString value() const;
     
 private:
-    inline SetNodeAttributeCommandImpl *impl() const;
+    SetNodeAttributeCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -437,14 +359,13 @@ private:
 class SplitTextNodeCommand : public EditCommand
 {
 public:
-       SplitTextNodeCommand(DOM::DocumentImpl *, DOM::TextImpl *, long);
-       virtual ~SplitTextNodeCommand();
+    SplitTextNodeCommand(DOM::DocumentImpl *, DOM::TextImpl *, long);
 
     DOM::TextImpl *node() const;
     long offset() const;
     
 private:
-    inline SplitTextNodeCommandImpl *impl() const;
+    SplitTextNodeCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
@@ -454,27 +375,24 @@ class TypingCommand : public CompositeEditCommand
 {
 public:
     static void deleteKeyPressed(DOM::DocumentImpl *document);
-    static void insertText(DOM::DocumentImpl *document, const DOM::DOMString &text);
+    static void insertText(DOM::DocumentImpl *document, const DOM::DOMString &text, bool selectInsertedText = false);
     static void insertNewline(DOM::DocumentImpl *document);
     static bool isOpenForMoreTypingCommand(const EditCommand &);
-    static void closeTyping(EditCommand);
+    static void closeTyping(const EditCommand &);
 
     bool openForMoreTyping() const;
-    void closeTyping();
+    void closeTyping() const;
 
     enum ETypingCommand { DeleteKey, InsertText, InsertNewline };
 
 private:
-    TypingCommand(DOM::DocumentImpl *document, ETypingCommand, const DOM::DOMString &text="");
-    TypingCommand(TypingCommand *);
-    TypingCommand(const TypingCommand &);
-    virtual ~TypingCommand();
+    TypingCommand(DOM::DocumentImpl *document, ETypingCommand, const DOM::DOMString &text = "", bool selectInsertedText = false);
 
-    void deleteKeyPressed();
-    void insertText(const DOM::DOMString &text);
-    void insertNewline();
+    void deleteKeyPressed() const;
+    void insertText(const DOM::DOMString &text, bool selectInsertedText = false) const;
+    void insertNewline() const;
 
-    inline TypingCommandImpl *impl() const;
+    TypingCommandImpl *impl() const;
 };
 
 //------------------------------------------------------------------------------------------
index 77a827d827fa4e102763b61241527cb7c312062f..b4288763002b3af2832c6551adc38bb5163bc613 100644 (file)
@@ -164,19 +164,22 @@ static void debugPosition(const char *prefix, const Position &pos)
 // StyleChange
 
 StyleChange::StyleChange(CSSStyleDeclarationImpl *style) 
-    : m_applyBold(false), m_applyItalic(false)
 {
     init(style, Position());
 }
 
 StyleChange::StyleChange(CSSStyleDeclarationImpl *style, const Position &position)
-     : m_applyBold(false), m_applyItalic(false)
 {
     init(style, position);
 }
 
 void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
 {
+    m_applyBold = false;
+    m_applyItalic = false;
+
+    QString styleText;
+
     for (QPtrListIterator<CSSProperty> it(*(style->values())); it.current(); ++it) {
         CSSProperty *property = it.current();
 
@@ -186,26 +189,26 @@ void StyleChange::init(CSSStyleDeclarationImpl *style, const Position &position)
             continue;
 
         // Figure out the manner of change that is needed.
+        DOMString valueText(property->value()->cssText());
         switch (property->id()) {
             case CSS_PROP_FONT_WEIGHT:
-                if (strcasecmp(property->value()->cssText(), "bold") == 0)
+                if (strcasecmp(valueText, "bold") == 0) {
                     m_applyBold = true;
-                else
-                    m_cssStyle += property->cssText();
-                break;
-            case CSS_PROP_FONT_STYLE: {
-                    DOMString cssText(property->value()->cssText());
-                    if (strcasecmp(cssText, "italic") == 0 || strcasecmp(cssText, "oblique") == 0)
-                        m_applyItalic = true;
-                    else
-                        m_cssStyle += property->cssText();
+                    continue;
                 }
                 break;
-            default:
-                m_cssStyle += property->cssText();
+            case CSS_PROP_FONT_STYLE:
+                if (strcasecmp(valueText, "italic") == 0 || strcasecmp(valueText, "oblique") == 0) {
+                    m_applyItalic = true;
+                    continue;
+                }
                 break;
         }
+
+        styleText += property->cssText().string();
     }
+
+    m_cssStyle = styleText.stripWhiteSpace();
 }
 
 bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *property)
@@ -216,14 +219,14 @@ bool StyleChange::currentlyHasStyle(const Position &pos, const CSSProperty *prop
     style->ref();
     CSSValueImpl *value = style->getPropertyCSSValue(property->id());
     style->deref();
-    return strcasecmp(value->cssText(), property->value()->cssText()) == 0;
+    return value && strcasecmp(value->cssText(), property->value()->cssText()) == 0;
 }
 
 //------------------------------------------------------------------------------------------
 // EditCommandImpl
 
 EditCommandImpl::EditCommandImpl(DocumentImpl *document) 
-    : SharedCommandImpl(), m_document(document), m_state(NotApplied), m_typingStyle(0), m_parent(0)
+    : m_document(document), m_state(NotApplied), m_typingStyle(0), m_parent(0)
 {
     ASSERT(m_document);
     ASSERT(m_document->part());
@@ -240,11 +243,6 @@ EditCommandImpl::~EditCommandImpl()
         m_typingStyle->deref();
 }
 
-int EditCommandImpl::commandID() const
-{
-    return EditCommandID;
-}
-
 void EditCommandImpl::apply()
 {
     ASSERT(m_document);
@@ -255,12 +253,9 @@ void EditCommandImpl::apply()
     
     m_state = Applied;
 
-    // The delete selection command is a special case where we want the  
-    // typing style retained. For all other commands, clear it after
-    // applying.
     // FIXME: Improve typing style.
     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
-    if (commandID() != DeleteSelectionCommandID)
+    if (!preservesTypingStyle())
         setTypingStyle(0);
 
     if (!isCompositeStep()) {
@@ -308,22 +303,14 @@ void EditCommandImpl::doReapply()
 
 void EditCommandImpl::setStartingSelection(const Selection &s)
 {
-    m_startingSelection = s;
-    EditCommand cmd = parent();
-    while (cmd.notNull()) {
-        cmd.handle()->m_startingSelection = s;
-        cmd = cmd.parent();
-    }
+    for (EditCommandImpl *cmd = this; cmd; cmd = cmd->m_parent.get())
+        cmd->m_startingSelection = s;
 }
 
 void EditCommandImpl::setEndingSelection(const Selection &s)
 {
-    m_endingSelection = s;
-    EditCommand cmd = parent();
-    while (cmd.notNull()) {
-        cmd.handle()->m_endingSelection = s;
-        cmd = cmd.parent();
-    }
+    for (EditCommandImpl *cmd = this; cmd; cmd = cmd->m_parent.get())
+        cmd->m_endingSelection = s;
 }
 
 void EditCommandImpl::assignTypingStyle(DOM::CSSStyleDeclarationImpl *style)
@@ -340,12 +327,8 @@ void EditCommandImpl::setTypingStyle(CSSStyleDeclarationImpl *style)
 {
     // FIXME: Improve typing style.
     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
-    assignTypingStyle(style);
-    EditCommand cmd = parent();
-    while (cmd.notNull()) {
-        cmd.handle()->assignTypingStyle(style);
-        cmd = cmd.parent();
-    }
+    for (EditCommandImpl *cmd = this; cmd; cmd = cmd->m_parent.get())
+        cmd->assignTypingStyle(style);
 }
 
 void EditCommandImpl::markMisspellingsInSelection(const Selection &s)
@@ -353,14 +336,19 @@ void EditCommandImpl::markMisspellingsInSelection(const Selection &s)
     KWQ(document()->part())->markMisspellingsInSelection(s);
 }
 
-EditCommand EditCommandImpl::parent() const
+bool EditCommandImpl::preservesTypingStyle() const
 {
-    return m_parent;
+    return false;
 }
 
-void EditCommandImpl::setParent(const EditCommand &cmd)
+bool EditCommandImpl::isInputTextCommand() const
 {
-    m_parent = cmd;
+    return false;
+}
+
+bool EditCommandImpl::isTypingCommand() const
+{
+    return false;
 }
 
 //------------------------------------------------------------------------------------------
@@ -371,15 +359,6 @@ CompositeEditCommandImpl::CompositeEditCommandImpl(DocumentImpl *document)
 {
 }
 
-CompositeEditCommandImpl::~CompositeEditCommandImpl()
-{
-}
-
-int CompositeEditCommandImpl::commandID() const
-{
-    return CompositeEditCommandID;
-}
-
 void CompositeEditCommandImpl::doUnapply()
 {
     if (m_cmds.count() == 0) {
@@ -547,7 +526,7 @@ void CompositeEditCommandImpl::setNodeAttribute(ElementImpl *element, int attrib
     applyCommandToComposite(cmd);
 }
 
-ElementImpl *CompositeEditCommandImpl::applyTypingStyle(NodeImpl *child) const
+NodeImpl *CompositeEditCommandImpl::applyTypingStyle(NodeImpl *child) const
 {
     // FIXME: This function should share code with ApplyStyleCommandImpl::applyStyleIfNeeded
     // and ApplyStyleCommandImpl::computeStyleChange.
@@ -558,7 +537,6 @@ ElementImpl *CompositeEditCommandImpl::applyTypingStyle(NodeImpl *child) const
     StyleChange styleChange(document()->part()->typingStyle());
 
     NodeImpl *childToAppend = child;
-    ElementImpl *element = 0;
     int exceptionCode = 0;
 
     if (styleChange.applyItalic()) {
@@ -566,7 +544,6 @@ ElementImpl *CompositeEditCommandImpl::applyTypingStyle(NodeImpl *child) const
         ASSERT(exceptionCode == 0);
         italicElement->appendChild(childToAppend, exceptionCode);
         ASSERT(exceptionCode == 0);
-        element = italicElement;
         childToAppend = italicElement;
     }
 
@@ -575,7 +552,6 @@ ElementImpl *CompositeEditCommandImpl::applyTypingStyle(NodeImpl *child) const
         ASSERT(exceptionCode == 0);
         boldElement->appendChild(childToAppend, exceptionCode);
         ASSERT(exceptionCode == 0);
-        element = boldElement;
         childToAppend = boldElement;
     }
 
@@ -586,11 +562,10 @@ ElementImpl *CompositeEditCommandImpl::applyTypingStyle(NodeImpl *child) const
         styleElement->setAttribute(ATTR_CLASS, styleSpanClassString());
         styleElement->appendChild(childToAppend, exceptionCode);
         ASSERT(exceptionCode == 0);
-        element = styleElement;
         childToAppend = styleElement;
     }
 
-    return element;
+    return childToAppend;
 }
 
 void CompositeEditCommandImpl::deleteUnrenderedText(NodeImpl *node)
@@ -638,7 +613,6 @@ void CompositeEditCommandImpl::deleteUnrenderedText(const Position &pos)
         setEndingSelection(block);
 }
 
-
 //==========================================================================================
 // Concrete commands
 //------------------------------------------------------------------------------------------
@@ -656,15 +630,11 @@ AppendNodeCommandImpl::AppendNodeCommandImpl(DocumentImpl *document, NodeImpl *a
 
 AppendNodeCommandImpl::~AppendNodeCommandImpl()
 {
-    if (m_appendChild)
-        m_appendChild->deref();
-    if (m_parentNode)
-        m_parentNode->deref();
-}
+    ASSERT(m_appendChild);
+    m_appendChild->deref();
 
-int AppendNodeCommandImpl::commandID() const
-{
-    return AppendNodeCommandID;
+    ASSERT(m_parentNode);
+    m_parentNode->deref();
 }
 
 void AppendNodeCommandImpl::doApply()
@@ -704,11 +674,6 @@ ApplyStyleCommandImpl::~ApplyStyleCommandImpl()
     m_style->deref();
 }
 
-int ApplyStyleCommandImpl::commandID() const
-{
-    return ApplyStyleCommandID;
-}
-
 void ApplyStyleCommandImpl::doApply()
 {
     if (endingSelection().state() != Selection::RANGE)
@@ -910,7 +875,7 @@ void ApplyStyleCommandImpl::surroundNodeRangeWithElement(NodeImpl *startNode, No
 void ApplyStyleCommandImpl::applyStyleIfNeeded(NodeImpl *startNode, NodeImpl *endNode)
 {
     // FIXME: This function should share code with CompositeEditCommandImpl::applyTypingStyle.
-    // Both function do similar work, and the common parts could be factored out.
+    // Both functions do similar work, and the common parts could be factored out.
 
     StyleChange styleChange(style(), Position(startNode, 0));
     int exceptionCode = 0;
@@ -993,15 +958,6 @@ DeleteSelectionCommandImpl::DeleteSelectionCommandImpl(DocumentImpl *document, c
 {
 }
 
-DeleteSelectionCommandImpl::~DeleteSelectionCommandImpl()
-{
-}
-       
-int DeleteSelectionCommandImpl::commandID() const
-{
-    return DeleteSelectionCommandID;
-}
-
 CSSStyleDeclarationImpl *DeleteSelectionCommandImpl::computeTypingStyle(const Position &pos) const
 {
     ElementImpl *element = pos.element();
@@ -1321,6 +1277,11 @@ void DeleteSelectionCommandImpl::doApply()
     setEndingSelection(endingPosition);
 }
 
+bool DeleteSelectionCommandImpl::preservesTypingStyle() const
+{
+    return true;
+}
+
 //------------------------------------------------------------------------------------------
 // DeleteTextCommandImpl
 
@@ -1337,13 +1298,8 @@ DeleteTextCommandImpl::DeleteTextCommandImpl(DocumentImpl *document, TextImpl *n
 
 DeleteTextCommandImpl::~DeleteTextCommandImpl()
 {
-    if (m_node)
-        m_node->deref();
-}
-
-int DeleteTextCommandImpl::commandID() const
-{
-    return DeleteTextCommandID;
+    ASSERT(m_node);
+    m_node->deref();
 }
 
 void DeleteTextCommandImpl::doApply()
@@ -1376,15 +1332,6 @@ InputNewlineCommandImpl::InputNewlineCommandImpl(DocumentImpl *document)
 {
 }
 
-InputNewlineCommandImpl::~InputNewlineCommandImpl() 
-{
-}
-
-int InputNewlineCommandImpl::commandID() const
-{
-    return InputNewlineCommandID;
-}
-
 void InputNewlineCommandImpl::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
 {
     // Insert the BR after the caret position. In the case the
@@ -1508,24 +1455,10 @@ InputTextCommandImpl::InputTextCommandImpl(DocumentImpl *document)
 {
 }
 
-InputTextCommandImpl::~InputTextCommandImpl() 
-{
-}
-
-int InputTextCommandImpl::commandID() const
-{
-    return InputTextCommandID;
-}
-
 void InputTextCommandImpl::doApply()
 {
 }
 
-void InputTextCommandImpl::input(const DOMString &text)
-{
-    execute(text);
-}
-
 void InputTextCommandImpl::deleteCharacter()
 {
     ASSERT(state() == Applied);
@@ -1616,7 +1549,7 @@ Position InputTextCommandImpl::prepareForTextInsertion(bool adjustDownstream)
     return pos;
 }
 
-void InputTextCommandImpl::execute(const DOMString &text)
+void InputTextCommandImpl::input(const DOMString &text, bool selectInsertedText)
 {
     Selection selection = endingSelection();
     bool adjustDownstream = selection.start().downstream(StayInBlock).isFirstRenderedPositionOnLine();
@@ -1645,12 +1578,18 @@ void InputTextCommandImpl::execute(const DOMString &text)
             insertSpace(textNode, offset);
             document()->updateLayout();
         }
-        setEndingSelection(Position(textNode, offset + spacesPerTab));
+        if (selectInsertedText)
+            setEndingSelection(Selection(Position(textNode, offset), Position(textNode, offset + spacesPerTab)));
+        else
+            setEndingSelection(Position(textNode, offset + spacesPerTab));
         m_charactersAdded += spacesPerTab;
     }
     else if (isWS(text)) {
         insertSpace(textNode, offset);
-        setEndingSelection(Position(textNode, offset + 1));
+        if (selectInsertedText)
+            setEndingSelection(Selection(Position(textNode, offset), Position(textNode, offset + 1)));
+        else
+            setEndingSelection(Position(textNode, offset + 1));
         m_charactersAdded++;
     }
     else {
@@ -1665,7 +1604,10 @@ void InputTextCommandImpl::execute(const DOMString &text)
             replaceText(textNode, offset - 1, 1, " ");
         }
         insertText(textNode, offset, text);
-        setEndingSelection(Position(textNode, offset + text.length()));
+        if (selectInsertedText)
+            setEndingSelection(Selection(Position(textNode, offset), Position(textNode, offset + text.length())));
+        else
+            setEndingSelection(Position(textNode, offset + text.length()));
         m_charactersAdded += text.length();
     }
 }
@@ -1715,6 +1657,11 @@ void InputTextCommandImpl::insertSpace(TextImpl *textNode, unsigned long offset)
     insertText(textNode, offset, nonBreakingSpaceString());
 }
 
+bool InputTextCommandImpl::isInputTextCommand() const
+{
+    return true;
+}
+
 //------------------------------------------------------------------------------------------
 // InsertNodeBeforeCommandImpl
 
@@ -1730,15 +1677,11 @@ InsertNodeBeforeCommandImpl::InsertNodeBeforeCommandImpl(DocumentImpl *document,
 
 InsertNodeBeforeCommandImpl::~InsertNodeBeforeCommandImpl()
 {
-    if (m_insertChild)
-        m_insertChild->deref();
-    if (m_refChild)
-        m_refChild->deref();
-}
+    ASSERT(m_insertChild);
+    m_insertChild->deref();
 
-int InsertNodeBeforeCommandImpl::commandID() const
-{
-    return InsertNodeBeforeCommandID;
+    ASSERT(m_refChild);
+    m_refChild->deref();
 }
 
 void InsertNodeBeforeCommandImpl::doApply()
@@ -1782,11 +1725,6 @@ InsertTextCommandImpl::~InsertTextCommandImpl()
         m_node->deref();
 }
 
-int InsertTextCommandImpl::commandID() const
-{
-    return InsertTextCommandID;
-}
-
 void InsertTextCommandImpl::doApply()
 {
     ASSERT(m_node);
@@ -1794,7 +1732,6 @@ void InsertTextCommandImpl::doApply()
     if (m_text.isEmpty())
        return;
 
-
     int exceptionCode = 0;
     m_node->insertData(m_offset, m_text, exceptionCode);
     ASSERT(exceptionCode == 0);
@@ -1831,15 +1768,10 @@ JoinTextNodesCommandImpl::JoinTextNodesCommandImpl(DocumentImpl *document, TextI
 
 JoinTextNodesCommandImpl::~JoinTextNodesCommandImpl()
 {
-    if (m_text1)
-        m_text1->deref();
-    if (m_text2)
-        m_text2->deref();
-}
-
-int JoinTextNodesCommandImpl::commandID() const
-{
-    return JoinTextNodesCommandID;
+    ASSERT(m_text1);
+    m_text1->deref();
+    ASSERT(m_text2);
+    m_text2->deref();
 }
 
 void JoinTextNodesCommandImpl::doApply()
@@ -1881,15 +1813,14 @@ void JoinTextNodesCommandImpl::doUnapply()
 ReplaceSelectionCommandImpl::ReplaceSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement) 
     : CompositeEditCommandImpl(document), m_fragment(fragment), m_selectReplacement(selectReplacement)
 {
+    ASSERT(m_fragment);
+    m_fragment->ref();
 }
 
 ReplaceSelectionCommandImpl::~ReplaceSelectionCommandImpl()
 {
-}
-
-int ReplaceSelectionCommandImpl::commandID() const
-{
-    return ReplaceSelectionCommandID;
+    ASSERT(m_fragment);
+    m_fragment->deref();
 }
 
 void ReplaceSelectionCommandImpl::doApply()
@@ -1981,17 +1912,16 @@ void ReplaceSelectionCommandImpl::doApply()
 // MoveSelectionCommandImpl
 
 MoveSelectionCommandImpl::MoveSelectionCommandImpl(DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position) 
-: CompositeEditCommandImpl(document), m_fragment(fragment), m_position(position)
+    : CompositeEditCommandImpl(document), m_fragment(fragment), m_position(position)
 {
+    ASSERT(m_fragment);
+    m_fragment->ref();
 }
 
 MoveSelectionCommandImpl::~MoveSelectionCommandImpl()
 {
-}
-
-int MoveSelectionCommandImpl::commandID() const
-{
-    return MoveSelectionCommandID;
+    ASSERT(m_fragment);
+    m_fragment->deref();
 }
 
 void MoveSelectionCommandImpl::doApply()
@@ -2035,11 +1965,6 @@ RemoveCSSPropertyCommandImpl::~RemoveCSSPropertyCommandImpl()
     m_decl->deref();
 }
 
-int RemoveCSSPropertyCommandImpl::commandID() const
-{
-    return RemoveCSSPropertyCommandID;
-}
-
 void RemoveCSSPropertyCommandImpl::doApply()
 {
     ASSERT(m_decl);
@@ -2075,11 +2000,6 @@ RemoveNodeAttributeCommandImpl::~RemoveNodeAttributeCommandImpl()
     m_element->deref();
 }
 
-int RemoveNodeAttributeCommandImpl::commandID() const
-{
-    return RemoveNodeAttributeCommandID;
-}
-
 void RemoveNodeAttributeCommandImpl::doApply()
 {
     ASSERT(m_element);
@@ -2122,19 +2042,16 @@ RemoveNodeCommandImpl::RemoveNodeCommandImpl(DocumentImpl *document, NodeImpl *r
 
 RemoveNodeCommandImpl::~RemoveNodeCommandImpl()
 {
-    if (m_parent)
-        m_parent->deref();
-    if (m_removeChild)
-        m_removeChild->deref();
+    ASSERT(m_parent);
+    m_parent->deref();
+
+    ASSERT(m_removeChild);
+    m_removeChild->deref();
+
     if (m_refChild)
         m_refChild->deref();
 }
 
-int RemoveNodeCommandImpl::commandID() const
-{
-    return RemoveNodeCommandID;
-}
-
 void RemoveNodeCommandImpl::doApply()
 {
     ASSERT(m_parent);
@@ -2151,10 +2068,7 @@ void RemoveNodeCommandImpl::doUnapply()
     ASSERT(m_removeChild);
 
     int exceptionCode = 0;
-    if (m_refChild)
-        m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
-    else
-        m_parent->appendChild(m_removeChild, exceptionCode);
+    m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
     ASSERT(exceptionCode == 0);
 }
 
@@ -2170,13 +2084,8 @@ RemoveNodePreservingChildrenCommandImpl::RemoveNodePreservingChildrenCommandImpl
 
 RemoveNodePreservingChildrenCommandImpl::~RemoveNodePreservingChildrenCommandImpl()
 {
-    if (m_node)
-        m_node->deref();
-}
-
-int RemoveNodePreservingChildrenCommandImpl::commandID() const
-{
-    return RemoveNodePreservingChildrenCommandID;
+    ASSERT(m_node);
+    m_node->deref();
 }
 
 void RemoveNodePreservingChildrenCommandImpl::doApply()
@@ -2201,13 +2110,8 @@ SetNodeAttributeCommandImpl::SetNodeAttributeCommandImpl(DocumentImpl *document,
 
 SetNodeAttributeCommandImpl::~SetNodeAttributeCommandImpl()
 {
-    if (m_element)
-        m_element->deref();
-}
-
-int SetNodeAttributeCommandImpl::commandID() const
-{
-    return SetNodeAttributeCommandID;
+    ASSERT(m_element);
+    m_element->deref();
 }
 
 void SetNodeAttributeCommandImpl::doApply()
@@ -2247,13 +2151,9 @@ SplitTextNodeCommandImpl::~SplitTextNodeCommandImpl()
 {
     if (m_text1)
         m_text1->deref();
-    if (m_text2)
-        m_text2->deref();
-}
 
-int SplitTextNodeCommandImpl::commandID() const
-{
-    return SplitTextNodeCommandID;
+    ASSERT(m_text2);
+    m_text2->deref();
 }
 
 void SplitTextNodeCommandImpl::doApply()
@@ -2306,20 +2206,11 @@ void SplitTextNodeCommandImpl::doUnapply()
 //------------------------------------------------------------------------------------------
 // TypingCommandImpl
 
-TypingCommandImpl::TypingCommandImpl(DocumentImpl *document, TypingCommand::ETypingCommand commandType, const DOM::DOMString &textToInsert)
-    : CompositeEditCommandImpl(document), m_commandType(commandType), m_textToInsert(textToInsert), m_openForMoreTyping(true), m_applyEditing(false)
-{
-}
-
-TypingCommandImpl::~TypingCommandImpl()
+TypingCommandImpl::TypingCommandImpl(DocumentImpl *document, TypingCommand::ETypingCommand commandType, const DOM::DOMString &textToInsert, bool selectInsertedText)
+    : CompositeEditCommandImpl(document), m_commandType(commandType), m_textToInsert(textToInsert), m_openForMoreTyping(true), m_applyEditing(false), m_selectInsertedText(selectInsertedText)
 {
 }
 
-int TypingCommandImpl::commandID() const
-{
-    return TypingCommandID;
-}
-
 void TypingCommandImpl::doApply()
 {
     if (endingSelection().state() == Selection::NONE)
@@ -2328,14 +2219,16 @@ void TypingCommandImpl::doApply()
     switch (m_commandType) {
         case TypingCommand::DeleteKey:
             deleteKeyPressed();
-            break;
+            return;
         case TypingCommand::InsertText:
-            insertText(m_textToInsert);
-            break;
+            insertText(m_textToInsert, m_selectInsertedText);
+            return;
         case TypingCommand::InsertNewline:
             insertNewline();
-            break;
+            return;
     }
+
+    ASSERT_NOT_REACHED();
 }
 
 void TypingCommandImpl::markMisspellingsAfterTyping()
@@ -2365,24 +2258,24 @@ void TypingCommandImpl::typingAddedToOpenCommand()
     m_applyEditing = true;
 }
 
-void TypingCommandImpl::insertText(const DOMString &text)
+void TypingCommandImpl::insertText(const DOMString &text, bool selectInsertedText)
 {
     // FIXME: Improve typing style.
     // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
     if (document()->part()->typingStyle() || m_cmds.count() == 0) {
         InputTextCommand cmd(document());
         applyCommandToComposite(cmd);
-        cmd.input(text);
+        cmd.input(text, selectInsertedText);
     }
     else {
         EditCommand lastCommand = m_cmds.last();
-        if (lastCommand.commandID() == InputTextCommandID) {
-            static_cast<InputTextCommand &>(lastCommand).input(text);
+        if (lastCommand.isInputTextCommand()) {
+            static_cast<InputTextCommand &>(lastCommand).input(text, selectInsertedText);
         }
         else {
             InputTextCommand cmd(document());
             applyCommandToComposite(cmd);
-            cmd.input(text);
+            cmd.input(text, selectInsertedText);
         }
     }
     typingAddedToOpenCommand();
@@ -2432,14 +2325,14 @@ void TypingCommandImpl::deleteKeyPressed()
     }
     else {
         EditCommand lastCommand = m_cmds.last();
-        if (lastCommand.commandID() == InputTextCommandID) {
-            InputTextCommand cmd = static_cast<InputTextCommand &>(lastCommand);
+        if (lastCommand.isInputTextCommand()) {
+            InputTextCommand &cmd = static_cast<InputTextCommand &>(lastCommand);
             cmd.deleteCharacter();
             if (cmd.charactersAdded() == 0) {
-                removeCommand(cmd);
+                removeCommand(lastCommand);
             }
         }
-        else if (lastCommand.commandID() == InputNewlineCommandID) {
+        else if (lastCommand.isInputNewlineCommand()) {
             lastCommand.unapply();
             removeCommand(lastCommand);
         }
@@ -2464,6 +2357,24 @@ void TypingCommandImpl::removeCommand(const EditCommand &cmd)
         setEndingSelection(m_cmds.last().endingSelection());
 }
 
+bool TypingCommandImpl::preservesTypingStyle() const
+{
+    switch (m_commandType) {
+        case TypingCommand::DeleteKey:
+            return true;
+        case TypingCommand::InsertText:
+        case TypingCommand::InsertNewline:
+            return false;
+    }
+    ASSERT_NOT_REACHED();
+    return false;
+}
+
+bool TypingCommandImpl::isTypingCommand() const
+{
+    return true;
+}
+
 //------------------------------------------------------------------------------------------
 
 } // namespace khtml
index 2098b14225edfc1448773077c004b156c07be521..7a1af99901931c41e76261ef02f13e737783f8e5 100644 (file)
@@ -59,13 +59,13 @@ namespace khtml {
 
 class StyleChange {
 public:
-    StyleChange() : m_applyBold(false), m_applyItalic(false) {}
+    StyleChange() : m_applyBold(false), m_applyItalic(false) { }
     explicit StyleChange(DOM::CSSStyleDeclarationImpl *);
     StyleChange(DOM::CSSStyleDeclarationImpl *, const DOM::Position &);
 
-    DOM::DOMString cssStyle() { return m_cssStyle; }
-    bool applyBold() { return m_applyBold; }
-    bool applyItalic() { return m_applyItalic; }
+    DOM::DOMString cssStyle() const { return m_cssStyle; }
+    bool applyBold() const { return m_applyBold; }
+    bool applyItalic() const { return m_applyItalic; }
 
 private:
     void init(DOM::CSSStyleDeclarationImpl *, const DOM::Position &);
@@ -79,16 +79,15 @@ private:
 //------------------------------------------------------------------------------------------
 // EditCommandImpl
 
-class EditCommandImpl : public SharedCommandImpl
+class EditCommandImpl : public Shared<EditCommandImpl>
 {
 public:
     EditCommandImpl(DOM::DocumentImpl *);
     virtual ~EditCommandImpl();
 
-    virtual int commandID() const;
-    bool isCompositeStep() const { return parent().notNull(); }
-    EditCommand parent() const;
-    void setParent(const EditCommand &);
+    bool isCompositeStep() const { return m_parent.notNull(); }
+    EditCommandImpl *parent() const { return m_parent.get(); }
+    void setParent(EditCommandImpl *parent) { m_parent = parent; }
 
     enum ECommandState { NotApplied, Applied };
     
@@ -116,9 +115,14 @@ public:
     
     void markMisspellingsInSelection(const DOM::Selection &s);
 
+    virtual bool isInputTextCommand() const;
+    virtual bool isTypingCommand() const;
+
 private:
     void assignTypingStyle(DOM::CSSStyleDeclarationImpl *);
 
+    virtual bool preservesTypingStyle() const;
+
     DOM::DocumentImpl *m_document;
     ECommandState m_state;
     DOM::Selection m_startingSelection;
@@ -134,11 +138,7 @@ class CompositeEditCommandImpl : public EditCommandImpl
 {
 public:
     CompositeEditCommandImpl(DOM::DocumentImpl *);
-    virtual ~CompositeEditCommandImpl();
        
-    virtual int commandID() const;
-
-    virtual void doApply() = 0;        
     virtual void doUnapply();
     virtual void doReapply();
 
@@ -166,7 +166,7 @@ protected:
     void setNodeAttribute(DOM::ElementImpl *, int attribute, const DOM::DOMString &);
     void splitTextNode(DOM::TextImpl *text, long offset);
 
-    DOM::ElementImpl *applyTypingStyle(DOM::NodeImpl *) const;
+    DOM::NodeImpl *applyTypingStyle(DOM::NodeImpl *) const;
     void deleteUnrenderedText(DOM::NodeImpl *);
     void deleteUnrenderedText(const DOM::Position &pos);
 
@@ -184,8 +184,6 @@ public:
     AppendNodeCommandImpl(DOM::DocumentImpl *, DOM::NodeImpl *appendChild, DOM::NodeImpl *parentNode);
     virtual ~AppendNodeCommandImpl();
 
-    virtual int commandID() const;
-
     virtual void doApply();
     virtual void doUnapply();
 
@@ -206,8 +204,6 @@ public:
     ApplyStyleCommandImpl(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *style);
     virtual ~ApplyStyleCommandImpl();
        
-    virtual int commandID() const;
-
     virtual void doApply();
 
     DOM::CSSStyleDeclarationImpl *style() const { return m_style; }
@@ -238,14 +234,12 @@ class DeleteSelectionCommandImpl : public CompositeEditCommandImpl
 public:
     DeleteSelectionCommandImpl(DOM::DocumentImpl *document);
     DeleteSelectionCommandImpl(DOM::DocumentImpl *document, const DOM::Selection &selection);
-
-    virtual ~DeleteSelectionCommandImpl();
        
-    virtual int commandID() const;
-
     virtual void doApply();
     
 private:
+    virtual bool preservesTypingStyle() const;
+
     void deleteDownstreamWS(const DOM::Position &start);
     bool containsOnlyWhitespace(const DOM::Position &start, const DOM::Position &end);
     DOM::CSSStyleDeclarationImpl *computeTypingStyle(const DOM::Position &pos) const;
@@ -264,8 +258,6 @@ public:
     DeleteTextCommandImpl(DOM::DocumentImpl *document, DOM::TextImpl *node, long offset, long count);
     virtual ~DeleteTextCommandImpl();
        
-    virtual int commandID() const;
-
     virtual void doApply();
     virtual void doUnapply();
 
@@ -287,9 +279,6 @@ class InputNewlineCommandImpl : public CompositeEditCommandImpl
 {
 public:
     InputNewlineCommandImpl(DOM::DocumentImpl *document);
-    virtual ~InputNewlineCommandImpl();
-
-    virtual int commandID() const;
 
     virtual void doApply();
 
@@ -305,20 +294,18 @@ class InputTextCommandImpl : public CompositeEditCommandImpl
 {
 public:
     InputTextCommandImpl(DOM::DocumentImpl *document);
-    virtual ~InputTextCommandImpl();
-
-    virtual int commandID() const;
 
     virtual void doApply();
 
     void deleteCharacter();
-    void input(const DOM::DOMString &text);
+    void input(const DOM::DOMString &text, bool selectInsertedText = false);
     
     unsigned long charactersAdded() const { return m_charactersAdded; }
     
 private:
+    virtual bool isInputTextCommand() const;
+
     DOM::Position prepareForTextInsertion(bool adjustDownstream);
-    void execute(const DOM::DOMString &text);
     void insertSpace(DOM::TextImpl *textNode, unsigned long offset);
 
     unsigned long m_charactersAdded;
@@ -333,8 +320,6 @@ public:
     InsertNodeBeforeCommandImpl(DOM::DocumentImpl *, DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
     virtual ~InsertNodeBeforeCommandImpl();
 
-    virtual int commandID() const;
-
     virtual void doApply();
     virtual void doUnapply();
 
@@ -355,8 +340,6 @@ public:
     InsertTextCommandImpl(DOM::DocumentImpl *document, DOM::TextImpl *, long, const DOM::DOMString &);
     virtual ~InsertTextCommandImpl();
        
-    virtual int commandID() const;
-
     virtual void doApply();
     virtual void doUnapply();
 
@@ -379,8 +362,6 @@ public:
     JoinTextNodesCommandImpl(DOM::DocumentImpl *, DOM::TextImpl *, DOM::TextImpl *);
     virtual ~JoinTextNodesCommandImpl();
        
-    virtual int commandID() const;
-
     virtual void doApply();
     virtual void doUnapply();
 
@@ -402,8 +383,6 @@ public:
     ReplaceSelectionCommandImpl(DOM::DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement=true);
     virtual ~ReplaceSelectionCommandImpl();
     
-    virtual int commandID() const;
-
     virtual void doApply();
 
 private:
@@ -420,8 +399,6 @@ public:
     MoveSelectionCommandImpl(DOM::DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position);
     virtual ~MoveSelectionCommandImpl();
     
-    virtual int commandID() const;
-    
     virtual void doApply();
     
 private:
@@ -438,8 +415,6 @@ public:
     RemoveCSSPropertyCommandImpl(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *, int property);
     virtual ~RemoveCSSPropertyCommandImpl();
 
-    virtual int commandID() const;
-
     virtual void doApply();
     virtual void doUnapply();
 
@@ -462,8 +437,6 @@ public:
     RemoveNodeAttributeCommandImpl(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute);
     virtual ~RemoveNodeAttributeCommandImpl();
 
-    virtual int commandID() const;
-
     virtual void doApply();
     virtual void doUnapply();
 
@@ -485,8 +458,6 @@ public:
     RemoveNodeCommandImpl(DOM::DocumentImpl *, DOM::NodeImpl *);
     virtual ~RemoveNodeCommandImpl();
        
-    virtual int commandID() const;
-
     virtual void doApply();
     virtual void doUnapply();
 
@@ -507,8 +478,6 @@ public:
     RemoveNodePreservingChildrenCommandImpl(DOM::DocumentImpl *, DOM::NodeImpl *);
     virtual ~RemoveNodePreservingChildrenCommandImpl();
        
-    virtual int commandID() const;
-
     virtual void doApply();
 
     DOM::NodeImpl *node() const { return m_node; }
@@ -526,8 +495,6 @@ public:
     SetNodeAttributeCommandImpl(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute, const DOM::DOMString &value);
     virtual ~SetNodeAttributeCommandImpl();
 
-    virtual int commandID() const;
-
     virtual void doApply();
     virtual void doUnapply();
 
@@ -551,8 +518,6 @@ public:
     SplitTextNodeCommandImpl(DOM::DocumentImpl *, DOM::TextImpl *, long);
     virtual ~SplitTextNodeCommandImpl();
        
-    virtual int commandID() const;
-
     virtual void doApply();
     virtual void doUnapply();
 
@@ -571,21 +536,21 @@ private:
 class TypingCommandImpl : public CompositeEditCommandImpl
 {
 public:
-    TypingCommandImpl(DOM::DocumentImpl *document, TypingCommand::ETypingCommand, const DOM::DOMString &);
-    virtual ~TypingCommandImpl();
+    TypingCommandImpl(DOM::DocumentImpl *document, TypingCommand::ETypingCommand, const DOM::DOMString &, bool selectInsertedText);
     
-    virtual int commandID() const;
-
     virtual void doApply();
 
     bool openForMoreTyping() const { return m_openForMoreTyping; }
     void closeTyping() { m_openForMoreTyping = false; }
 
-    void insertText(const DOM::DOMString &text);
+    void insertText(const DOM::DOMString &text, bool selectInsertedText);
     void insertNewline();
     void deleteKeyPressed();
 
 private:
+    virtual bool isTypingCommand() const;
+    virtual bool preservesTypingStyle() const;
+
     void issueCommandForDeleteKey();
     void removeCommand(const EditCommand &);
     void markMisspellingsAfterTyping();
@@ -595,6 +560,7 @@ private:
     DOM::DOMString m_textToInsert;
     bool m_openForMoreTyping;
     bool m_applyEditing;
+    bool m_selectInsertedText;
 };
 
 //------------------------------------------------------------------------------------------
index 5a2e315af79808bf57cc30eace531a0e1a46ff9c..05b7882d8527951bda37aa335c8661253c840643 100644 (file)
@@ -311,6 +311,12 @@ bool execSuperscript(KHTMLPart *part, bool userInterface, const DOMString &value
     return execStyleChange(part, CSS_PROP_VERTICAL_ALIGN, "super");
 }
 
+bool execUnderline(KHTMLPart *part, bool userInterface, const DOMString &value)
+{
+    bool isUnderlined = selectionStartHasStyle(part, CSS_PROP_TEXT_DECORATION, "underline");
+    return execStyleChange(part, CSS_PROP_TEXT_DECORATION, isUnderlined ? "none" : "underline");
+}
+
 bool execUndo(KHTMLPart *part, bool userInterface, const DOMString &value)
 {
     part->undo();
@@ -409,6 +415,11 @@ KHTMLPart::TriState stateSuperscript(KHTMLPart *part)
     return stateStyle(part, CSS_PROP_VERTICAL_ALIGN, "super");
 }
 
+KHTMLPart::TriState stateUnderline(KHTMLPart *part)
+{
+    return stateStyle(part, CSS_PROP_TEXT_DECORATION, "underline");
+}
+
 // =============================================================================================
 //
 // queryCommandValue implementations
@@ -474,6 +485,7 @@ QDict<CommandImp> createCommandDictionary()
         { "SelectAll", { execSelectAll, enabled, stateNone, valueNull } },
         { "Subscript", { execSubscript, enabledAnySelection, stateSubscript, valueNull } },
         { "Superscript", { execSuperscript, enabledAnySelection, stateSuperscript, valueNull } },
+        { "Underline", { execUnderline, enabledAnySelection, stateUnderline, valueNull } },
         { "Undo", { execUndo, enabledUndo, stateNone, valueNull } },
         { "Unselect", { execUnselect, enabledAnySelection, stateNone, valueNull } }
 
@@ -482,7 +494,7 @@ QDict<CommandImp> createCommandDictionary()
         // documentation used as the basis for the list.
         //
 
-        // 2d-position (not supported)
+        // 2D-Position (not supported)
         // AbsolutePosition (not supported)
         // BlockDirLTR (not supported)
         // BlockDirRTL (not supported)
@@ -530,10 +542,9 @@ QDict<CommandImp> createCommandDictionary()
         // SizeToControlHeight (not supported)
         // SizeToControlWidth (not supported)
         // Stop (not supported)
-        // Stopimage (not supported)
+        // StopImage (not supported)
         // Strikethrough (not supported)
         // Unbookmark (not supported)
-        // Underline (not supported)
         // Unlink (not supported)
     };
 
index 75bba6361a22c8c9896a1c331bd26036489ed183..17a8182534c9e38527e1994a0c97d30ca16b1cb7 100644 (file)
@@ -58,9 +58,7 @@ using khtml::RenderText;
 
 namespace DOM {
 
-static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset);
-static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset);
-static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection);
+static Selection selectionForLine(const Position &position);
 
 static inline Position &emptyPosition()
 {
@@ -208,23 +206,22 @@ Position Selection::modifyExtendingRightForward(ETextGranularity granularity)
         case WORD:
             pos = pos.nextWordPosition();
             break;
+        case PARAGRAPH:
+            // "Next paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = pos.nextLinePosition(xPosForVerticalArrowNavigation(EXTENT));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT: {
             ElementImpl *elem = start().node()->getDocument()->documentElement();
             pos = Position(elem, elem->childNodeCount());
             break;
         }
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(end().node(), end().offset(), selection);
-            pos = selection.end();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(end()).end();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = end().endParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -243,23 +240,22 @@ Position Selection::modifyMovingRightForward(ETextGranularity granularity)
         case WORD:
             pos = extent().nextWordPosition();
             break;
+        case PARAGRAPH:
+            // "Next paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = end().nextLinePosition(xPosForVerticalArrowNavigation(END, state() == RANGE));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT: {
             ElementImpl *elem = start().node()->getDocument()->documentElement();
             pos = Position(elem, elem->childNodeCount());
             break;
         }
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(end().node(), end().offset(), selection);
-            pos = selection.end();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(end()).end();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = end().endParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -278,21 +274,20 @@ Position Selection::modifyExtendingLeftBackward(ETextGranularity granularity)
         case WORD:
             pos = pos.previousWordPosition();
             break;
+        case PARAGRAPH:
+            // "Previous paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = pos.previousLinePosition(xPosForVerticalArrowNavigation(EXTENT));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT:
             pos = Position(start().node()->getDocument()->documentElement(), 0);
             break;
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(start().node(), start().offset(), selection);
-            pos = selection.start();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(start()).start();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = start().startParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -311,21 +306,20 @@ Position Selection::modifyMovingLeftBackward(ETextGranularity granularity)
         case WORD:
             pos = extent().previousWordPosition();
             break;
+        case PARAGRAPH:
+            // "Previous paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = start().previousLinePosition(xPosForVerticalArrowNavigation(START, state() == RANGE));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT:
             pos = Position(start().node()->getDocument()->documentElement(), 0);
             break;
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(start().node(), start().offset(), selection);
-            pos = selection.start();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(start()).start();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = start().startParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -653,6 +647,7 @@ void Selection::validate(ETextGranularity granularity)
                 assignStartAndEnd(extent(), base());
             break;
         case WORD: {
+            // FIXME: This doesn't handle words that cross node boundaries.
             int baseStartOffset = base().offset();
             int baseEndOffset = base().offset();
             int extentStartOffset = extent().offset();
@@ -683,36 +678,47 @@ void Selection::validate(ETextGranularity granularity)
         case LINE_BOUNDARY: {
             Selection baseSelection = *this;
             Selection extentSelection = *this;
-            if (base().notEmpty() && (base().node()->nodeType() == Node::TEXT_NODE || base().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
-                if (startAndEndLineNodesIncludingNode(base().node(), base().offset(), baseSelection)) {
-                    assignStart(Position(baseSelection.base().node(), baseSelection.base().offset()));
-                    assignEnd(Position(baseSelection.extent().node(), baseSelection.extent().offset()));
-                }
+            Selection baseLine = selectionForLine(base());
+            if (baseLine.notEmpty()) {
+                baseSelection = baseLine;
             }
-            if (extent().notEmpty() && (extent().node()->nodeType() == Node::TEXT_NODE || extent().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
-                if (startAndEndLineNodesIncludingNode(extent().node(), extent().offset(), extentSelection)) {
-                    assignStart(Position(extentSelection.base().node(), extentSelection.base().offset()));
-                    assignEnd(Position(extentSelection.extent().node(), extentSelection.extent().offset()));
-                }
+            Selection extentLine = selectionForLine(extent());
+            if (extentLine.notEmpty()) {
+                extentSelection = extentLine;
             }
             if (m_baseIsStart) {
                 assignStart(baseSelection.start());
                 assignEnd(extentSelection.end());
-            }
-            else {
+            } else {
                 assignStart(extentSelection.start());
                 assignEnd(baseSelection.end());
             }
+            break;
         }
         case PARAGRAPH:
-            // not implemented
+            if (m_baseIsStart) {
+                assignStart(base().startParagraphBoundary());
+                assignEnd(extent().endParagraphBoundary(IncludeLineBreak));
+            } else {
+                assignStart(extent().startParagraphBoundary());
+                assignEnd(base().endParagraphBoundary(IncludeLineBreak));
+            }
             break;
         case DOCUMENT: {
-            NodeImpl *topNode = start().node()->getDocument()->documentElement();
-            assignStart(Position(topNode, 0));
-            assignEnd(Position(topNode, 1));
+            NodeImpl *de = start().node()->getDocument()->documentElement();
+            assignStart(Position(de, 0).equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
+            assignEnd(Position(de, de->childNodeCount()).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
             break;
         }
+        case PARAGRAPH_BOUNDARY:
+            if (m_baseIsStart) {
+                assignStart(base().startParagraphBoundary());
+                assignEnd(extent().endParagraphBoundary());
+            } else {
+                assignStart(extent().startParagraphBoundary());
+                assignEnd(base().endParagraphBoundary());
+            }
+            break;
     }
 
     // adjust the state
@@ -823,104 +829,94 @@ bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2) const
     return result;
 }
 
-static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset)
+static Position startOfFirstRunAt(RenderObject *renderNode, int y)
 {
     for (RenderObject *n = renderNode; n; n = n->nextSibling()) {
         if (n->isText()) {
-            RenderText *textRenderer = static_cast<khtml::RenderText *>(n);
-            for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
-                if (box->m_y == y) {
-                    startNode = textRenderer->element();
-                    startOffset = box->m_start;
-                    return true;
-                }
-            }
+            RenderText *textRenderer = static_cast<RenderText *>(n);
+            for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox())
+                if (box->m_y == y)
+                    return Position(textRenderer->element(), box->m_start);
         }
         
-        if (firstRunAt(n->firstChild(), y, startNode, startOffset)) {
-            return true;
-        }
+        Position position = startOfFirstRunAt(n->firstChild(), y);
+        if (position.notEmpty())
+            return position;
     }
     
-    return false;
+    return Position();
 }
 
-static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset)
+static Position endOfLastRunAt(RenderObject *renderNode, int y)
 {
     RenderObject *n = renderNode;
-    if (!n) {
-        return false;
-    }
-    RenderObject *next;
-    while ((next = n->nextSibling())) {
-        n = next;
-    }
+    if (!n)
+        return Position();
+    if (RenderObject *parent = n->parent())
+        n = parent->lastChild();
     
     while (1) {
-        if (lastRunAt(n->firstChild(), y, endNode, endOffset)) {
-            return true;
-        }
-    
+        Position position = endOfLastRunAt(n->firstChild(), y);
+        if (position.notEmpty())
+            return position;
+        
         if (n->isText()) {
-            RenderText *textRenderer =  static_cast<khtml::RenderText *>(n);
-            for (InlineTextBox* box = textRenderer->lastTextBox(); box; box = box->prevTextBox()) {
-                if (box->m_y == y) {
-                    endNode = textRenderer->element();
-                    endOffset = box->m_start + box->m_len;
-                    return true;
-                }
-            }
+            RenderText *textRenderer = static_cast<RenderText *>(n);
+            for (InlineTextBox* box = textRenderer->lastTextBox(); box; box = box->prevTextBox())
+                if (box->m_y == y)
+                    return Position(textRenderer->element(), box->m_start + box->m_len);
         }
         
-        if (n == renderNode) {
-            return false;
-        }
+        if (n == renderNode)
+            return Position();
         
         n = n->previousSibling();
     }
 }
 
-static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection)
+static Selection selectionForLine(const Position &position)
 {
-    if (node && (node->nodeType() == Node::TEXT_NODE || node->nodeType() == Node::CDATA_SECTION_NODE)) {
-        int pos;
-        int selectionPointY;
-        RenderText *renderer = static_cast<RenderText *>(node->renderer());
-        InlineTextBox * run = renderer->findNextInlineTextBox( offset, pos );
-        DOMString t = node->nodeValue();
-        
-        if (!run)
-            return false;
-            
-        selectionPointY = run->m_y;
-        
-        // Go up to first non-inline element.
-        khtml::RenderObject *renderNode = renderer;
-        while (renderNode && renderNode->isInline())
-            renderNode = renderNode->parent();
-        
-        renderNode = renderNode->firstChild();
-        
-        NodeImpl *startNode = 0;
-        NodeImpl *endNode = 0;
-        long startOffset;
-        long endOffset;
+    NodeImpl *node = position.node();
+
+    if (!node)
+        return Selection();
+
+    switch (node->nodeType()) {
+        case Node::TEXT_NODE:
+        case Node::CDATA_SECTION_NODE:
+            break;
+        default:
+            return Selection();
+    }
+
+    RenderText *renderer = static_cast<RenderText *>(node->renderer());
+
+    int pos;
+    InlineTextBox *run = renderer->findNextInlineTextBox(position.offset(), pos);
+    if (!run)
+        return Selection();
         
-        // Look for all the first child in the block that is on the same line
-        // as the selection point.
-        if (!firstRunAt (renderNode, selectionPointY, startNode, startOffset))
-            return false;
+    int selectionPointY = run->m_y;
     
-        // Look for all the last child in the block that is on the same line
-        // as the selection point.
-        if (!lastRunAt (renderNode, selectionPointY, endNode, endOffset))
-            return false;
-        
-        selection.moveTo(Position(startNode, startOffset), Position(endNode, endOffset));
-        
-        return true;
-    }
-    return false;
+    // Go up to first non-inline element.
+    RenderObject *renderNode = renderer;
+    while (renderNode && renderNode->isInline())
+        renderNode = renderNode->parent();
+    renderNode = renderNode->firstChild();
+    
+    // Look for all the first child in the block that is on the same line
+    // as the selection point.
+    Position start = startOfFirstRunAt(renderNode, selectionPointY);
+    if (start.isEmpty())
+        return Selection();
+
+    // Look for all the last child in the block that is on the same line
+    // as the selection point.
+    Position end = endOfLastRunAt(renderNode, selectionPointY);
+    if (end.isEmpty())
+        return Selection();
+    
+    return Selection(start, end);
 }
 
 void Selection::debugRenderer(RenderObject *r, bool selected) const
index b5f81705c11aa07f9cf8ac22b5617f28ce84f380..c38bd9d88ea660920fdb808aa4b3eadbc0adc49a 100644 (file)
@@ -48,7 +48,7 @@ public:
     enum EState { NONE, CARET, RANGE };
     enum EAlter { MOVE, EXTEND };
     enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT };
-    enum ETextGranularity { CHARACTER, WORD, LINE, PARAGRAPH, DOCUMENT, LINE_BOUNDARY };
+    enum ETextGranularity { CHARACTER, WORD, LINE, PARAGRAPH, DOCUMENT, LINE_BOUNDARY, PARAGRAPH_BOUNDARY };
 
     Selection();
     Selection(const Range &);
@@ -100,9 +100,6 @@ public:
     Selection &operator=(const Range &r) { moveTo(r); return *this; }
     Selection &operator=(const Position &r) { moveTo(r); return *this; }
     
-    friend bool operator==(const Selection &a, const Selection &b);
-    friend bool operator!=(const Selection &a, const Selection &b);
-    
     friend class KHTMLPart;
 
 #ifndef NDEBUG
index 4d438e699307823fa22baecc79a9273221fbf081..4732debdfc5b503f0bb5491c37ace44be6906acd 100644 (file)
@@ -4426,12 +4426,12 @@ void KHTMLPart::handleMousePressEventTripleClick(khtml::MousePressEvent *event)
         Position pos(innerNode.handle()->positionForCoordinates(event->x(), event->y()));
         if (pos.node() && (pos.node()->nodeType() == Node::TEXT_NODE || pos.node()->nodeType() == Node::CDATA_SECTION_NODE)) {
             selection.moveTo(pos);
-            selection.expandUsingGranularity(Selection::LINE);
+            selection.expandUsingGranularity(Selection::PARAGRAPH);
         }
     }
     
     if (selection.state() != Selection::CARET) {
-        d->m_selectionGranularity = Selection::LINE;
+        d->m_selectionGranularity = Selection::PARAGRAPH;
         d->m_beganSelectingText = true;
     }
     
@@ -4913,54 +4913,11 @@ void KHTMLPart::slotAutoScroll()
 
 void KHTMLPart::selectAll()
 {
-  if(!d->m_doc) return;
-
-  NodeImpl *first;
-  if (d->m_doc->isHTMLDocument())
-    first = static_cast<HTMLDocumentImpl*>(d->m_doc)->body();
-  else
-    first = d->m_doc;
-  NodeImpl *next;
-
-  // Look for first rendered text node
-  while (first && !(first->renderer() && first->renderer()->isText()))
-  {
-    next = first->firstChild();
-    if ( !next ) next = first->nextSibling();
-    while( first && !next )
-    {
-      first = first->parentNode();
-      if ( first )
-        next = first->nextSibling();
-    }
-    first = next;
-  }
-
-  NodeImpl *last;
-  if (d->m_doc->isHTMLDocument())
-    last = static_cast<HTMLDocumentImpl*>(d->m_doc)->body();
-  else
-    last = d->m_doc;
-  // Look for last rendered text node
-  while (last && !(last->renderer() && last->renderer()->isText()))
-  {
-    next = last->lastChild();
-    if ( !next ) next = last->previousSibling();
-    while ( last && !next )
-    {
-      last = last->parentNode();
-      if ( last )
-        next = last->previousSibling();
-    }
-    last = next;
-  }
-
-  if ( !first || !last )
-    return;
-  Q_ASSERT(first->renderer());
-  Q_ASSERT(last->renderer());
-  Selection selection(Position(first, 0), Position(last, last->nodeValue().length()));
-  setSelection(selection);
+    if (!d->m_doc)
+        return;
+    Selection selection(Position(d->m_doc->documentElement(), 0));
+    selection.validate(Selection::DOCUMENT);
+    setSelection(selection);
 }
 
 bool KHTMLPart::shouldBeginEditing(const Range &range) const
@@ -5012,7 +4969,7 @@ void KHTMLPart::appliedEditing(EditCommand &cmd)
 
     // Command will be equal to last edit command only in the case of typing
     if (d->m_lastEditCommand == cmd) {
-        assert(cmd.commandID() == khtml::TypingCommandID);
+        assert(cmd.isTypingCommand());
     }
     else {
 #if APPLE_CHANGES
@@ -5394,14 +5351,14 @@ KHTMLPart::TriState KHTMLPart::selectionHasStyle(CSSStyleDeclarationImpl *style)
         }
     } else {
         for (NodeImpl *node = d->m_selection.start().node(); node; node = node->traverseNextNode()) {
-            if (node->isHTMLElement()) {
-                CSSStyleDeclarationImpl *computedStyle = new CSSComputedStyleDeclarationImpl(node);
+            CSSStyleDeclarationImpl *computedStyle = new CSSComputedStyleDeclarationImpl(node);
+            if (computedStyle) {
                 computedStyle->ref();
                 updateState(style, computedStyle, atStart, state);
                 computedStyle->deref();
-                if (state == mixedTriState)
-                    break;
             }
+            if (state == mixedTriState)
+                break;
             if (node == d->m_selection.end().node())
                 break;
         }
index 190265d54cf992fefef55c19ce1cefe7ff4e684c..6274827bc4944950cdd806a3969d7d6ad2a1188d 100644 (file)
@@ -22,6 +22,10 @@ public:
 //    static int counter;
 protected:
     unsigned int _ref;
+
+private:
+    Shared(const Shared &);
+    Shared &operator=(const Shared &);
 };
 
 template<class type> class TreeShared
@@ -50,14 +54,18 @@ private:
     unsigned int _ref;
 protected:
     type *m_parent;
+
+private:
+    TreeShared(const TreeShared &);
+    TreeShared &operator=(const TreeShared &);
 };
 
 template <class T> class SharedPtr
 {
 public:
     SharedPtr() : m_ptr(0) {}
-       explicit SharedPtr(T *ptr) : m_ptr(ptr) { if (m_ptr) m_ptr->ref(); }
-       SharedPtr(const SharedPtr &o) : m_ptr(o.m_ptr) { if (m_ptr) m_ptr->ref(); }
+    explicit SharedPtr(T *ptr) : m_ptr(ptr) { if (m_ptr) m_ptr->ref(); }
+    SharedPtr(const SharedPtr &o) : m_ptr(o.m_ptr) { if (m_ptr) m_ptr->ref(); }
     ~SharedPtr() { if (m_ptr) m_ptr->deref(); }
        
     bool isNull() const { return m_ptr == 0; }
@@ -66,32 +74,31 @@ public:
     void reset() { if (m_ptr) m_ptr->deref(); m_ptr = 0; }
     
     T * get() const { return m_ptr; }
-       T &operator*() const { return *m_ptr; }
-       T *operator->() const { return m_ptr; }
+    T &operator*() const { return *m_ptr; }
+    T *operator->() const { return m_ptr; }
 
-       bool operator!() const { return m_ptr == 0; }
+    bool operator!() const { return m_ptr == 0; }
 
-       inline friend bool operator==(const SharedPtr &a, const SharedPtr &b) { return a.m_ptr == b.m_ptr; }
-       inline friend bool operator==(const SharedPtr &a, const T *b) { return a.m_ptr == b; }
-       inline friend bool operator==(const T *a, const SharedPtr &b) { return a == b.m_ptr; }
+    inline friend bool operator==(const SharedPtr &a, const SharedPtr &b) { return a.m_ptr == b.m_ptr; }
+    inline friend bool operator==(const SharedPtr &a, const T *b) { return a.m_ptr == b; }
+    inline friend bool operator==(const T *a, const SharedPtr &b) { return a == b.m_ptr; }
 
-       SharedPtr &operator=(const SharedPtr &);
+    SharedPtr &operator=(const SharedPtr &);
 
 private:
-       T* m_ptr;
+    T* m_ptr;
 };
 
 template <class T> SharedPtr<T> &SharedPtr<T>::operator=(const SharedPtr<T> &o) 
 {
-       if (m_ptr != o.m_ptr) {
-               if (m_ptr)
+    if (m_ptr != o.m_ptr) {
+        if (m_ptr)
             m_ptr->deref();
-               m_ptr = o.m_ptr;
-               if (m_ptr) 
+        m_ptr = o.m_ptr;
+        if (m_ptr) 
             m_ptr->ref();
-       }
-       
-       return *this;
+    }
+    return *this;
 }
 
 template <class T> inline bool operator!=(const SharedPtr<T> &a, const SharedPtr<T> &b) { return !(a==b); }
index 67ecf94d0a22e05cb0c9948dbceb6e54401b1c37..c88a4120d69deb20b46879b6b633892c2a27f826 100644 (file)
@@ -523,7 +523,7 @@ void RenderCanvas::setSelection(RenderObject *s, int sp, RenderObject *e, int ep
     
     // Does the selection span objects and is the new end object different, or did the position
     // in the end element change?  If so we have to draw it.
-    if (oldStart != oldEnd && 
+    if (m_selectionStart != m_selectionEnd && 
         (oldEnd != m_selectionEnd ||
         (oldEnd == m_selectionEnd && oldEndPos != m_selectionEndPos))){
         m_view->updateContents( enclosingPositionedRect(m_selectionEnd) );
index ff7f9e627943f2ab1ba0cb6268d387d6ee9c81a8..aac9d932e53f59f3d1c9baef7b410db4183e2dc1 100644 (file)
@@ -264,7 +264,7 @@ struct CollapsedBorderValue
     EBorderPrecedence precedence;    
 };
 
-class BorderData : public Shared<BorderData>
+class BorderData
 {
 public:
     BorderValue left;
index e3bebce4cb8958c4af1a86337f0faccdeb7af9a9..30a91b45f16699de3e7c0545eec3e917bf9b1abb 100644 (file)
@@ -3106,11 +3106,6 @@ DocumentFragmentImpl::DocumentFragmentImpl(DocumentPtr *doc) : NodeBaseImpl(doc)
 {
 }
 
-DocumentFragmentImpl::DocumentFragmentImpl(const DocumentFragmentImpl &other)
-    : NodeBaseImpl(other)
-{
-}
-
 DOMString DocumentFragmentImpl::nodeName() const
 {
   return "#document-fragment";
index a7c9d1781760b4e3ff016545a99d5fe4757a6dff..861c6bf696e015082bcfcf894cc331e09c0b1670 100644 (file)
@@ -736,7 +736,6 @@ class DocumentFragmentImpl : public NodeBaseImpl
 {
 public:
     DocumentFragmentImpl(DocumentPtr *doc);
-    DocumentFragmentImpl(const DocumentFragmentImpl &other);
 
     // DOM methods overridden from  parent classes
     virtual DOMString nodeName() const;
index 60426f19dd1dd87ebe350db1a447ec37b677e871..d5df5f7bc9dabd1ac1154e1b6b9b270acfdccb20 100644 (file)
@@ -933,6 +933,20 @@ NodeImpl *NodeImpl::traversePreviousNode() const
     }
 }
 
+NodeImpl *NodeImpl::traversePreviousNodePostOrder(NodeImpl *stayWithin) const
+{
+    if (lastChild())
+       return lastChild();
+    if (previousSibling())
+       return previousSibling();
+    const NodeImpl *n = this;
+    while (n && !n->previousSibling() && (!stayWithin || n->parentNode() != stayWithin))
+        n = n->parentNode();
+    if (n && (!stayWithin || n->parentNode() != stayWithin))
+        return n->previousSibling();
+    return 0;
+}
+
 void NodeImpl::checkSetPrefix(const DOMString &_prefix, int &exceptioncode)
 {
     // Perform error checking as required by spec for setting Node.prefix. Used by
index dbb5ab6297d725bb2d3abb9bb66737ea20d49265..d271d33549d8e16bb8e4796ae35db0334d1ab5f9 100644 (file)
@@ -334,6 +334,9 @@ public:
      */
     NodeImpl *traversePreviousNode() const;
 
+    /* Like traversePreviousNode, but visits nodes before their children. */
+    NodeImpl *traversePreviousNodePostOrder(NodeImpl *stayWithin = 0) const;
+
     DocumentPtr *docPtr() const { return document; }
 
     NodeImpl *previousEditable() const;
index 3ac09b4be9c6fcc8d79de20fe820cb8ed862653b..b65662268d4b7a7a862445899e6e24386f967a14 100644 (file)
 #endif
 
 using khtml::CharacterIterator;
+using khtml::findWordBoundary;
 using khtml::InlineBox;
 using khtml::InlineFlowBox;
 using khtml::InlineTextBox;
+using khtml::nextWordFromIndex;
+using khtml::PRE;
 using khtml::RenderBlock;
 using khtml::RenderFlow;
 using khtml::RenderObject;
+using khtml::RenderStyle;
 using khtml::RenderText;
 using khtml::RootInlineBox;
 using khtml::SimplifiedBackwardsTextIterator;
 using khtml::TextIterator;
+using khtml::VISIBLE;
 
 namespace DOM {
 
@@ -101,24 +106,23 @@ static NodeImpl *previousRenderedEditable(NodeImpl *node)
 
 
 Position::Position(NodeImpl *node, long offset) 
-    : m_node(0), m_offset(offset) 
+    : m_node(node), m_offset(offset) 
 { 
     if (node) {
-        m_node = node;
-        m_node->ref();
+        node->ref();
     }
 };
 
 Position::Position(const Position &o)
-    : m_node(0), m_offset(o.offset()
+    : m_node(o.m_node), m_offset(o.m_offset
 {
-    if (o.node()) {
-        m_node = o.node();
+    if (m_node) {
         m_node->ref();
     }
 }
 
-Position::~Position() {
+Position::~Position()
+{
     if (m_node) {
         m_node->deref();
     }
@@ -222,7 +226,7 @@ Position Position::previousRenderedEditablePosition() const
         n = n->previousEditable();
         if (!n)
             return Position();
-        if (n->renderer() && n->renderer()->style()->visibility() == khtml::VISIBLE)
+        if (n->renderer() && n->renderer()->style()->visibility() == VISIBLE)
             break;
     }
     
@@ -242,7 +246,7 @@ Position Position::nextRenderedEditablePosition() const
         n = n->nextEditable();
         if (!n)
             return Position();
-        if (n->renderer() && n->renderer()->style()->visibility() == khtml::VISIBLE)
+        if (n->renderer() && n->renderer()->style()->visibility() == VISIBLE)
             break;
     }
     
@@ -320,7 +324,7 @@ Position Position::previousWordBoundary() const
             QChar *chars = t.unicode();
             uint len = t.length();
             int start, end;
-            khtml::findWordBoundary(chars, len, pos.offset(), &start, &end);
+            findWordBoundary(chars, len, pos.offset(), &start, &end);
             pos = Position(pos.node(), start);
             if (pos != *this)
                 return pos;
@@ -358,7 +362,7 @@ Position Position::nextWordBoundary() const
             QChar *chars = t.unicode();
             uint len = t.length();
             int start, end;
-            khtml::findWordBoundary(chars, len, pos.offset(), &start, &end);
+            findWordBoundary(chars, len, pos.offset(), &start, &end);
             pos = Position(pos.node(), end);
             if (pos != *this)
                 return pos;
@@ -397,7 +401,7 @@ Position Position::previousWordPosition() const
         // Keep asking the iterator for chunks until the nextWordFromIndex() function
         // returns a non-zero value.
         string.prepend(it.characters(), it.length());
-        next = khtml::nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), string.length(), false);
+        next = nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), string.length(), false);
         if (next != 0)
             break;
         it.advance();
@@ -418,7 +422,7 @@ Position Position::previousWordPosition() const
         chars[0] = 'X';
         chars[1] = ' ';
         string.prepend(chars, 2);
-        unsigned pastImage = khtml::nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), string.length(), false);
+        unsigned pastImage = nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), string.length(), false);
         Range range(it.range());
         if (pastImage == 0)
             pos = Position(range.startContainer().handle(), range.startOffset());
@@ -460,7 +464,7 @@ Position Position::nextWordPosition() const
         // Keep asking the iterator for chunks until the nextWordFromIndex() function
         // returns a value not equal to the length of the string passed to it.
         string.append(it.characters(), it.length());
-        next = khtml::nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), 0, true);
+        next = nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), 0, true);
         if (next != string.length())
             break;
         it.advance();
@@ -481,7 +485,7 @@ Position Position::nextWordPosition() const
         chars[0] = ' ';
         chars[1] = 'X';
         string.append(chars, 2);
-        unsigned pastImage = khtml::nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), 0, true);
+        unsigned pastImage = nextWordFromIndex(const_cast<QChar *>(string.unicode()), string.length(), 0, true);
         Range range(it.range());
         if (next != pastImage)
             pos = Position(range.endContainer().handle(), range.endOffset());
@@ -617,7 +621,95 @@ Position Position::nextLinePosition(int x) const
     return Position(rootElement, rootElement->childNodeCount());
 }
 
-Position Position::upstream(bool stayInBlock) const
+Position Position::startParagraphBoundary() const
+{
+    NodeImpl *startNode = m_node;
+    if (!startNode)
+        return *this;
+
+    Position p = *this;
+    NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
+
+    for (NodeImpl *n = startNode; n; n = n->traversePreviousNodePostOrder(startBlock)) {
+        RenderObject *r = n->renderer();
+        if (!r)
+            continue;
+        RenderStyle *style = r->style();
+        if (style->visibility() != VISIBLE)
+            continue;
+        if (r->isBR() || r->isBlockFlow())
+            break;
+        if (r->isText()) {
+            if (style->whiteSpace() == PRE) {
+                QChar *text = static_cast<RenderText *>(r)->text();
+                long i = static_cast<RenderText *>(r)->length();
+                long o = m_offset;
+                if (n == startNode && o < i)
+                    i = kMax(0L, o);
+                while (--i >= 0)
+                    if (text[i] == '\n')
+                        return Position(n, i + 1);
+            }
+            p.m_node = n;
+            p.m_offset = 0;
+        } else if (r->isReplaced()) {
+            p.m_node = n;
+            p.m_offset = 0;
+        }
+    }
+
+    return p;
+}
+
+Position Position::endParagraphBoundary(EIncludeLineBreak includeLineBreak) const
+{
+    NodeImpl *startNode = m_node;
+    if (!startNode)
+        return *this;
+
+    Position p = *this;
+    NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
+
+    for (NodeImpl *n = startNode; n; n = n->traverseNextNode(startBlock)) {
+        RenderObject *r = n->renderer();
+        if (!r)
+            continue;
+        RenderStyle *style = r->style();
+        if (style->visibility() != VISIBLE)
+            continue;
+        if (r->isBR()) {
+            if (includeLineBreak)
+                return Position(n, 1).downstream();
+            break;
+        }
+        if (r->isBlockFlow()) {
+            if (includeLineBreak)
+                return Position(n, n->childNodeCount()).downstream();
+            break;
+        }
+        if (r->isText()) {
+            long length = static_cast<RenderText *>(r)->length();
+            if (style->whiteSpace() == PRE) {
+                QChar *text = static_cast<RenderText *>(r)->text();
+                long o = m_offset;
+                if (n == startNode && o < length)
+                    length = kMax(0L, o);
+                for (long i = 0; i < length; ++i)
+                    if (text[i] == '\n')
+                        return Position(n, i);
+            }
+            p.m_node = n;
+            p.m_offset = length;
+        } else if (r->isReplaced()) {
+            p.m_node = n;
+            p.m_offset = 1;
+        }
+    }
+
+    return p;
+}
+
+Position Position::upstream(EStayInBlock stayInBlock) const
 {
     if (!node())
         return Position();
@@ -636,7 +728,7 @@ Position Position::upstream(bool stayInBlock) const
         if (!renderer)
             continue;
 
-        if (renderer->style()->visibility() != khtml::VISIBLE)
+        if (renderer->style()->visibility() != VISIBLE)
             continue;
 
         if ((it.current().node() != node() && renderer->isBlockFlow()) || renderer->isReplaced() || renderer->isBR()) {
@@ -669,7 +761,7 @@ Position Position::upstream(bool stayInBlock) const
     return it.current();
 }
 
-Position Position::downstream(bool stayInBlock) const
+Position Position::downstream(EStayInBlock stayInBlock) const
 {
     if (!node())
         return Position();
@@ -688,7 +780,7 @@ Position Position::downstream(bool stayInBlock) const
         if (!renderer)
             continue;
 
-        if (renderer->style()->visibility() != khtml::VISIBLE)
+        if (renderer->style()->visibility() != VISIBLE)
             continue;
 
         if ((it.current().node() != node() && renderer->isBlockFlow()) || renderer->isReplaced() || renderer->isBR()) {
@@ -850,9 +942,10 @@ bool Position::inRenderedContent() const
     if (!renderer)
         return false;
     
-    if (renderer->style()->visibility() != khtml::VISIBLE)
+    if (renderer->style()->visibility() != VISIBLE)
         return false;
 
+    // FIXME: This check returns false for a <br> at the end of a line!
     if (renderer->isBR() && static_cast<RenderText *>(renderer)->firstTextBox()) {
         return offset() == 0;
     }
@@ -930,32 +1023,6 @@ bool Position::isRenderedCharacter() const
     return false;
 }
 
-bool Position::rendersOnSameLine(const Position &pos) const
-{
-    if (isEmpty() || pos.isEmpty())
-        return false;
-
-    if (node() == pos.node() && offset() == pos.offset())
-        return true;
-
-    if (node()->enclosingBlockFlowElement() != pos.node()->enclosingBlockFlowElement())
-        return false;
-
-    RenderObject *renderer = node()->renderer();
-    if (!renderer)
-        return false;
-    
-    RenderObject *posRenderer = pos.node()->renderer();
-    if (!posRenderer)
-        return false;
-
-    if (renderer->style()->visibility() != khtml::VISIBLE ||
-        posRenderer->style()->visibility() != khtml::VISIBLE)
-        return false;
-
-    return renderersOnDifferentLine(renderer, offset(), posRenderer, pos.offset());
-}
-
 bool Position::rendersInDifferentPosition(const Position &pos) const
 {
     if (isEmpty() || pos.isEmpty())
@@ -969,8 +1036,8 @@ bool Position::rendersInDifferentPosition(const Position &pos) const
     if (!posRenderer)
         return false;
 
-    if (renderer->style()->visibility() != khtml::VISIBLE ||
-        posRenderer->style()->visibility() != khtml::VISIBLE)
+    if (renderer->style()->visibility() != VISIBLE ||
+        posRenderer->style()->visibility() != VISIBLE)
         return false;
     
     if (node() == pos.node()) {
@@ -1049,7 +1116,7 @@ bool Position::isFirstRenderedPositionOnLine() const
     if (!renderer)
         return false;
 
-    if (renderer->style()->visibility() != khtml::VISIBLE)
+    if (renderer->style()->visibility() != VISIBLE)
         return false;
     
     if (!inRenderedContent())
@@ -1078,7 +1145,7 @@ bool Position::isLastRenderedPositionOnLine() const
     if (!renderer)
         return false;
 
-    if (renderer->style()->visibility() != khtml::VISIBLE)
+    if (renderer->style()->visibility() != VISIBLE)
         return false;
     
     if (!inRenderedContent())
@@ -1110,7 +1177,7 @@ bool Position::isLastRenderedPositionInEditableBlock() const
     if (!renderer)
         return false;
 
-    if (renderer->style()->visibility() != khtml::VISIBLE)
+    if (renderer->style()->visibility() != VISIBLE)
         return false;
 
     if (renderedOffset() != (long)node()->caretMaxRenderedOffset())
@@ -1215,7 +1282,8 @@ bool Position::inLastEditableInContainingEditableBlock() const
 
 static inline bool isWS(const QChar &c)
 {
-    return c.isSpace() && c != QChar(0xa0);
+    const char nonBreakingSpace = 0xA0;
+    return c.isSpace() && c != nonBreakingSpace;
 }
 
 Position Position::leadingWhitespacePosition() const
index 2bf0f90f21356004b5eb745ebc60a0c71c02332f..ca8a5987864a5c4d3ddac1c06ab2f2b203337509 100644 (file)
@@ -39,12 +39,13 @@ class NodeImpl;
 // NSSelectionAffinityDownstream = 1
 enum EAffinity { UPSTREAM = 0, DOWNSTREAM = 1 };
 
-static const bool StayInBlock = true;
+enum EStayInBlock { DoNotStayInBlock = false, StayInBlock = true };
+enum EIncludeLineBreak { DoNotIncludeLineBreak = false, IncludeLineBreak = true };
 
 class Position
 {
 public:
-    Position() : m_node(0), m_offset(0) {};
+    Position() : m_node(0), m_offset(0) {}
     Position(NodeImpl *node, long offset);
     Position(const Position &);
     ~Position();
@@ -61,25 +62,30 @@ public:
     bool notEmpty() const { return m_node != 0; }
     
     Position equivalentLeafPosition() const;
+
     Position previousRenderedEditablePosition() const;
     Position nextRenderedEditablePosition() const;
+
     Position previousCharacterPosition() const;
     Position nextCharacterPosition() const;
     
     // suitable for moving by word in the UI
     Position previousWordPosition() const;
     Position nextWordPosition() const;
-    Position previousLinePosition(int x) const;
-    Position nextLinePosition(int x) const;
 
-    // next word boundary - would be too tedious to use in UI
+    // next word boundary - stops between words, so not right for moving by word
     Position previousWordBoundary() const;
     Position nextWordBoundary() const;
 
+    Position previousLinePosition(int x) const;
+    Position nextLinePosition(int x) const;
+
+    Position startParagraphBoundary() const;
+    Position endParagraphBoundary(EIncludeLineBreak includeLineBreak = DoNotIncludeLineBreak) const;
+
     Position leadingWhitespacePosition() const;
     Position trailingWhitespacePosition() const;
 
-
     // These functions only consider leaf nodes, and if stayInBlock is true, blocks.
     // Hence, the results from these functions are idiosyncratic, and until you
     // become familiar with the results, you may find using these functions confusing.
@@ -90,9 +96,8 @@ public:
     // same position as the caller's position. The same goes for downstream position
     // except that it is the latest position for earliest position in the above 
     // description.
-    Position upstream(bool stayInBlock=false) const;
-    Position downstream(bool stayInBlock=false) const;
-    
+    Position upstream(EStayInBlock stayInBlock = DoNotStayInBlock) const;
+    Position downstream(EStayInBlock stayInBlock = DoNotStayInBlock) const;
     
     Position equivalentRangeCompliantPosition() const;
     Position equivalentShallowPosition() const;
@@ -103,7 +108,6 @@ public:
     bool inRenderedContent() const;
     bool inRenderedText() const;
     bool isRenderedCharacter() const;
-    bool rendersOnSameLine(const Position &pos) const;
     bool rendersInDifferentPosition(const Position &pos) const;
     bool isFirstRenderedPositionOnLine() const;
     bool isLastRenderedPositionOnLine() const;
@@ -115,9 +119,6 @@ public:
     
     Position &operator=(const Position &o);
     
-    friend bool operator==(const Position &a, const Position &b);
-    friend bool operator!=(const Position &a, const Position &b);
-    
     void debugPosition(const char *msg="") const;
 
 #ifndef NDEBUG
index 75bba6361a22c8c9896a1c331bd26036489ed183..17a8182534c9e38527e1994a0c97d30ca16b1cb7 100644 (file)
@@ -58,9 +58,7 @@ using khtml::RenderText;
 
 namespace DOM {
 
-static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset);
-static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset);
-static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection);
+static Selection selectionForLine(const Position &position);
 
 static inline Position &emptyPosition()
 {
@@ -208,23 +206,22 @@ Position Selection::modifyExtendingRightForward(ETextGranularity granularity)
         case WORD:
             pos = pos.nextWordPosition();
             break;
+        case PARAGRAPH:
+            // "Next paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = pos.nextLinePosition(xPosForVerticalArrowNavigation(EXTENT));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT: {
             ElementImpl *elem = start().node()->getDocument()->documentElement();
             pos = Position(elem, elem->childNodeCount());
             break;
         }
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(end().node(), end().offset(), selection);
-            pos = selection.end();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(end()).end();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = end().endParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -243,23 +240,22 @@ Position Selection::modifyMovingRightForward(ETextGranularity granularity)
         case WORD:
             pos = extent().nextWordPosition();
             break;
+        case PARAGRAPH:
+            // "Next paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = end().nextLinePosition(xPosForVerticalArrowNavigation(END, state() == RANGE));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT: {
             ElementImpl *elem = start().node()->getDocument()->documentElement();
             pos = Position(elem, elem->childNodeCount());
             break;
         }
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(end().node(), end().offset(), selection);
-            pos = selection.end();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(end()).end();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = end().endParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -278,21 +274,20 @@ Position Selection::modifyExtendingLeftBackward(ETextGranularity granularity)
         case WORD:
             pos = pos.previousWordPosition();
             break;
+        case PARAGRAPH:
+            // "Previous paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = pos.previousLinePosition(xPosForVerticalArrowNavigation(EXTENT));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT:
             pos = Position(start().node()->getDocument()->documentElement(), 0);
             break;
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(start().node(), start().offset(), selection);
-            pos = selection.start();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(start()).start();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = start().startParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -311,21 +306,20 @@ Position Selection::modifyMovingLeftBackward(ETextGranularity granularity)
         case WORD:
             pos = extent().previousWordPosition();
             break;
+        case PARAGRAPH:
+            // "Previous paragraph" not implemented yet. Fall through to LINE.
         case LINE:
             pos = start().previousLinePosition(xPosForVerticalArrowNavigation(START, state() == RANGE));
             break;
-        case PARAGRAPH:
-            // not implemented
-            break;
         case DOCUMENT:
             pos = Position(start().node()->getDocument()->documentElement(), 0);
             break;
-        case LINE_BOUNDARY: {
-            Selection selection;
-            startAndEndLineNodesIncludingNode(start().node(), start().offset(), selection);
-            pos = selection.start();
+        case LINE_BOUNDARY:
+            pos = selectionForLine(start()).start();
+            break;
+        case PARAGRAPH_BOUNDARY:
+            pos = start().startParagraphBoundary();
             break;
-        }
     }
     return pos;
 }
@@ -653,6 +647,7 @@ void Selection::validate(ETextGranularity granularity)
                 assignStartAndEnd(extent(), base());
             break;
         case WORD: {
+            // FIXME: This doesn't handle words that cross node boundaries.
             int baseStartOffset = base().offset();
             int baseEndOffset = base().offset();
             int extentStartOffset = extent().offset();
@@ -683,36 +678,47 @@ void Selection::validate(ETextGranularity granularity)
         case LINE_BOUNDARY: {
             Selection baseSelection = *this;
             Selection extentSelection = *this;
-            if (base().notEmpty() && (base().node()->nodeType() == Node::TEXT_NODE || base().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
-                if (startAndEndLineNodesIncludingNode(base().node(), base().offset(), baseSelection)) {
-                    assignStart(Position(baseSelection.base().node(), baseSelection.base().offset()));
-                    assignEnd(Position(baseSelection.extent().node(), baseSelection.extent().offset()));
-                }
+            Selection baseLine = selectionForLine(base());
+            if (baseLine.notEmpty()) {
+                baseSelection = baseLine;
             }
-            if (extent().notEmpty() && (extent().node()->nodeType() == Node::TEXT_NODE || extent().node()->nodeType() == Node::CDATA_SECTION_NODE)) {
-                if (startAndEndLineNodesIncludingNode(extent().node(), extent().offset(), extentSelection)) {
-                    assignStart(Position(extentSelection.base().node(), extentSelection.base().offset()));
-                    assignEnd(Position(extentSelection.extent().node(), extentSelection.extent().offset()));
-                }
+            Selection extentLine = selectionForLine(extent());
+            if (extentLine.notEmpty()) {
+                extentSelection = extentLine;
             }
             if (m_baseIsStart) {
                 assignStart(baseSelection.start());
                 assignEnd(extentSelection.end());
-            }
-            else {
+            } else {
                 assignStart(extentSelection.start());
                 assignEnd(baseSelection.end());
             }
+            break;
         }
         case PARAGRAPH:
-            // not implemented
+            if (m_baseIsStart) {
+                assignStart(base().startParagraphBoundary());
+                assignEnd(extent().endParagraphBoundary(IncludeLineBreak));
+            } else {
+                assignStart(extent().startParagraphBoundary());
+                assignEnd(base().endParagraphBoundary(IncludeLineBreak));
+            }
             break;
         case DOCUMENT: {
-            NodeImpl *topNode = start().node()->getDocument()->documentElement();
-            assignStart(Position(topNode, 0));
-            assignEnd(Position(topNode, 1));
+            NodeImpl *de = start().node()->getDocument()->documentElement();
+            assignStart(Position(de, 0).equivalentDeepPosition().closestRenderedPosition(DOWNSTREAM));
+            assignEnd(Position(de, de->childNodeCount()).equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
             break;
         }
+        case PARAGRAPH_BOUNDARY:
+            if (m_baseIsStart) {
+                assignStart(base().startParagraphBoundary());
+                assignEnd(extent().endParagraphBoundary());
+            } else {
+                assignStart(extent().startParagraphBoundary());
+                assignEnd(base().endParagraphBoundary());
+            }
+            break;
     }
 
     // adjust the state
@@ -823,104 +829,94 @@ bool Selection::nodeIsBeforeNode(NodeImpl *n1, NodeImpl *n2) const
     return result;
 }
 
-static bool firstRunAt(RenderObject *renderNode, int y, NodeImpl *&startNode, long &startOffset)
+static Position startOfFirstRunAt(RenderObject *renderNode, int y)
 {
     for (RenderObject *n = renderNode; n; n = n->nextSibling()) {
         if (n->isText()) {
-            RenderText *textRenderer = static_cast<khtml::RenderText *>(n);
-            for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
-                if (box->m_y == y) {
-                    startNode = textRenderer->element();
-                    startOffset = box->m_start;
-                    return true;
-                }
-            }
+            RenderText *textRenderer = static_cast<RenderText *>(n);
+            for (InlineTextBox* box = textRenderer->firstTextBox(); box; box = box->nextTextBox())
+                if (box->m_y == y)
+                    return Position(textRenderer->element(), box->m_start);
         }
         
-        if (firstRunAt(n->firstChild(), y, startNode, startOffset)) {
-            return true;
-        }
+        Position position = startOfFirstRunAt(n->firstChild(), y);
+        if (position.notEmpty())
+            return position;
     }
     
-    return false;
+    return Position();
 }
 
-static bool lastRunAt(RenderObject *renderNode, int y, NodeImpl *&endNode, long &endOffset)
+static Position endOfLastRunAt(RenderObject *renderNode, int y)
 {
     RenderObject *n = renderNode;
-    if (!n) {
-        return false;
-    }
-    RenderObject *next;
-    while ((next = n->nextSibling())) {
-        n = next;
-    }
+    if (!n)
+        return Position();
+    if (RenderObject *parent = n->parent())
+        n = parent->lastChild();
     
     while (1) {
-        if (lastRunAt(n->firstChild(), y, endNode, endOffset)) {
-            return true;
-        }
-    
+        Position position = endOfLastRunAt(n->firstChild(), y);
+        if (position.notEmpty())
+            return position;
+        
         if (n->isText()) {
-            RenderText *textRenderer =  static_cast<khtml::RenderText *>(n);
-            for (InlineTextBox* box = textRenderer->lastTextBox(); box; box = box->prevTextBox()) {
-                if (box->m_y == y) {
-                    endNode = textRenderer->element();
-                    endOffset = box->m_start + box->m_len;
-                    return true;
-                }
-            }
+            RenderText *textRenderer = static_cast<RenderText *>(n);
+            for (InlineTextBox* box = textRenderer->lastTextBox(); box; box = box->prevTextBox())
+                if (box->m_y == y)
+                    return Position(textRenderer->element(), box->m_start + box->m_len);
         }
         
-        if (n == renderNode) {
-            return false;
-        }
+        if (n == renderNode)
+            return Position();
         
         n = n->previousSibling();
     }
 }
 
-static bool startAndEndLineNodesIncludingNode(NodeImpl *node, int offset, Selection &selection)
+static Selection selectionForLine(const Position &position)
 {
-    if (node && (node->nodeType() == Node::TEXT_NODE || node->nodeType() == Node::CDATA_SECTION_NODE)) {
-        int pos;
-        int selectionPointY;
-        RenderText *renderer = static_cast<RenderText *>(node->renderer());
-        InlineTextBox * run = renderer->findNextInlineTextBox( offset, pos );
-        DOMString t = node->nodeValue();
-        
-        if (!run)
-            return false;
-            
-        selectionPointY = run->m_y;
-        
-        // Go up to first non-inline element.
-        khtml::RenderObject *renderNode = renderer;
-        while (renderNode && renderNode->isInline())
-            renderNode = renderNode->parent();
-        
-        renderNode = renderNode->firstChild();
-        
-        NodeImpl *startNode = 0;
-        NodeImpl *endNode = 0;
-        long startOffset;
-        long endOffset;
+    NodeImpl *node = position.node();
+
+    if (!node)
+        return Selection();
+
+    switch (node->nodeType()) {
+        case Node::TEXT_NODE:
+        case Node::CDATA_SECTION_NODE:
+            break;
+        default:
+            return Selection();
+    }
+
+    RenderText *renderer = static_cast<RenderText *>(node->renderer());
+
+    int pos;
+    InlineTextBox *run = renderer->findNextInlineTextBox(position.offset(), pos);
+    if (!run)
+        return Selection();
         
-        // Look for all the first child in the block that is on the same line
-        // as the selection point.
-        if (!firstRunAt (renderNode, selectionPointY, startNode, startOffset))
-            return false;
+    int selectionPointY = run->m_y;
     
-        // Look for all the last child in the block that is on the same line
-        // as the selection point.
-        if (!lastRunAt (renderNode, selectionPointY, endNode, endOffset))
-            return false;
-        
-        selection.moveTo(Position(startNode, startOffset), Position(endNode, endOffset));
-        
-        return true;
-    }
-    return false;
+    // Go up to first non-inline element.
+    RenderObject *renderNode = renderer;
+    while (renderNode && renderNode->isInline())
+        renderNode = renderNode->parent();
+    renderNode = renderNode->firstChild();
+    
+    // Look for all the first child in the block that is on the same line
+    // as the selection point.
+    Position start = startOfFirstRunAt(renderNode, selectionPointY);
+    if (start.isEmpty())
+        return Selection();
+
+    // Look for all the last child in the block that is on the same line
+    // as the selection point.
+    Position end = endOfLastRunAt(renderNode, selectionPointY);
+    if (end.isEmpty())
+        return Selection();
+    
+    return Selection(start, end);
 }
 
 void Selection::debugRenderer(RenderObject *r, bool selected) const
index b5f81705c11aa07f9cf8ac22b5617f28ce84f380..c38bd9d88ea660920fdb808aa4b3eadbc0adc49a 100644 (file)
@@ -48,7 +48,7 @@ public:
     enum EState { NONE, CARET, RANGE };
     enum EAlter { MOVE, EXTEND };
     enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT };
-    enum ETextGranularity { CHARACTER, WORD, LINE, PARAGRAPH, DOCUMENT, LINE_BOUNDARY };
+    enum ETextGranularity { CHARACTER, WORD, LINE, PARAGRAPH, DOCUMENT, LINE_BOUNDARY, PARAGRAPH_BOUNDARY };
 
     Selection();
     Selection(const Range &);
@@ -100,9 +100,6 @@ public:
     Selection &operator=(const Range &r) { moveTo(r); return *this; }
     Selection &operator=(const Position &r) { moveTo(r); return *this; }
     
-    friend bool operator==(const Selection &a, const Selection &b);
-    friend bool operator!=(const Selection &a, const Selection &b);
-    
     friend class KHTMLPart;
 
 #ifndef NDEBUG
index 3c1e0c01d62043eef02cbded4875c373b661127f..0f9ea959f2e9a56a9ef7ae07a369bd2a760bd79a 100644 (file)
@@ -38,7 +38,8 @@ using khtml::Fixed;
 
 DOMStringImpl* DOMStringImpl::empty()
 {
-    static DOMStringImpl e = WithOneRef();
+    static WithOneRef w;
+    static DOMStringImpl e(w);
     return &e;
 }
 
@@ -487,7 +488,7 @@ unsigned DOMStringImpl::computeHash(const char *s)
 
 const char *DOMStringImpl::ascii() const
 {
-    char *buffer = new char[l];
+    char *buffer = new char[l + 1];
     char *p = buffer;
     for (unsigned i = 0; i != l; ++i) {
         unsigned short c = s[i].unicode();
@@ -496,6 +497,7 @@ const char *DOMStringImpl::ascii() const
         else
             *p++ = '?';
     }
+    *p++ = '\0';
     return buffer;
 }
 
index d4184223e74e8bc118f9ca1b5a613f90f2173cbf..5664236c43e9faf79f9f00ad99ccd7dc5e9ba54a 100644 (file)
@@ -225,7 +225,7 @@ public:
     NSImage *selectionImage() const;
     NSImage *snapshotDragImage(DOM::Node node, NSRect *imageRect, NSRect *elementRect) const;
 
-    NSFont *fontForCurrentPosition() const;
+    NSFont *fontForSelection(bool *hasMultipleFonts) const;
     void markMisspellingsInSelection(const DOM::Selection &selection);
     void updateSpellChecking();
 
index 2fa5019a73b5f2153f2cd554f5fc26a46f6dd825..e918316d2519b8860d6284ac5a32173190835671 100644 (file)
@@ -93,6 +93,7 @@ using DOM::Range;
 using DOM::RangeImpl;
 using DOM::Selection;
 using DOM::TextImpl;
+using DOM::UPSTREAM;
 
 using khtml::Cache;
 using khtml::CharacterIterator;
@@ -3327,58 +3328,73 @@ NSImage *KWQKHTMLPart::snapshotDragImage(DOM::Node node, NSRect *imageRect, NSRe
     return result;
 }
 
-NSFont *KWQKHTMLPart::fontForCurrentPosition() const
+NSFont *KWQKHTMLPart::fontForSelection(bool *hasMultipleFonts) const
 {
-    if (d->m_selection.state() == Selection::NONE)
-        return nil;
-    
-    Range range(d->m_selection.toRange());
-    Position pos(range.startContainer().handle(), range.startOffset());
-    ASSERT(pos.notEmpty());
-    ElementImpl *elem = pos.element();
-    if (!elem)
+    if (hasMultipleFonts)
+        *hasMultipleFonts = false;
+
+    if (!xmlDocImpl())
         return nil;
-    
-    pos = Position(elem, elem->caretMinOffset());
-    if (!pos.inRenderedContent())
+    if (d->m_selection.state() == Selection::NONE)
         return nil;
     
-    if (d->m_typingStyle) {
-        if (!xmlDocImpl())
+    if (d->m_selection.state() == Selection::CARET) {
+        Position pos(d->m_selection.start().equivalentDeepPosition().closestRenderedPosition(UPSTREAM));
+        ASSERT(pos.notEmpty());
+        if (!pos.inRenderedContent())
+            return nil;
+        NodeImpl *node = pos.node();
+        if (!node)
             return nil;
 
         int exceptionCode = 0;
-        ElementImpl *styleElement = xmlDocImpl()->createHTMLElement("SPAN", exceptionCode);
+        ElementImpl *styleElement = xmlDocImpl()->createHTMLElement("span", exceptionCode);
         ASSERT(exceptionCode == 0);
         
-        styleElement->setAttribute(ATTR_STYLE, d->m_typingStyle->cssText().implementation(), exceptionCode);
-        ASSERT(exceptionCode == 0);
+        if (d->m_typingStyle) {
+            styleElement->setAttribute(ATTR_STYLE, d->m_typingStyle->cssText().implementation(), exceptionCode);
+            ASSERT(exceptionCode == 0);
+        }
         
         TextImpl *text = xmlDocImpl()->createEditingTextNode("");
         styleElement->appendChild(text, exceptionCode);
         ASSERT(exceptionCode == 0);
 
-        elem->appendChild(styleElement, exceptionCode);
+        node->parentNode()->insertBefore(styleElement, node, exceptionCode);
         ASSERT(exceptionCode == 0);
         
         RenderObject *renderer = styleElement->renderer();
         ASSERT(renderer);
         NSFont *result = renderer->style()->font().getNSFont();
         
-        styleElement->removeChild(text, exceptionCode);
-        ASSERT(exceptionCode == 0);
-
-        elem->removeChild(styleElement, exceptionCode);
+        styleElement->remove(exceptionCode);
         ASSERT(exceptionCode == 0);
         
         return result;
     }
-    else {
-        RenderObject *renderer = elem->renderer();
-        if (renderer)
-            return renderer->style()->font().getNSFont();
+
+    NSFont *font = nil;
+
+    Range r = d->m_selection.toRange();
+    RangeImpl *range = r.handle();
+    NodeImpl *pastEnd = range->pastEndNode();
+    for (NodeImpl *n = range->startNode(); 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 = false;
+            break;
+        }
     }
-    return nil;
+
+    return font;
 }
 
 KWQWindowWidget *KWQKHTMLPart::topLevelWidget()
@@ -3524,13 +3540,12 @@ void KWQKHTMLPart::setName(const QString &name)
     KWQ_UNBLOCK_EXCEPTIONS;
 }
 
-
 void KWQKHTMLPart::didTellBridgeAboutLoad(const QString &urlString)
 {
-    urlsBridgeKnowsAbout.insert(urlString, (char *)1);
+    static char dummy;
+    urlsBridgeKnowsAbout.insert(urlString, &dummy);
 }
 
-
 bool KWQKHTMLPart::haveToldBridgeAboutLoad(const QString &urlString)
 {
     return urlsBridgeKnowsAbout.find(urlString) != 0;
@@ -3670,7 +3685,7 @@ DocumentFragmentImpl *KWQKHTMLPart::documentFragmentWithText(NSString *text)
     for (i = 0; i < count; i++) {
         int exceptionCode = 0;
         if (i != 0) {
-            ElementImpl *breakNode = xmlDocImpl()->createHTMLElement("BR", exceptionCode);
+            ElementImpl *breakNode = xmlDocImpl()->createHTMLElement("br", exceptionCode);
             ASSERT(exceptionCode == 0);
             fragment->appendChild(breakNode, exceptionCode);
             ASSERT(exceptionCode == 0);
@@ -3688,16 +3703,16 @@ DocumentFragmentImpl *KWQKHTMLPart::documentFragmentWithText(NSString *text)
 
 void KWQKHTMLPart::registerCommandForUndo(const khtml::EditCommand &cmd)
 {
-    ASSERT(cmd.handle());
-    KWQEditCommand *kwq = [KWQEditCommand commandWithEditCommandImpl:cmd.handle()];
+    ASSERT(cmd.get());
+    KWQEditCommand *kwq = [KWQEditCommand commandWithEditCommandImpl:cmd.get()];
     [[_bridge undoManager] registerUndoWithTarget:_bridge selector:@selector(undoEditing:) object:kwq];
     _haveUndoRedoOperations = YES;
 }
 
 void KWQKHTMLPart::registerCommandForRedo(const khtml::EditCommand &cmd)
 {
-    ASSERT(cmd.handle());
-    KWQEditCommand *kwq = [KWQEditCommand commandWithEditCommandImpl:cmd.handle()];
+    ASSERT(cmd.get());
+    KWQEditCommand *kwq = [KWQEditCommand commandWithEditCommandImpl:cmd.get()];
     [[_bridge undoManager] registerUndoWithTarget:_bridge selector:@selector(redoEditing:) object:kwq];
     _haveUndoRedoOperations = YES;
 }
index 8a1d7f2fe4fc1a59f9d0644646b85bd47d0e4e81..26cc46318900653f587937b04c0b0c8e27e22135 100644 (file)
@@ -112,7 +112,8 @@ typedef enum {
     WebSelectByLine,
     WebSelectByParagraph,
     WebSelectByDocument,
-    WebSelectToLineBoundary
+    WebSelectToLineBoundary,
+    WebSelectToParagraphBoundary
 } WebSelectionGranularity;
 
 
@@ -272,7 +273,8 @@ typedef enum {
 - (void)clearMarkedDOMRange;
 
 - (NSAttributedString *)attributedStringFrom:(DOMNode *)startNode startOffset:(int)startOffset to:(DOMNode *)endNode endOffset:(int)endOffset;
-- (NSFont *)renderedFontForNode:(DOMNode *)node;
+
+- (NSFont *)fontForSelection:(BOOL *)hasMultipleFonts;
 
 + (NSString *)stringWithData:(NSData *)data textEncoding:(CFStringEncoding)textEncoding;
 + (NSString *)stringWithData:(NSData *)data textEncodingName:(NSString *)textEncodingName;
@@ -308,7 +310,7 @@ typedef enum {
 - (void)replaceSelectionWithMarkupString:(NSString *)markupString baseURLString:(NSString *)baseURLString selectReplacement:(BOOL)selectReplacement;
 - (void)replaceSelectionWithText:(NSString *)text selectReplacement:(BOOL)selectReplacement;
 
-- (void)insertText:(NSString *)text;
+- (void)insertText:(NSString *)text selectInsertedText:(BOOL)selectInsertedText;
 - (void)insertNewline;
 
 - (void)setSelectionToDragCaret;
@@ -320,8 +322,10 @@ typedef enum {
 
 - (void)deleteSelection;
 - (void)deleteKeyPressed;
+
 - (void)applyStyle:(DOMCSSStyleDeclaration *)style;
-- (NSFont *)fontForCurrentPosition;
+- (BOOL)selectionStartHasStyle:(DOMCSSStyleDeclaration *)style;
+
 - (void)ensureCaretVisible;
 
 - (WebScriptObject *)windowScriptObject;
index 1e35bae2b2c9ebc641028fdf5623454a6f89d1bc..89f9ef19605609e46b1d1672a5f05a07d4a7a3b5 100644 (file)
@@ -99,21 +99,22 @@ using DOM::NodeImpl;
 using DOM::Position;
 using DOM::Range;
 using DOM::Selection;
+using DOM::UPSTREAM;
 
+using khtml::ApplyStyleCommand;
 using khtml::Decoder;
 using khtml::DeleteSelectionCommand;
 using khtml::EditCommand;
 using khtml::EditCommandImpl;
 using khtml::MoveSelectionCommand;
-using khtml::ReplaceSelectionCommand;
 using khtml::parseURL;
-using khtml::ApplyStyleCommand;
 using khtml::RenderCanvas;
 using khtml::RenderImage;
 using khtml::RenderObject;
 using khtml::RenderPart;
 using khtml::RenderStyle;
 using khtml::RenderWidget;
+using khtml::ReplaceSelectionCommand;
 using khtml::TypingCommand;
 
 using KJS::SavedProperties;
@@ -446,8 +447,8 @@ static bool initializedKJS = FALSE;
 - (BOOL)isSelectionEditable
 {
     // EDIT FIXME: This needs to consider the entire selected range
-       NodeImpl *startNode = _part->selection().start().node();
-       return startNode ? startNode->isContentEditable() : NO;
+    NodeImpl *startNode = _part->selection().start().node();
+    return startNode ? startNode->isContentEditable() : NO;
 }
 
 - (WebSelectionState)selectionState
@@ -1169,15 +1170,6 @@ static HTMLFormElementImpl *formElementFromDOMElement(DOMElement *element)
     return _part->attributedString([startNode _nodeImpl], startOffset, [endNode _nodeImpl], endOffset);
 }
 
-- (NSFont *)renderedFontForNode:(DOMNode *)node
-{
-    RenderObject *renderer = [node _nodeImpl]->renderer();
-    if (renderer) {
-        return renderer->style()->font().getNSFont();
-    }
-    return nil;
-}
-
 - (DOMNode *)selectionStart
 {
     return [DOMNode _nodeWithImpl:_part->selectionStart()];
@@ -1424,9 +1416,15 @@ static HTMLFormElementImpl *formElementFromDOMElement(DOMElement *element)
     ASSERT(startContainer->getDocument());
     ASSERT(startContainer->getDocument() == endContainer->getDocument());
     
-    DocumentImpl *doc = startContainer->getDocument();
-    doc->updateLayout();
-    Selection selection(Position(startContainer, [range startOffset]), Position(endContainer, [range endOffset]));
+    _part->xmlDocImpl()->updateLayout();
+
+    // Work around bug where isRenderedContent returns false for <br> elements at the ends of lines.
+    // If that bug wasn't an issue, we could just make the position from the range directly.
+    Position start(startContainer, [range startOffset]);
+    Position end(endContainer, [range endOffset]);
+    start = start.equivalentDeepPosition().closestRenderedPosition(UPSTREAM);
+
+    Selection selection(start, end);
     selection.setAffinity(static_cast<DOM::EAffinity>(selectionAffinity));
     _part->setSelection(selection);
 }
@@ -1517,12 +1515,12 @@ static HTMLFormElementImpl *formElementFromDOMElement(DOMElement *element)
     [self ensureCaretVisible];
 }
 
-- (void)insertText:(NSString *)text
+- (void)insertText:(NSString *)text selectInsertedText:(BOOL)selectInsertedText
 {
     if (!_part || !_part->xmlDocImpl())
         return;
     
-    TypingCommand::insertText(_part->xmlDocImpl(), text);
+    TypingCommand::insertText(_part->xmlDocImpl(), text, selectInsertedText);
     [self ensureCaretVisible];
 }
 
@@ -1602,9 +1600,22 @@ static HTMLFormElementImpl *formElementFromDOMElement(DOMElement *element)
     _part->applyStyle([style _styleDeclarationImpl]);
 }
 
-- (NSFont *)fontForCurrentPosition
+- (BOOL)selectionStartHasStyle:(DOMCSSStyleDeclaration *)style
+{
+    if (!_part)
+        return NO;
+    return _part->selectionStartHasStyle([style _styleDeclarationImpl]);
+}
+
+- (NSFont *)fontForSelection:(BOOL *)hasMultipleFonts
 {
-    return _part ? _part->fontForCurrentPosition() : nil;
+    bool multipleFonts = false;
+    NSFont *font = nil;
+    if (_part)
+        font = _part->fontForSelection(hasMultipleFonts ? &multipleFonts : 0);
+    if (hasMultipleFonts)
+        *hasMultipleFonts = multipleFonts;
+    return font;
 }
 
 - (void)ensureCaretVisible
index 233057493eb78536204d210938160c005bb205e2..503e7ebd4dea3379e85148b5df05fe113f211d31 100644 (file)
@@ -1,3 +1,131 @@
+2004-09-06  Darin Adler  <darin@apple.com>
+
+        Reviewed by John.
+
+        - fixed <rdar://problem/3696542> REGRESSION (Mail): Editable WebKit doesn't support underline yet (in the iChat profile window, at least)
+        - fixed <rdar://problem/3780249> REGRESSION (Mail): copy style/paste style doesn't work in HTML editing in Mail
+        - fixed <rdar://problem/3788857> REGRESSION (Mail): Home and End keys don't work in message composer
+        - fixed <rdar://problem/3788884> REGRESSION (Mail): ctrl-d emacs key binding does not work (delete forward)
+        - fixed <rdar://problem/3788890> REGRESSION (Mail): ctrl-k emacs key binding does not work (delete to end of paragraph)
+        - fixed <rdar://problem/3788899> REGRESSION (Mail): ctrl-y emacs key binding does not work (yank)
+        - fixed <rdar://problem/3788901> REGRESSION (Mail): ctrl-o emacs key binding does not work (insert newline in front of insertion point)
+        - fixed <rdar://problem/3788908> REGRESSION (Mail): ctrl-left-arrow emacs key binding does not work (move to beginning of line)
+        - fixed <rdar://problem/3788913> REGRESSION (Mail): ctrl-right-arrow emacs key binding does not work (move to end of line)
+        - implemented a first cut at other attribute changes from Text Panel besides underline (bugs?)
+        - dealt with a couple of FIXMEs in WebHTMLView.m
+        - updated list of not-yet-implemented methods in WebHTMLView.m
+        - fixed many deletion operations to call the correct editing delegate methods
+
+        * WebView.subproj/WebFrameViewPrivate.h: Remove _scrollToTopLeft and _scrollToBottomLeft.
+        No one was calling them anyway, so they should really have been marked internal and not private.
+        * WebView.subproj/WebFrameView.m:
+        (-[WebFrameView scrollToBeginningOfDocument:]): Renamed _scrollToTopLeft to this, so the home key
+        would start working with the key bindings machinery.
+        (-[WebFrameView scrollToEndOfDocument:]): Same thing, for end key.
+        (-[WebFrameView keyDown:]): Update for name changes, and also make sure we don't try to grab
+        control-arrow keys here (probably not necessary, but good anyway).
+
+        * WebView.subproj/WebHTMLViewInternal.h: Added keyDownEvent field, and startNewKillRingSequence
+        and nextResponderDisabledOnce flags.
+        * WebView.subproj/WebHTMLView.m:
+        Rearrange declarations at the top of the file so that external things are up with
+        the #import directives and things inside this file are declared below.
+        (-[WebHTMLView _shouldReplaceSelectionWithText:givenAction:]): Ditto.
+        (-[WebHTMLView _calculatePrintHeight]): Moved up into the "internal to file" category.
+        (-[WebHTMLView _updateTextSizeMultiplier]): Ditto.
+        (-[WebHTMLView _selectedRange]): Added.
+        (-[WebHTMLView _openLinkFromMenu:]): Left this method lying around even though I deleted the
+        other APPKIT_CODE_FOR_REFERENCE in case this shows up in the context menu we are now sharing
+        with the AppKit. Chris will look at this later, and he can delete it then.
+        (+[WebHTMLView initialize]): Call _NSInitializeKillRing.
+        (-[WebHTMLView _documentRange]): Added.
+        (-[WebHTMLView string]): Call the bridge to get the plain text rather than making an attributed
+        string and then getting the text from there.
+        (-[WebHTMLView becomeFirstResponder]): Set startNewKillRingSequence flag, so that new deletions
+        will create a new kill ring entry.
+        (-[WebHTMLView moveToBeginningOfDocument:]): Use backward direction instead of left direction.
+        (-[WebHTMLView moveToBeginningOfDocumentAndModifySelection:]): Ditto.
+        (-[WebHTMLView moveToBeginningOfLine:]): Ditto.
+        (-[WebHTMLView moveToBeginningOfLineAndModifySelection:]): Ditto.
+        (-[WebHTMLView moveToBeginningOfParagraph:]): Ditto, also use WebSelectToParagraphBoundary.
+        (-[WebHTMLView moveToBeginningOfParagraphAndModifySelection:]): Ditto.
+        (-[WebHTMLView moveToEndOfDocument:]): Use forward direction instead of right direction.
+        (-[WebHTMLView moveToEndOfDocumentAndModifySelection:]): Ditto.
+        (-[WebHTMLView moveToEndOfLine:]): Ditto.
+        (-[WebHTMLView moveToEndOfLineAndModifySelection:]): Ditto.
+        (-[WebHTMLView moveToEndOfParagraph:]): Ditto, also use WebSelectToParagraphBoundary.
+        (-[WebHTMLView moveToEndOfParagraphAndModifySelection:]): Ditto.
+        (-[WebHTMLView _shouldDeleteRange:]): Added.
+        (-[WebHTMLView _deleteRange:preflight:killRing:prepend:]): Added.
+        (-[WebHTMLView delete:]): Changed to call new _deleteRange method.
+        (-[WebHTMLView cut:]): Changed to preflight property and call new _deleteRange method.
+        (-[WebHTMLView _selectionFontAttributes]): Added.
+        (-[WebHTMLView _selectionFontAttributesAsRTF]): Added.
+        (-[WebHTMLView _fontAttributesFromFontPasteboard]): Added.
+        (-[WebHTMLView _emptyStyle]): Added.
+        (-[WebHTMLView _styleFromFontAttributes:]): Added.
+        (-[WebHTMLView _applyStyleToSelection:]): Added.
+        (-[WebHTMLView copyFont:]): Implemented.
+        (-[WebHTMLView pasteFont:]): Implemented.
+        (-[WebHTMLView _originalFontA]): Added.
+        (-[WebHTMLView _originalFontB]): Added.
+        (-[WebHTMLView _addToStyle:fontA:fontB:]): Added. Has code from the method that figures out
+        what the font manager is doing for changeFont:, now needed for changeAttribute: too.
+        (-[WebHTMLView _styleFromFontManagerOperation]): Renamed and now calls shared methods.
+        (-[WebHTMLView changeFont:]): Call shared method, still does the same thing.
+        (-[WebHTMLView _colorAsString:]): Added. Has code from the method we were using with the
+        color panel before.
+        (-[WebHTMLView _shadowAsString:]): Added.
+        (-[WebHTMLView _styleForAttributeChange:]): Added.
+        (-[WebHTMLView changeAttributes:]): Implemented.
+        (-[WebHTMLView _styleFromColorPanelWithSelector:]): Renamed and now calls shared methods.
+        (-[WebHTMLView _changeCSSColorUsingSelector:inRange:]): Call method by new name.
+        (-[WebHTMLView changeDocumentBackgroundColor:]): Call method by new name.
+        (-[WebHTMLView changeColor:]): Changed around a bit; still doesn't work yet.
+        (-[WebHTMLView _alignSelectionUsingCSSValue:]): Call shared methods.
+        (-[WebHTMLView indent:]): Removed, since NSTextView doesn't implement this method. Added to list
+        of methods to possibly implement later in the file.
+        (-[WebHTMLView insertTab:]): Call insertText: to save code and so we get WebViewInsertActionTyped
+        instead of WebViewInsertActionPasted.
+        (-[WebHTMLView changeCaseOfLetter:]): Removed, since NSTextView doesn't implement this method.
+        Added to list of methods to possibly implement later in the file.
+        (-[WebHTMLView _deleteWithDirection:granularity:killRing:]): Added.
+        (-[WebHTMLView deleteForward:]): Implemented. This makes Control-D work.
+        (-[WebHTMLView deleteBackwardByDecomposingPreviousCharacter:]): Implemented by just calling
+        deleteBackward for now; probably better than doing nothing.
+        (-[WebHTMLView deleteWordForward:]): Changed to call new _delete method above. Fixes things
+        so that we delete the selection if there is one, get the appropriate delegate calls, handle
+        the kill ring properly, and don't do any selection if we can't delete.
+        (-[WebHTMLView deleteWordBackward:]): Ditto.
+        (-[WebHTMLView deleteToBeginningOfLine:]): Ditto.
+        (-[WebHTMLView deleteToEndOfLine:]): Ditto.
+        (-[WebHTMLView deleteToBeginningOfParagraph:]): Ditto.
+        (-[WebHTMLView deleteToEndOfParagraph:]): Ditto. Added additional behavior needed since this
+        is bound to Control-K, so it's not really just delete to end of paragraph.
+        (-[WebHTMLView insertNewlineIgnoringFieldEditor:]): Added. Calls insertNewline:.
+        (-[WebHTMLView insertTabIgnoringFieldEditor:]): Added. Calls insertTab:.
+        (-[WebHTMLView subscript:]): Added.
+        (-[WebHTMLView superscript:]): Added.
+        (-[WebHTMLView unscript:]): Added.
+        (-[WebHTMLView underline:]): Added.
+        (-[WebHTMLView yank:]): Added.
+        (-[WebHTMLView yankAndSelect:]): Added. Calls _insertText.
+        (-[WebHTMLView _arrowKeyDownEventSelectorIfPreprocessing:]): Added. Part of workaround for
+        control-arrow key trouble.
+        (-[WebHTMLView respondsToSelector:]): Added. More of workaround.
+        (-[WebHTMLView nextResponder:]): Added. More of workaround.
+        (-[WebHTMLView _selectionChanged]): Set startNewKillRingSequence flag, so that new deletions
+        will create a new kill ring entry.
+        (-[WebHTMLView _updateFontPanel]): Remove a bunch of code here that wasn't working very well
+        because it walked a DOM range incorrectly, and instead use the new method that does all the
+        right stuff on the other side of the bridge.
+        (-[WebHTMLView _insertText:selectInsertedText:]): Added new helper method for use by both
+        insertText and yankAndSelect, with most of the guts of insertText and one additional parameter.
+        (-[WebHTMLView insertText:]): Call the new _insertText.
+
+        * WebView.subproj/WebView.m: Use macros to make the forwarding from WebView more terse.
+        Updated the list to include a few methods it didn't before.
+
 2004-09-06  John Sullivan  <sullivan@apple.com>
 
         Reviewed by Darin.
         (-[WebHTMLView _replaceSelectionWithPasteboard:selectReplacement:allowPlainText:]): Added allowPlainText
         boolean, moved method into new location in file so it can be in the right category.
         (-[WebHTMLView concludeDragForDraggingInfo:]): Pass YES for allowPlainText.
+        (-[WebHTMLView keyDown:]): Set keyDownEvent field for use by workaround below.
         (-[WebHTMLView centerSelectionInVisibleArea:]): Moved here from WebView.
         (-[WebHTMLView _alterCurrentSelection:direction:granularity:]): Ditto.
         (-[WebHTMLView moveBackward:]): Ditto.
index 039ea955d9cd16f1187f06b97f11716f2214d86b..8e0f63ed693753fbbc3d57af37482cef7b6d847f 100644 (file)
@@ -254,17 +254,6 @@ enum {
     [self _pageHorizontally:NO];
 }
 
-- (void)_scrollToTopLeft
-{
-    [[self _contentView] scrollPoint:[[[self _scrollView] documentView] frame].origin];
-}
-
-- (void)_scrollToBottomLeft
-{
-    NSRect frame = [[[self _scrollView] documentView] frame];
-    [[self _contentView] scrollPoint:NSMakePoint(frame.origin.x, NSMaxY(frame))];
-}
-
 - (void)scrollLineUp:(id)sender
 {
     [self _scrollLineVertically: YES];
@@ -573,6 +562,17 @@ static NSMutableDictionary *viewTypes;
     [self _tile];
 }
 
+- (void)scrollToBeginningOfDocument:(id)sender
+{
+    [[self _contentView] scrollPoint:[[[self _scrollView] documentView] frame].origin];
+}
+
+- (void)scrollToEndOfDocument:(id)sender
+{
+    NSRect frame = [[[self _scrollView] documentView] frame];
+    [[self _contentView] scrollPoint:NSMakePoint(frame.origin.x, NSMaxY(frame))];
+}
+
 - (void)keyDown:(NSEvent *)event
 {
     NSString *characters = [event characters];
@@ -633,7 +633,7 @@ static NSMutableDictionary *viewTypes;
                     callSuper = YES;
                     break;
                 }
-                [self _scrollToTopLeft];
+                [self scrollToBeginningOfDocument:nil];
                 callSuper = NO;
                 break;
             case NSEndFunctionKey:
@@ -641,12 +641,12 @@ static NSMutableDictionary *viewTypes;
                     callSuper = YES;
                     break;
                 }
-                [self _scrollToBottomLeft];
+                [self scrollToEndOfDocument:nil];
                 callSuper = NO;
                 break;
             case NSUpArrowFunctionKey:
-                // We don't handle shifted arrow keys here, so let super have a chance.
-                if ([event modifierFlags] & NSShiftKeyMask) {
+                // We don't handle shifted or control-arrow keys here, so let super have a chance.
+                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
                     callSuper = YES;
                     break;
                 }
@@ -659,7 +659,7 @@ static NSMutableDictionary *viewTypes;
                     break;
                 }
                 if ([event modifierFlags] & NSCommandKeyMask) {
-                    [self _scrollToTopLeft];
+                    [self scrollToBeginningOfDocument:nil];
                 } else if ([event modifierFlags] & NSAlternateKeyMask) {
                     [self scrollPageUp:nil];
                 } else {
@@ -668,8 +668,8 @@ static NSMutableDictionary *viewTypes;
                 callSuper = NO;
                 break;
             case NSDownArrowFunctionKey:
-                // We don't handle shifted arrow keys here, so let super have a chance.
-                if ([event modifierFlags] & NSShiftKeyMask) {
+                // We don't handle shifted or control-arrow keys here, so let super have a chance.
+                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
                     callSuper = YES;
                     break;
                 }
@@ -682,7 +682,7 @@ static NSMutableDictionary *viewTypes;
                     break;
                 }
                 if ([event modifierFlags] & NSCommandKeyMask) {
-                    [self _scrollToBottomLeft];
+                    [self scrollToEndOfDocument:nil];
                 } else if ([event modifierFlags] & NSAlternateKeyMask) {
                     [self scrollPageDown:nil];
                 } else {
@@ -691,8 +691,8 @@ static NSMutableDictionary *viewTypes;
                 callSuper = NO;
                 break;
             case NSLeftArrowFunctionKey:
-                // We don't handle shifted arrow keys here, so let super have a chance.
-                if ([event modifierFlags] & NSShiftKeyMask) {
+                // We don't handle shifted or control-arrow keys here, so let super have a chance.
+                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
                     callSuper = YES;
                     break;
                 }
@@ -719,8 +719,8 @@ static NSMutableDictionary *viewTypes;
                 callSuper = NO;
                 break;
             case NSRightArrowFunctionKey:
-                // We don't handle shifted arrow keys here, so let super have a chance.
-                if ([event modifierFlags] & NSShiftKeyMask) {
+                // We don't handle shifted or control-arrow keys here, so let super have a chance.
+                if ([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask)) {
                     callSuper = YES;
                     break;
                 }
index 7084bbe137878dc01ac4bbdac127cca37f5dba4c..5bcfde4f420581546e7a1644e020ea9b8ca70b70 100644 (file)
@@ -41,8 +41,6 @@
 - (void)_scrollLineHorizontally: (BOOL)left;
 - (void)_pageLeft;
 - (void)_pageRight;
-- (void)_scrollToTopLeft;
-- (void)_scrollToBottomLeft;
 - (void)_lineLeft;
 - (void)_lineRight;
 - (void)_goBack;
index 7084bbe137878dc01ac4bbdac127cca37f5dba4c..5bcfde4f420581546e7a1644e020ea9b8ca70b70 100644 (file)
@@ -41,8 +41,6 @@
 - (void)_scrollLineHorizontally: (BOOL)left;
 - (void)_pageLeft;
 - (void)_pageRight;
-- (void)_scrollToTopLeft;
-- (void)_scrollToBottomLeft;
 - (void)_lineLeft;
 - (void)_lineRight;
 - (void)_goBack;
index a8e92a5476ea249b4a55be80c75beb03c9cb0a90..30c2339d8163a0556a277e87e2fbbc6dd8653b48 100644 (file)
 // <rdar://problem/3630640>: "Calling interpretKeyEvents: in a custom text view can fail to process keys right after app startup"
 #import <AppKit/NSKeyBindingManager.h>
 
+// Kill ring calls. Would be better to use NSKillRing.h, but that's not available in SPI.
+void _NSInitializeKillRing(void);
+void _NSAppendToKillRing(NSString *);
+void _NSPrependToKillRing(NSString *);
+NSString *_NSYankFromKillRing(void);
+NSString *_NSYankPreviousFromKillRing(void);
+void _NSNewKillRingSequence(void);
+void _NSSetKillRingToYankedState(void);
+void _NSResetKillRingOperationFlag(void);
+
+@interface NSView (AppKitSecretsIKnowAbout)
+- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView;
+- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect;
+- (NSRect)_dirtyRect;
+- (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants;
+@end
+
+@interface NSApplication (AppKitSecretsIKnowAbout)
+- (void)speakString:(NSString *)string;
+@end
+
+@interface NSWindow (AppKitSecretsIKnowAbout)
+- (id)_newFirstResponderAfterResigning;
+@end
+
+@interface NSAttributedString (AppKitSecretsIKnowAbout)
+- (id)_initWithDOMRange:(DOMRange *)domRange;
+- (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
+@end
+
+@interface NSSpellChecker (CurrentlyPrivateForTextView)
+- (void)learnWord:(NSString *)word;
+@end
+
 // By imaging to a width a little wider than the available pixels,
 // thin pages will be scaled down a little, matching the way they
 // print in IE and Camino. This lets them use fewer sheets than they
 #define DRAG_LINK_URL_FONT_SIZE   10.0
 
 #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_3
-#define BUILT_ON_TIGER_OR_LATER
+#define USE_APPKIT_FOR_ATTRIBUTED_STRINGS
 #endif
 
+// Any non-zero value will do, but using something recognizable might help us debug some day.
+#define TRACKING_RECT_TAG 0xBADFACE
+
 static BOOL forceRealHitTest = NO;
 
 @interface WebHTMLView (WebTextSizing) <_web_WebDocumentTextSizing>
 @end
 
-@interface WebHTMLView (WebFileInternal)
+@interface WebHTMLView (WebHTMLViewFileInternal)
 - (BOOL)_imageExistsAtPaths:(NSArray *)paths;
 - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText;
 - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText;
 - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
 - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action;
+- (float)_calculatePrintHeight;
+- (void)_updateTextSizeMultiplier;
+- (DOMRange *)_selectedRange;
 @end
 
-@interface WebHTMLView (WebHTMLViewPrivate)
+@interface WebHTMLView (WebForwardDeclaration) // FIXME: Put this in a normal category and stop doing the forward declaration trick.
 - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize;
-- (void)_updateTextSizeMultiplier;
-- (float)_calculatePrintHeight;
-- (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation;
 @end
 
 @interface WebHTMLView (WebNSTextInputSupport) <NSTextInput>
 - (void)_updateSelectionForInputManager;
+- (void)_insertText:(NSString *)text selectInsertedText:(BOOL)selectText;
 @end
 
-// Any non-zero value will do, but using somethign recognizable might help us debug some day.
-#define TRACKING_RECT_TAG 0xBADFACE
-
-@interface NSView (AppKitSecretsIKnowAbout)
-- (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView;
-- (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect;
-- (NSRect)_dirtyRect;
-- (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants;
-@end
-
-@interface NSApplication (AppKitSecretsIKnowAbout)
-- (void)speakString:(NSString *)string;
-@end
-
-@interface NSWindow (AppKitSecretsIKnowAbout)
-- (id)_newFirstResponderAfterResigning;
-@end
-
-@interface NSView (WebHTMLViewPrivate)
+@interface NSView (WebHTMLViewFileInternal)
 - (void)_web_setPrintingModeRecursive;
 - (void)_web_clearPrintingModeRecursive;
 - (void)_web_layoutIfNeededRecursive:(NSRect)rect testDirtyRect:(bool)testDirtyRect;
 @end
 
-@interface NSMutableDictionary (WebHTMLViewPrivate)
+@interface NSMutableDictionary (WebHTMLViewFileInternal)
 - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key;
 @end
 
-@interface WebElementOrTextFilter : NSObject <DOMNodeFilter>
-+ (WebElementOrTextFilter *)filter;
-@end
-
-@interface NSAttributedString(AppKitSecretsIKnowAbout)
-- (id)_initWithDOMRange:(DOMRange *)domRange;
-- (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources;
-@end
-
-@interface NSSpellChecker(CurrentlyPrivateForTextView)
-- (void)learnWord:(NSString *)word;
-@end
-
-static WebElementOrTextFilter *elementOrTextFilterInstance = nil;
-
 // Handles the complete: text command
 @interface WebTextCompleteController : NSObject
 {
@@ -190,7 +195,7 @@ static WebElementOrTextFilter *elementOrTextFilterInstance = nil;
 
 @end
 
-@implementation WebHTMLView (WebFileInternal)
+@implementation WebHTMLView (WebHTMLViewFileInternal)
 
 - (BOOL)_imageExistsAtPaths:(NSArray *)paths
 {
@@ -286,7 +291,7 @@ static WebElementOrTextFilter *elementOrTextFilterInstance = nil;
         return fragment;
     }
     
-#ifdef BUILT_ON_TIGER_OR_LATER
+#ifdef USE_APPKIT_FOR_ATTRIBUTED_STRINGS
     NSAttributedString *string = nil;
     if ([types containsObject:NSRTFDPboardType]) {
         string = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL];
@@ -332,7 +337,7 @@ static WebElementOrTextFilter *elementOrTextFilterInstance = nil;
 {
     DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard allowPlainText:allowPlainText];
     WebBridge *bridge = [self _bridge];
-    if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[bridge selectedDOMRange] givenAction:WebViewInsertActionPasted]) {
+    if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[self _selectedRange] givenAction:WebViewInsertActionPasted]) {
         [bridge replaceSelectionWithFragment:fragment selectReplacement:NO];
     }
 }
@@ -351,11 +356,31 @@ static WebElementOrTextFilter *elementOrTextFilterInstance = nil;
 - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action
 {
     WebView *webView = [self _webView];
-    DOMRange *selectedRange = [[self _bridge] selectedDOMRange];
-
+    DOMRange *selectedRange = [self _selectedRange];
     return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:selectedRange givenAction:action];
 }
 
+// Calculate the vertical size of the view that fits on a single page
+- (float)_calculatePrintHeight
+{
+    // Obtain the print info object for the current operation
+    NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo];
+    
+    // Calculate the page height in points
+    NSSize paperSize = [pi paperSize];
+    return paperSize.height - [pi topMargin] - [pi bottomMargin];
+}
+
+- (void)_updateTextSizeMultiplier
+{
+    [[self _bridge] setTextSizeMultiplier:[[self _webView] textSizeMultiplier]];    
+}
+
+- (DOMRange *)_selectedRange
+{
+    return [[self _bridge] selectedDOMRange];
+}
+
 @end
 
 @implementation WebHTMLView (WebPrivate)
@@ -739,7 +764,7 @@ static WebHTMLView *lastHitView = nil;
 - (WebArchive *)_selectedArchive
 {
     NSArray *nodes;
-    NSString *markupString = [[self _bridge] markupStringFromRange:[[self _bridge] selectedDOMRange] nodes:&nodes];
+    NSString *markupString = [[self _bridge] markupStringFromRange:[self _selectedRange] nodes:&nodes];
     return [[self _dataSource] _archiveWithMarkupString:markupString nodes:nodes];
 }
 
@@ -1171,9 +1196,29 @@ static WebHTMLView *lastHitView = nil;
     [[NSSpellChecker sharedSpellChecker] learnWord:[self selectedString]];
 }
 
+#if APPKIT_CODE_FOR_REFERENCE
+
+- (void)_openLinkFromMenu:(id)sender
+{
+    NSTextStorage *text = _getTextStorage(self);
+    NSRange charRange = [self selectedRange];
+    if (charRange.location != NSNotFound && charRange.length > 0) {
+        id link = [text attribute:NSLinkAttributeName atIndex:charRange.location effectiveRange:NULL];
+        if (link) {
+            [self clickedOnLink:link atIndex:charRange.location];
+        } else {
+            NSString *string = [[text string] substringWithRange:charRange];
+            link = [NSURL URLWithString:string];
+            if (link) [[NSWorkspace sharedWorkspace] openURL:link];
+        }
+    }
+}
+
+#endif
+
 @end
 
-@implementation NSView (WebHTMLViewPrivate)
+@implementation NSView (WebHTMLViewFileInternal)
 
 - (void)_web_setPrintingModeRecursive
 {
@@ -1197,7 +1242,7 @@ static WebHTMLView *lastHitView = nil;
 
 @end
 
-@implementation NSMutableDictionary (WebHTMLViewPrivate)
+@implementation NSMutableDictionary (WebHTMLViewFileInternal)
 
 - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key
 {
@@ -1219,10 +1264,10 @@ static WebHTMLView *lastHitView = nil;
 @interface NSToolTipPanel : NSPanel
 @end
 
-@interface NSToolTipPanel (WebHTMLViewPrivate)
+@interface NSToolTipPanel (WebHTMLViewFileInternal)
 @end
 
-@implementation NSToolTipPanel (WebHTMLViewPrivate)
+@implementation NSToolTipPanel (WebHTMLViewFileInternal)
 
 - (void)setAcceptsMouseMovedEvents:(BOOL)flag
 {
@@ -1245,6 +1290,7 @@ static WebHTMLView *lastHitView = nil;
 {
     WebKitInitializeUnicode();
     [NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes] returnTypes:nil];
+    _NSInitializeKillRing();
 }
 
 - (id)initWithFrame:(NSRect)frame
@@ -1252,7 +1298,6 @@ static WebHTMLView *lastHitView = nil;
     [super initWithFrame:frame];
     
     // Make all drawing go through us instead of subviews.
-    // The bulk of the code to handle this is in WebHTMLViewPrivate.m.
     if (NSAppKitVersionNumber >= 711) {
         [self _setDrawsOwnDescendants:YES];
     }
@@ -1695,16 +1740,20 @@ static WebHTMLView *lastHitView = nil;
     return [[self _bridge] searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag];
 }
 
+- (DOMRange *)_documentRange
+{
+    return [[[self _bridge] DOMDocument] _documentRange];
+}
+
 - (NSString *)string
 {
-    // FIXME: We should be using WebCore logic for converting the document into a string and not creating an attributed string to do this.
-    return [[self attributedString] string];
+    return [[self _bridge] stringForRange:[self _documentRange]];
 }
 
 - (NSAttributedString *)_attributeStringFromDOMRange:(DOMRange *)range
 {
     NSAttributedString *attributedString = nil;
-#ifdef BUILT_ON_TIGER_OR_LATER
+#ifdef USE_APPKIT_FOR_ATTRIBUTED_STRINGS
     attributedString = [[[NSAttributedString alloc] _initWithDOMRange:range] autorelease];
 #endif
     return attributedString;
@@ -1729,7 +1778,7 @@ static WebHTMLView *lastHitView = nil;
 - (NSAttributedString *)selectedAttributedString
 {
     WebBridge *bridge = [self _bridge];
-    NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[bridge selectedDOMRange]];
+    NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[self _selectedRange]];
     if (attributedString == nil) {
         attributedString = [bridge selectedAttributedString];
     }
@@ -2245,6 +2294,7 @@ static WebHTMLView *lastHitView = nil;
         [[self window] makeFirstResponder:view];
     }
     [self updateFocusDisplay];
+    _private->startNewKillRingSequence = YES;
     return YES;
 }
 
@@ -2414,16 +2464,6 @@ static WebHTMLView *lastHitView = nil;
     return [[_private->pageRects objectAtIndex: (page-1)] rectValue];
 }
 
-// Calculate the vertical size of the view that fits on a single page
-- (float)_calculatePrintHeight {
-    // Obtain the print info object for the current operation
-    NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo];
-    
-    // Calculate the page height in points
-    NSSize paperSize = [pi paperSize];
-    return paperSize.height - [pi topMargin] - [pi bottomMargin];
-}
-
 - (void)drawPageBorderWithSize:(NSSize)borderSize
 {
     ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize]));    
@@ -2438,11 +2478,6 @@ static WebHTMLView *lastHitView = nil;
     [[self window] setAutodisplay:YES];
 }
 
-- (void)_updateTextSizeMultiplier
-{
-    [[self _bridge] setTextSizeMultiplier:[[self _webView] textSizeMultiplier]];    
-}
-
 - (BOOL)performKeyEquivalent:(NSEvent *)event
 {
     // Pass command-key combos through WebCore if there is a key binding available for
@@ -2475,23 +2510,25 @@ static WebHTMLView *lastHitView = nil;
 
 - (void)keyDown:(NSEvent *)event
 {
+    _private->keyDownEvent = event;
+
     WebBridge *bridge = [self _bridge];
     if ([bridge interceptKeyEvent:event toView:self]) {
         // WebCore processed a key event, bail on any outstanding complete: UI
         [_private->compController endRevertingChange:YES moveLeft:NO];
-        return;
+    } else if (_private->compController && [_private->compController filterKeyDown:event]) {
+        // Consumed by complete: popup window
+    } else {
+        // We're going to process a key event, bail on any outstanding complete: UI
+        [_private->compController endRevertingChange:YES moveLeft:NO];
+        if ([self _canType] && [self _interceptEditingKeyEvent:event]) {
+            // Consumed by key bindings manager.
+        } else {
+            [super keyDown:event];
+        }
     }
 
-    if (_private->compController && [_private->compController filterKeyDown:event])
-        return;     // consumed by complete: popup window
-
-    // We're going to process a key event, bail on any outstanding complete: UI
-    [_private->compController endRevertingChange:YES moveLeft:NO];
-    
-    if ([self _canType] && [self _interceptEditingKeyEvent:event]) 
-        return;
-    
-    [super keyDown:event];
+    _private->keyDownEvent = nil;
 }
 
 - (void)keyUp:(NSEvent *)event
@@ -2534,7 +2571,7 @@ static WebHTMLView *lastHitView = nil;
     WebBridge *bridge = [self _bridge];
     DOMRange *proposedRange = [bridge rangeByAlteringCurrentSelection:alteration direction:direction granularity:granularity];
     WebView *webView = [self _webView];
-    if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[bridge selectedDOMRange] toDOMRange:proposedRange affinity:[bridge selectionAffinity] stillSelecting:NO]) {
+    if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:proposedRange affinity:[bridge selectionAffinity] stillSelecting:NO]) {
         [bridge alterCurrentSelection:alteration direction:direction granularity:granularity];
     }
 }
@@ -2591,62 +2628,62 @@ static WebHTMLView *lastHitView = nil;
 
 - (void)moveToBeginningOfDocument:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectLeft granularity:WebSelectByDocument];
+    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectByDocument];
 }
 
 - (void)moveToBeginningOfDocumentAndModifySelection:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectLeft granularity:WebSelectByDocument];
+    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectByDocument];
 }
 
 - (void)moveToBeginningOfLine:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectLeft granularity:WebSelectToLineBoundary];
+    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectToLineBoundary];
 }
 
 - (void)moveToBeginningOfLineAndModifySelection:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectLeft granularity:WebSelectToLineBoundary];
+    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectToLineBoundary];
 }
 
 - (void)moveToBeginningOfParagraph:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectLeft granularity:WebSelectByParagraph];
+    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectToParagraphBoundary];
 }
 
 - (void)moveToBeginningOfParagraphAndModifySelection:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectLeft granularity:WebSelectByParagraph];
+    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectToParagraphBoundary];
 }
 
 - (void)moveToEndOfDocument:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectRight granularity:WebSelectByDocument];
+    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectByDocument];
 }
 
 - (void)moveToEndOfDocumentAndModifySelection:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectRight granularity:WebSelectByDocument];
+    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectByDocument];
 }
 
 - (void)moveToEndOfLine:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectRight granularity:WebSelectToLineBoundary];
+    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectToLineBoundary];
 }
 
 - (void)moveToEndOfLineAndModifySelection:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectRight granularity:WebSelectToLineBoundary];
+    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectToLineBoundary];
 }
 
 - (void)moveToEndOfParagraph:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectRight granularity:WebSelectByParagraph];
+    [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectToParagraphBoundary];
 }
 
 - (void)moveToEndOfParagraphAndModifySelection:(id)sender
 {
-    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectRight granularity:WebSelectByParagraph];
+    [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectToParagraphBoundary];
 }
 
 - (void)moveUp:(id)sender
@@ -2701,13 +2738,13 @@ static WebHTMLView *lastHitView = nil;
 
 - (void)pageDown:(id)sender
 {
-    // FIXME: This should to scroll page down, then move the caret to the top left.
+    // FIXME: This should move the caret down one page and then scroll to reveal it.
     ERROR("unimplemented");
 }
 
 - (void)pageUp:(id)sender
 {
-    // FIXME: This should to scroll page up, then move the caret to the top left.
+    // FIXME: This should move the caret up one page and then scroll to reveal it.
     ERROR("unimplemented");
 }
 
@@ -2717,7 +2754,7 @@ static WebHTMLView *lastHitView = nil;
     DOMRange *range = [bridge rangeByExpandingSelectionWithGranularity:granularity];
     if (range && ![range collapsed]) {
         WebView *webView = [self _webView];
-        if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[bridge selectedDOMRange] toDOMRange:range affinity:[bridge selectionAffinity] stillSelecting:NO]) {
+        if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:range affinity:[bridge selectionAffinity] stillSelecting:NO]) {
             [bridge setSelectedDOMRange:range affinity:[bridge selectionAffinity]];
         }
     }
@@ -2743,7 +2780,6 @@ static WebHTMLView *lastHitView = nil;
     if ([[self _bridge] tryDHTMLCopy]) {
         return;     // DHTML did the whole operation
     }
-
     if (![self _canCopy]) {
         NSBeep();
         return;
@@ -2751,18 +2787,33 @@ static WebHTMLView *lastHitView = nil;
     [self _writeSelectionToPasteboard:[NSPasteboard generalPasteboard]];
 }
 
-- (void)cut:(id)sender
+- (BOOL)_shouldDeleteRange:(DOMRange *)range
 {
-    if ([[self _bridge] tryDHTMLCut]) {
-        return;     // DHTML did the whole operation
-    }
+    if (range == nil || [range collapsed])
+        return NO;
+    WebView *webView = [self _webView];
+    return [[webView _editingDelegateForwarder] webView:webView shouldDeleteDOMRange:range];
+}
 
-    if (![self _canCut]) {
-        NSBeep();
+- (void)_deleteRange:(DOMRange *)range preflight:(BOOL)preflight killRing:(BOOL)killRing prepend:(BOOL)prepend
+{
+    if (![self _shouldDeleteRange:range]) {
         return;
     }
-    [self copy:sender];
-    [[self _bridge] deleteSelection];
+    WebBridge *bridge = [self _bridge];
+    if (killRing && _private->startNewKillRingSequence) {
+        _NSNewKillRingSequence();
+    }
+    [bridge setSelectedDOMRange:range affinity:NSSelectionAffinityUpstream];
+    if (killRing) {
+        if (prepend) {
+            _NSPrependToKillRing([bridge selectedString]);
+        } else {
+            _NSAppendToKillRing([bridge selectedString]);
+        }
+        _private->startNewKillRingSequence = NO;
+    }
+    [bridge deleteSelection];
 }
 
 - (void)delete:(id)sender
@@ -2771,7 +2822,23 @@ static WebHTMLView *lastHitView = nil;
         NSBeep();
         return;
     }
-    [[self _bridge] deleteSelection];
+    [self _deleteRange:[self _selectedRange] preflight:YES killRing:YES prepend:NO];
+}
+
+- (void)cut:(id)sender
+{
+    if ([[self _bridge] tryDHTMLCut]) {
+        return;     // DHTML did the whole operation
+    }
+    if (![self _canCut]) {
+        NSBeep();
+        return;
+    }
+    DOMRange *range = [self _selectedRange];
+    if ([self _shouldDeleteRange:range]) {
+        [self _writeSelectionToPasteboard:[NSPasteboard generalPasteboard]];
+        [self _deleteRange:[self _selectedRange] preflight:NO killRing:YES prepend:NO];
+    }
 }
 
 - (void)paste:(id)sender
@@ -2779,28 +2846,98 @@ static WebHTMLView *lastHitView = nil;
     if ([[self _bridge] tryDHTMLPaste]) {
         return;     // DHTML did the whole operation
     }
-
     if (![self _canPaste]) {
         NSBeep();
         return;
     }
-
     [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:YES];
 }
 
+- (NSDictionary *)_selectionFontAttributes
+{
+    NSFont *font = [[self _bridge] fontForSelection:NULL];
+    if (font == nil)
+        return nil;
+    return [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
+}
+
+- (NSData *)_selectionFontAttributesAsRTF
+{
+    NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"x" attributes:[self _selectionFontAttributes]];
+    NSData *data = [string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:nil];
+    [string release];
+    return data;
+}
+
+- (NSDictionary *)_fontAttributesFromFontPasteboard
+{
+    NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard];
+    if (fontPasteboard == nil)
+        return nil;
+    NSData *data = [fontPasteboard dataForType:NSFontPboardType];
+    if (data == nil || [data length] == 0)
+        return nil;
+    // NSTextView does something more efficient by parsing the attributes only, but that's not available in API.
+    NSAttributedString *string = [[[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL] autorelease];
+    if (string == nil || [string length] == 0)
+        return nil;
+    return [string fontAttributesInRange:NSMakeRange(0, 1)];
+}
+
+- (DOMCSSStyleDeclaration *)_emptyStyle
+{
+    return [[[self _bridge] DOMDocument] createCSSStyleDeclaration];
+}
+
+- (DOMCSSStyleDeclaration *)_styleFromFontAttributes:(NSDictionary *)dictionary
+{
+    DOMCSSStyleDeclaration *style = [self _emptyStyle];
+
+    NSFont *font = [dictionary objectForKey:NSFontAttributeName];
+    if (font != nil) {
+        NSFontManager *fm = [NSFontManager sharedFontManager];
+        [style setFontFamily:[font familyName]];
+        [style setFontSize:[NSString stringWithFormat:@"%0.fpx", [font pointSize]]];
+        if ([fm weightOfFont:font] >= 9) {
+            [style setFontWeight:@"bold"];
+        } else {
+            [style setFontWeight:@"normal"];
+        }
+        if (([fm traitsOfFont:font] & NSItalicFontMask) != 0) {
+            [style setFontStyle:@"italic"];
+        } else {
+            [style setFontStyle:@"normal"];
+        }
+    }
+
+    return style;
+}
+
+- (void)_applyStyleToSelection:(DOMCSSStyleDeclaration *)style
+{
+    if (style == nil || [style length] == 0)
+        return;
+    WebView *webView = [self _webView];
+    WebBridge *bridge = [self _bridge];
+    if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:[self _selectedRange]]) {
+        [bridge applyStyle:style];
+    }
+}
+
 - (void)copyFont:(id)sender
 {
-    // Font is RTF with a single character in it.
-    // NSTextView uses the first character in the selection, or a space if there are no characters.
-    ERROR("unimplemented");
+    // Put RTF with font attributes on the pasteboard.
+    // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font.
+    NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard];
+    [fontPasteboard declareTypes:[NSArray arrayWithObject:NSFontPboardType] owner:nil];
+    [fontPasteboard setData:[self _selectionFontAttributesAsRTF] forType:NSFontPboardType];
 }
 
 - (void)pasteFont:(id)sender
 {
-    // FIXME: Support RTF to HTML (or DOM) conversion.
-    // Font is RTF. Call fontAttributesInRange to extract the attributes to apply.
-    // Then convert the RTF into CSS.
-    ERROR("unimplemented");
+    // Read RTF with font attributes from the pasteboard.
+    // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font.
+    [self _applyStyleToSelection:[self _styleFromFontAttributes:[self _fontAttributesFromFontPasteboard]]];
 }
 
 - (void)pasteAsPlainText:(id)sender
@@ -2814,19 +2951,30 @@ static WebHTMLView *lastHitView = nil;
 
 - (void)pasteAsRichText:(id)sender
 {
+    // Since rich text always beats plain text when both are on the pasteboard, it's not
+    // clear how this is different from plain old paste.
     [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:NO];
 }
 
-- (DOMCSSStyleDeclaration *)_fontManagerOperationAsStyle
+- (NSFont *)_originalFontA
 {
-    WebBridge *bridge = [self _bridge];
-    DOMCSSStyleDeclaration *style = [[bridge DOMDocument] createCSSStyleDeclaration];
+    return [[NSFontManager sharedFontManager] fontWithFamily:@"Helvetica" traits:0 weight:5 size:10];
+}
+
+- (NSFont *)_originalFontB
+{
+    return [[NSFontManager sharedFontManager] fontWithFamily:@"Times" traits:(NSBoldFontMask | NSItalicFontMask) weight:10 size:12];
+}
+
+- (void)_addToStyle:(DOMCSSStyleDeclaration *)style fontA:(NSFont *)a fontB:(NSFont *)b
+{
+    if (a == nil || b == nil)
+        return;
 
     NSFontManager *fm = [NSFontManager sharedFontManager];
 
-    NSFont *a = [fm convertFont:[fm fontWithFamily:@"Helvetica" traits:0 weight:5 size:10]];
-    NSFont *b = [fm convertFont:[fm fontWithFamily:@"Times" traits:(NSBoldFontMask | NSItalicFontMask) weight:10 size:12]];
-    
+    NSFont *oa = [self _originalFontA];
+
     NSString *fa = [a familyName];
     NSString *fb = [b familyName];
     if ([fa isEqualToString:fb]) {
@@ -2835,8 +2983,13 @@ static WebHTMLView *lastHitView = nil;
 
     int sa = [a pointSize];
     int sb = [b pointSize];
+    int soa = [oa pointSize];
     if (sa == sb) {
         [style setFontSize:[NSString stringWithFormat:@"%dpx", sa]];
+    } else if (sa < soa) {
+        // FIXME: set up a style to tell WebCore to make the font in the selection 1 pixel smaller
+    } else if (sa > soa) {
+        // FIXME: set up a style to tell WebCore to make the font in the selection 1 pixel larger
     }
 
     int wa = [fm weightOfFont:a];
@@ -2858,70 +3011,196 @@ static WebHTMLView *lastHitView = nil;
             [style setFontStyle:@"normal"];
         }
     }
+}
+
+- (DOMCSSStyleDeclaration *)_styleFromFontManagerOperation
+{
+    DOMCSSStyleDeclaration *style = [self _emptyStyle];
+
+    NSFontManager *fm = [NSFontManager sharedFontManager];
+
+    NSFont *oa = [self _originalFontA];
+    NSFont *ob = [self _originalFontB];    
+    [self _addToStyle:style fontA:[fm convertFont:oa] fontB:[fm convertFont:ob]];
 
     return style;
 }
 
 - (void)changeFont:(id)sender
 {
-    DOMCSSStyleDeclaration *style = [self _fontManagerOperationAsStyle];
-    WebView *webView = [self _webView];
-    WebBridge *bridge = [self _bridge];
-    if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:[bridge selectedDOMRange]]) {
-        [bridge applyStyle:style];
+    [self _applyStyleToSelection:[self _styleFromFontManagerOperation]];
+}
+
+- (NSString *)_colorAsString:(NSColor *)color
+{
+    if (color == nil)
+        return @"transparent";
+    float r = [color redComponent];
+    float g = [color greenComponent];
+    float b = [color blueComponent];
+    if (r == 0 && g == 0 && b == 0)
+        return @"black";
+    if (r == 1 && g == 1 && b == 1)
+        return @"white";
+    return [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", r * 255, g * 255, b * 255];
+}
+
+- (NSString *)_shadowAsString:(NSShadow *)shadow
+{
+    if (shadow == nil)
+        return @"none";
+    NSSize offset = [shadow shadowOffset];
+    float blurRadius = [shadow shadowBlurRadius];
+    if (offset.width == 0 && offset.height == 0 && blurRadius == 0)
+        return @"none";
+    NSColor *color = [shadow shadowColor];
+    if (color == nil)
+        return @"none";
+    // FIXME: Handle non-integral values here?
+    if (blurRadius == 0)
+        return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height];
+    return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height, blurRadius];
+}
+
+- (DOMCSSStyleDeclaration *)_styleForAttributeChange:(id)sender
+{
+    DOMCSSStyleDeclaration *style = [self _emptyStyle];
+
+    NSShadow *shadow = [[NSShadow alloc] init];
+    [shadow setShadowOffset:NSMakeSize(1, 1)];
+
+    NSDictionary *oa = [NSDictionary dictionaryWithObjectsAndKeys:
+        [self _originalFontA], NSFontAttributeName,
+        nil];
+    NSDictionary *ob = [NSDictionary dictionaryWithObjectsAndKeys:
+        [NSColor blackColor], NSBackgroundColorAttributeName,
+        [self _originalFontB], NSFontAttributeName,
+        [NSColor whiteColor], NSForegroundColorAttributeName,
+        shadow, NSShadowAttributeName,
+        [NSNumber numberWithInt:NSUnderlineStyleSingle], NSStrikethroughStyleAttributeName,
+        [NSNumber numberWithInt:1], NSSuperscriptAttributeName,
+        [NSNumber numberWithInt:NSUnderlineStyleSingle], NSUnderlineStyleAttributeName,
+        nil];
+
+    [shadow release];
+
+#if 0
+
+NSObliquenessAttributeName        /* float; skew to be applied to glyphs, default 0: no skew */
+    // font-style, but that is just an on-off switch
+
+NSExpansionAttributeName          /* float; log of expansion factor to be applied to glyphs, default 0: no expansion */
+    // font-stretch?
+
+NSKernAttributeName               /* float, amount to modify default kerning, if 0, kerning off */
+    // letter-spacing? probably not good enough
+
+NSUnderlineColorAttributeName     /* NSColor, default nil: same as foreground color */
+NSStrikethroughColorAttributeName /* NSColor, default nil: same as foreground color */
+    // text-decoration-color?
+
+NSLigatureAttributeName           /* int, default 1: default ligatures, 0: no ligatures, 2: all ligatures */
+NSBaselineOffsetAttributeName     /* float, in points; offset from baseline, default 0 */
+NSStrokeWidthAttributeName        /* float, in percent of font point size, default 0: no stroke; positive for stroke alone, negative for stroke and fill (a typical value for outlined text would be 3.0) */
+NSStrokeColorAttributeName        /* NSColor, default nil: same as foreground color */
+    // need extensions?
+
+#endif
+    
+    NSDictionary *a = [sender convertAttributes:oa];
+    NSDictionary *b = [sender convertAttributes:ob];
+
+    NSColor *ca = [a objectForKey:NSBackgroundColorAttributeName];
+    NSColor *cb = [b objectForKey:NSBackgroundColorAttributeName];
+    if (ca == cb) {
+        [style setBackgroundColor:[self _colorAsString:ca]];
     }
+
+    [self _addToStyle:style fontA:[a objectForKey:NSFontAttributeName] fontB:[b objectForKey:NSFontAttributeName]];
+
+    ca = [a objectForKey:NSForegroundColorAttributeName];
+    cb = [b objectForKey:NSForegroundColorAttributeName];
+    if (ca == cb) {
+        [style setColor:[self _colorAsString:ca]];
+    }
+
+    NSShadow *sha = [a objectForKey:NSShadowAttributeName];
+    if (sha) {
+        [style setTextShadow:[self _shadowAsString:sha]];
+    } else if ([b objectForKey:NSShadowAttributeName] == nil) {
+        [style setTextShadow:@"none"];
+    }
+
+    int sa = [[a objectForKey:NSStrikethroughStyleAttributeName] intValue];
+    int sb = [[b objectForKey:NSStrikethroughStyleAttributeName] intValue];
+    if (sa == sb) {
+        if (sa == NSUnderlineStyleNone)
+            [style setTextDecoration:@"none"]; // we really mean "no line-through" rather than "none"
+        else
+            [style setTextDecoration:@"line-through"]; // we really mean "add line-through" rather than "line-through"
+    }
+
+    sa = [[a objectForKey:NSSuperscriptAttributeName] intValue];
+    sb = [[b objectForKey:NSSuperscriptAttributeName] intValue];
+    if (sa == sb) {
+        if (sa > 0)
+            [style setVerticalAlign:@"super"];
+        else if (sa < 0)
+            [style setVerticalAlign:@"sub"];
+        else
+            [style setVerticalAlign:@"baseline"];
+    }
+
+    int ua = [[a objectForKey:NSUnderlineStyleAttributeName] intValue];
+    int ub = [[b objectForKey:NSUnderlineStyleAttributeName] intValue];
+    if (ua == ub) {
+        if (ua == NSUnderlineStyleNone)
+            [style setTextDecoration:@"none"]; // we really mean "no underline" rather than "none"
+        else
+            [style setTextDecoration:@"underline"]; // we really mean "add underline" rather than "underline"
+    }
+
+    return style;
 }
 
 - (void)changeAttributes:(id)sender
 {
-    // Used for various fancy stuff from the font panel.
-    // Turn the attributes into CSS and then call applyStyle.
-    ERROR("unimplemented");
+    [self _applyStyleToSelection:[self _styleForAttributeChange:sender]];
 }
 
-- (DOMCSSStyleDeclaration *)_colorPanelColorAsStyleUsingSelector:(SEL)selector
+- (DOMCSSStyleDeclaration *)_styleFromColorPanelWithSelector:(SEL)selector
 {
-    WebBridge *bridge = [self _bridge];
-    DOMCSSStyleDeclaration *style = [[bridge DOMDocument] createCSSStyleDeclaration];
-    NSColor *color = [[NSColorPanel sharedColorPanel] color];
-    NSString *colorAsString = [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", [color redComponent]*255, [color greenComponent]*255, [color blueComponent]*255];
+    DOMCSSStyleDeclaration *style = [self _emptyStyle];
+
     ASSERT([style respondsToSelector:selector]);
-    [style performSelector:selector withObject:colorAsString];
+    [style performSelector:selector withObject:[self _colorAsString:[[NSColorPanel sharedColorPanel] color]]];
     
     return style;
 }
 
 - (void)_changeCSSColorUsingSelector:(SEL)selector inRange:(DOMRange *)range
 {
-    DOMCSSStyleDeclaration *style = [self _colorPanelColorAsStyleUsingSelector:selector];
+    DOMCSSStyleDeclaration *style = [self _styleFromColorPanelWithSelector:selector];
     WebView *webView = [self _webView];
     if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:range]) {
         [[self _bridge] applyStyle:style];
     }
 }
 
-- (DOMRange *)_entireDOMRange
-{
-    DOMDocument *document = [[self _bridge] DOMDocument];
-    DOMRange *range = [document createRange];
-    [range selectNode:[document documentElement]];
-    return range;
-}
-
 - (void)changeDocumentBackgroundColor:(id)sender
 {
     // Mimicking NSTextView, this method sets the background color for the
     // entire document. There is no NSTextView API for setting the background
     // color on the selected range only. Note that this method is currently
     // never called from the UI (see comment in changeColor:).
-    // FIXME: this actually has no effect when called, probably due to 3654850. _entireDOMRange seems
+    // FIXME: this actually has no effect when called, probably due to 3654850. _documentRange seems
     // to do the right thing because it works in startSpeaking:, and I know setBackgroundColor: does the
-    // right thing because I tested it with [[self _bridge] selectedDOMRange].
+    // right thing because I tested it with [self _selectedRange].
     // FIXME: This won't actually apply the style to the entire range here, because it ends up calling
     // [bridge applyStyle:], which operates on the current selection. To make this work right, we'll
     // need to save off the selection, temporarily set it to the entire range, make the change, then
     // restore the old selection.
-    [self _changeCSSColorUsingSelector:@selector(setBackgroundColor:) inRange:[self _entireDOMRange]];
+    [self _changeCSSColorUsingSelector:@selector(setBackgroundColor:) inRange:[self _documentRange]];
 }
 
 - (void)changeColor:(id)sender
@@ -2931,20 +3210,16 @@ static WebHTMLView *lastHitView = nil;
     // AppKit will have to be revised to allow this to work with anything that isn't an 
     // NSTextView. However, this might not be required for Tiger, since the background-color 
     // changing box in the font panel doesn't work in Mail (3674481), though it does in TextEdit.
-    [self _changeCSSColorUsingSelector:@selector(setColor:) inRange:[[self _bridge] selectedDOMRange]];
+    [self _applyStyleToSelection:[self _styleFromColorPanelWithSelector:@selector(setColor:)]];
 }
 
 - (void)_alignSelectionUsingCSSValue:(NSString *)CSSAlignmentValue
 {
     // FIXME 3675191: This doesn't work yet. Maybe it's blocked by 3654850, or maybe something other than
     // just applyStyle: needs to be called for block-level attributes like this.
-    WebBridge *bridge = [self _bridge];
-    DOMCSSStyleDeclaration *style = [[bridge DOMDocument] createCSSStyleDeclaration];
+    DOMCSSStyleDeclaration *style = [self _emptyStyle];
     [style setTextAlign:CSSAlignmentValue];
-    WebView *webView = [self _webView];
-    if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:[bridge selectedDOMRange]]) {
-        [[self _bridge] applyStyle:style];
-    }
+    [self _applyStyleToSelection:style];
 }
 
 - (void)alignCenter:(id)sender
@@ -2967,19 +3242,9 @@ static WebHTMLView *lastHitView = nil;
     [self _alignSelectionUsingCSSValue:@"right"];
 }
 
-- (void)indent:(id)sender
-{
-    // Figure out current indent level.
-    // Turn new indent level into CSS, then call applyStyle:.
-    ERROR("unimplemented");
-}
-
 - (void)insertTab:(id)sender
 {
-    WebBridge *bridge = [self _bridge];
-    if ([self _shouldReplaceSelectionWithText:@"\t" givenAction:WebViewInsertActionPasted]) {
-        [bridge insertText:@"\t"];
-    }
+    [self insertText:@"\t"];
 }
 
 - (void)insertBacktab:(id)sender
@@ -2999,15 +3264,10 @@ static WebHTMLView *lastHitView = nil;
 
 - (void)insertParagraphSeparator:(id)sender
 {
-    // FIXME: Should this do something different?
+    // FIXME: Should this do something different? Do we have the equivalent of a paragraph separator?
     [self insertNewline:sender];
 }
 
-- (void)changeCaseOfLetter:(id)sender
-{
-    ERROR("unimplemented");
-}
-
 - (void)_changeWordCaseWithSelector:(SEL)selector
 {
     WebBridge *bridge = [self _bridge];
@@ -3034,61 +3294,92 @@ static WebHTMLView *lastHitView = nil;
     [self _changeWordCaseWithSelector:@selector(capitalizedString)];
 }
 
+- (BOOL)_deleteWithDirection:(WebSelectionDirection)direction granularity:(WebSelectionGranularity)granularity killRing:(BOOL)killRing
+{
+    // Delete the selection, if there is one.
+    // If not, make a selection using the passed-in direction and granularity.
+    if (![self _isEditable])
+        return NO;
+    DOMRange *range;
+    BOOL prepend;
+    if ([self _hasSelection]) {
+        range = [self _selectedRange];
+        prepend = NO;
+    } else {
+        WebBridge *bridge = [self _bridge];
+        range = [bridge rangeByAlteringCurrentSelection:WebSelectByExtending direction:direction granularity:granularity];
+        if (range == nil || [range collapsed])
+            return NO;
+        switch (direction) {
+            case WebSelectForward:
+            case WebSelectRight:
+                prepend = NO;
+                break;
+            case WebSelectBackward:
+            case WebSelectLeft:
+                prepend = YES;
+                break;
+        }
+    }
+    [self _deleteRange:range preflight:YES killRing:killRing prepend:prepend];
+    return YES;
+}
+
 - (void)deleteForward:(id)sender
 {
-    ERROR("unimplemented");
+    [self _deleteWithDirection:WebSelectForward granularity:WebSelectByCharacter killRing:NO];
 }
 
 - (void)deleteBackward:(id)sender
 {
+    if (![self _isEditable])
+        return;
     WebBridge *bridge = [self _bridge];
-    if ([bridge isSelectionEditable]) {
-        WebView *webView = [self _webView];
-        if ([[webView _editingDelegateForwarder] webView:webView shouldDeleteDOMRange:[bridge selectedDOMRange]]) {
-            [bridge deleteKeyPressed];
-        }
+    WebView *webView = [self _webView];
+    if ([[webView _editingDelegateForwarder] webView:webView shouldDeleteDOMRange:[self _selectedRange]]) {
+        [bridge deleteKeyPressed];
     }
 }
 
 - (void)deleteBackwardByDecomposingPreviousCharacter:(id)sender
 {
-    ERROR("unimplemented");
+    ERROR("unimplemented, doing deleteBackward instead");
+    [self deleteBackward:sender];
 }
 
 - (void)deleteWordForward:(id)sender
 {
-    [self moveWordForwardAndModifySelection:sender];
-    [self delete:sender];
+    [self _deleteWithDirection:WebSelectForward granularity:WebSelectByWord killRing:YES];
 }
 
 - (void)deleteWordBackward:(id)sender
 {
-    [self moveWordBackwardAndModifySelection:sender];
-    [self delete:sender];
+    [self _deleteWithDirection:WebSelectBackward granularity:WebSelectByWord killRing:YES];
 }
 
 - (void)deleteToBeginningOfLine:(id)sender
 {
-    [self moveToBeginningOfLine:sender];
-    [self delete:sender];
+    [self _deleteWithDirection:WebSelectBackward granularity:WebSelectToLineBoundary killRing:YES];
 }
 
 - (void)deleteToEndOfLine:(id)sender
 {
-    [self moveToEndOfLine:sender];
-    [self delete:sender];
+    // FIXME: To match NSTextView, this command should delete the newline at the end of
+    // a paragraph if you are at the end of a paragraph (like deleteToEndOfParagraph does below).
+    [self _deleteWithDirection:WebSelectForward granularity:WebSelectToLineBoundary killRing:YES];
 }
 
 - (void)deleteToBeginningOfParagraph:(id)sender
 {
-    [self moveToBeginningOfParagraph:sender];
-    [self delete:sender];
+    [self _deleteWithDirection:WebSelectBackward granularity:WebSelectToParagraphBoundary killRing:YES];
 }
 
 - (void)deleteToEndOfParagraph:(id)sender
 {
-    [self moveToEndOfParagraph:sender];
-    [self delete:sender];
+    // Despite the name of the method, this should delete the newline if the caret is at the end of a paragraph.
+    // If deletion to the end of the paragraph fails, we delete one character forward, which will delete the newline.
+    if (![self _deleteWithDirection:WebSelectForward granularity:WebSelectToParagraphBoundary killRing:YES])
+        [self _deleteWithDirection:WebSelectForward granularity:WebSelectByCharacter killRing:YES];
 }
 
 - (void)complete:(id)sender
@@ -3112,51 +3403,6 @@ static WebHTMLView *lastHitView = nil;
         [checker updateSpellingPanelWithMisspelledWord:badWord];
     }
 }
-#if APPKIT_CODE_FOR_REFERENCE
-    NSTextStorage *text = _getTextStorage(self);
-    NSTextViewSharedData *sharedData = _getSharedData(self);
-    if (text && ([text length] > 0) && [self isSelectable]) {
-        NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
-        NSRange selCharRange = [self selectedRange];
-        NSRange misspellCharRange = {0, 0}, grammarCharRange = {0, 0};
-        unsigned i, count;
-        NSArray *grammarRanges = nil, *grammarDescriptions = nil;
-
-        if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
-
-        misspellCharRange = [checker checkSpellingOfString:[text string] startingAt:NSMaxRange(selCharRange) language:nil wrap:YES inSpellDocumentWithTag:[self spellCheckerDocumentTag] wordCount:NULL];
-#if GRAMMAR_CHECKING
-        grammarCharRange = [checker checkGrammarOfString:[text string] startingAt:NSMaxRange(selCharRange) language:nil wrap:YES inSpellDocumentWithTag:[self spellCheckerDocumentTag] ranges:&grammarRanges descriptions:&grammarDescriptions reconnectOnError:YES];
-#endif
-
-        if (misspellCharRange.length > 0 && (grammarCharRange.length == 0 || misspellCharRange.location <= grammarCharRange.location)) {
-            // select the text and drive the panel
-            [self setSelectedRange:misspellCharRange affinity:NSSelectionAffinityUpstream stillSelecting:NO];
-            if ([self isEditable]) {
-                [self _addSpellingAttributeForRange:misspellCharRange];
-                sharedData->_excludedSpellingCharRange = misspellCharRange;
-            }
-            [self scrollRangeToVisible:misspellCharRange];
-            [checker updateSpellingPanelWithMisspelledWord:[[text string] substringWithRange:misspellCharRange]];
-        } else if (grammarCharRange.length > 0) {
-            [self setSelectedRange:grammarCharRange affinity:NSSelectionAffinityUpstream stillSelecting:NO];
-            count = [grammarRanges count];
-            if ([self isEditable]) {
-                for (i = 0; i < count; i++) {
-                    NSRange range = [grammarRanges rangeAtIndex:i];
-                    range.location += grammarCharRange.location;
-                    [self _addSpellingAttributeForRange:range];
-                    [_getLayoutManager(self) addTemporaryAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[grammarDescriptions objectAtIndex:i], NSToolTipAttributeName, nil] forCharacterRange:range];
-                }
-            }
-            [self scrollRangeToVisible:grammarCharRange];
-        } else {
-            // Cause the beep to indicate there are no more misspellings.
-            [checker updateSpellingPanelWithMisspelledWord:@""];
-        }
-    }
-#endif
-
 
 - (void)showGuessPanel:(id)sender
 {
@@ -3173,31 +3419,6 @@ static WebHTMLView *lastHitView = nil;
     }
     [[checker spellingPanel] orderFront:sender];
 }
-#if APPKIT_CODE_FOR_REFERENCE
-    NSTextStorage *text = _getTextStorage(self);
-    NSTextViewSharedData *sharedData = _getSharedData(self);
-    NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
-    if (text && ([text length] > 0) && [self isSelectable]) {
-        NSRange selCharRange = [self selectedRange];
-        NSRange misspellCharRange = {0, 0};
-        if (checker && ![checker windowIsSpellingPanel:[self window]]) {
-            misspellCharRange = [checker checkSpellingOfString:[text string] startingAt:((selCharRange.location > 0) ? (selCharRange.location - 1) : 0) language:nil wrap:YES inSpellDocumentWithTag:[self spellCheckerDocumentTag] wordCount:NULL];
-            if (misspellCharRange.length) {
-                // select the text and drive the panel
-                [self setSelectedRange:misspellCharRange affinity:NSSelectionAffinityUpstream stillSelecting:NO];
-                if ([self isEditable]) {
-                    [self _addSpellingAttributeForRange:misspellCharRange];
-                    sharedData->_excludedSpellingCharRange = misspellCharRange;
-                }
-                [self scrollRangeToVisible:misspellCharRange];
-                [checker updateSpellingPanelWithMisspelledWord:[[text string] substringWithRange:misspellCharRange]];
-            }
-        }
-    }
-    if (checker) {
-        [[checker spellingPanel] orderFront:sender];
-    }
-#endif
 
 - (void)_changeSpellingToWord:(NSString *)newWord
 {
@@ -3220,29 +3441,14 @@ static WebHTMLView *lastHitView = nil;
         [bridge replaceSelectionWithText:newWord selectReplacement:YES];
     }
 }
-#if APPKIT_CODE_FOR_REFERENCE
-    NSRange charRange = [self rangeForUserTextChange];
-    if ([self isEditable] && charRange.location != NSNotFound) {
-        NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
-        if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
-        
-        // Don't correct to empty string.
-        if ([newWord isEqualToString:@""]) return;
-        
-        if ([self shouldChangeTextInRange:charRange replacementString:newWord]) {
-            [self replaceCharactersInRange:charRange withString:newWord];
-            charRange.length = [newWord length];
-            [self setSelectedRange:charRange affinity:NSSelectionAffinityUpstream stillSelecting:NO];
-            [self didChangeText];
-        }
-    }
-#endif
 
-- (void)changeSpelling:(id)sender {
+- (void)changeSpelling:(id)sender
+{
     [self _changeSpellingToWord:[[sender selectedCell] stringValue]];
 }
 
-- (void)ignoreSpelling:(id)sender {
+- (void)ignoreSpelling:(id)sender
+{
     WebBridge *bridge = [self _bridge];
     if (![bridge isSelectionEditable]) {
         return;
@@ -3258,122 +3464,201 @@ static WebHTMLView *lastHitView = nil;
     unsigned int length = [stringToIgnore length];
     if (stringToIgnore && length > 0) {
         [checker ignoreWord:stringToIgnore inSpellDocumentWithTag:[[self _webView] spellCheckerDocumentTag]];
-        //??? need to clear any special markup for misspelled words
+        // FIXME: Need to clear misspelling marker if the currently selected word is the one we are to ignore?
     }
 }
-#if APPKIT_CODE_FOR_REFERENCE
-    NSRange charRange = [self rangeForUserTextChange];
-    if ([self isEditable]) {
-        NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
-        NSString *stringToIgnore;
-        unsigned int length;
-        
-        if (!checker) return;
-        
-        stringToIgnore = [sender stringValue];
-        length = [stringToIgnore length];
-        if (stringToIgnore && length > 0) {
-            [checker ignoreWord:stringToIgnore inSpellDocumentWithTag:[self spellCheckerDocumentTag]];
-            if (length == charRange.length && [stringToIgnore isEqualToString:[[_getTextStorage(self) string] substringWithRange:charRange]]) {
-                [self _removeSpellingAttributeForRange:charRange];
-            }
-        }
-    }
-#endif
 
+- (void)performFindPanelAction:(id)sender
+{
+    // Implementing this will probably require copying all of NSFindPanel.h and .m.
+    // We need *almost* the same thing as AppKit, but not quite.
+    ERROR("unimplemented");
+}
 
-#if APPKIT_CODE_FOR_REFERENCE
+- (void)startSpeaking:(id)sender
+{
+    WebBridge *bridge = [self _bridge];
+    DOMRange *range = [self _selectedRange];
+    if (!range || [range collapsed]) {
+        range = [self _documentRange];
+    }
+    [NSApp speakString:[bridge stringForRange:range]];
+}
 
-- (void)_changeSpellingFromMenu:(id)sender {
-    [self _changeSpellingToWord:[sender title]];
+- (void)stopSpeaking:(id)sender
+{
+    [NSApp stopSpeaking:sender];
 }
 
-- (void)_ignoreSpellingFromMenu:(id)sender {
-    NSRange charRange = [self rangeForUserTextChange];
-    if ([self isEditable] && charRange.location != NSNotFound && charRange.length > 0) {
-        NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
-        if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
-        [self _removeSpellingAttributeForRange:charRange];
-        [checker ignoreWord:[[_getTextStorage(self) string] substringWithRange:charRange] inSpellDocumentWithTag:[self spellCheckerDocumentTag]];
-    }
+- (void)insertNewlineIgnoringFieldEditor:(id)sender
+{
+    [self insertNewline:sender];
 }
 
-- (void)_learnSpellingFromMenu:(id)sender {
-    NSRange charRange = [self rangeForUserTextChange];
-    if ([self isEditable] && charRange.location != NSNotFound && charRange.length > 0) {
-        NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
-        if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
-        [self _removeSpellingAttributeForRange:charRange];
-        [checker learnWord:[[_getTextStorage(self) string] substringWithRange:charRange]];
-    }
+- (void)insertTabIgnoringFieldEditor:(id)sender
+{
+    [self insertTab:sender];
 }
 
-- (void)_forgetSpellingFromMenu:(id)sender {
-    NSRange charRange = [self rangeForUserTextChange];
-    if ([self isEditable] && charRange.location != NSNotFound && charRange.length > 0) {
-        NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
-        if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
-        [checker forgetWord:[[_getTextStorage(self) string] substringWithRange:charRange]];
-    }
+- (void)subscript:(id)sender
+{
+    DOMCSSStyleDeclaration *style = [self _emptyStyle];
+    [style setVerticalAlign:@"sub"];
+    [self _applyStyleToSelection:style];
 }
 
-- (void)_openLinkFromMenu:(id)sender {
-    NSTextStorage *text = _getTextStorage(self);
-    NSRange charRange = [self selectedRange];
-    if (charRange.location != NSNotFound && charRange.length > 0) {
-        id link = [text attribute:NSLinkAttributeName atIndex:charRange.location effectiveRange:NULL];
-        if (link) {
-            [self clickedOnLink:link atIndex:charRange.location];
-        } else {
-            NSString *string = [[text string] substringWithRange:charRange];
-            link = [NSURL URLWithString:string];
-            if (link) [[NSWorkspace sharedWorkspace] openURL:link];
-        }
-    }
+- (void)superscript:(id)sender
+{
+    DOMCSSStyleDeclaration *style = [self _emptyStyle];
+    [style setVerticalAlign:@"super"];
+    [self _applyStyleToSelection:style];
 }
 
-#endif
+- (void)unscript:(id)sender
+{
+    DOMCSSStyleDeclaration *style = [self _emptyStyle];
+    [style setVerticalAlign:@"baseline"];
+    [self _applyStyleToSelection:style];
+}
 
-- (void)performFindPanelAction:(id)sender
+- (void)underline:(id)sender
 {
-    ERROR("unimplemented");
+    // Despite the name, this method is actually supposed to toggle underline.
+    // FIXME: This currently clears overline, line-through, and blink as an unwanted side effect.
+    DOMCSSStyleDeclaration *style = [self _emptyStyle];
+    [style setTextDecoration:@"underline"];
+    if ([[self _bridge] selectionStartHasStyle:style])
+        [style setTextDecoration:@"none"];
+    [self _applyStyleToSelection:style];
 }
 
-- (void)startSpeaking:(id)sender
+- (void)yank:(id)sender
 {
-    WebBridge *bridge = [self _bridge];
-    DOMRange *range = [bridge selectedDOMRange];
-    if (!range || [range collapsed]) {
-        range = [self _entireDOMRange];
-    }
-    [NSApp speakString:[bridge stringForRange:range]];
+    [self insertText:_NSYankFromKillRing()];
+    _NSSetKillRingToYankedState();
 }
 
-- (void)stopSpeaking:(id)sender
+- (void)yankAndSelect:(id)sender
 {
-    [NSApp stopSpeaking:sender];
+    [self _insertText:_NSYankPreviousFromKillRing() selectInsertedText:YES];
+    _NSSetKillRingToYankedState();
 }
 
 #if 0
 
 - (void)transpose:(id)sender;
-- (void)transposeWords:(id)sender;
-- (void)insertNewlineIgnoringFieldEditor:(id)sender;
-- (void)insertTabIgnoringFieldEditor:(id)sender;
-- (void)yank:(id)sender;
-- (void)yankAndSelect:(id)sender;
-- (void)setMark:(id)sender;
-- (void)deleteToMark:(id)sender;
-- (void)selectToMark:(id)sender;
-- (void)swapWithMark:(id)sender;
+
+// Implementing these requires motion by paragraph.
+// Currently move right by paragraph is actually "move to end of paragraph".
+// We'd have to fix that up if we want to implement these.
 - (void)moveParagraphBackwardAndModifySelection:(id)sender;
 - (void)moveParagraphForwardAndModifySelection:(id)sender;
+
+// Implementing these may be easy once we implement pageUp and pageDown.
 - (void)pageUpAndModifySelection:(id)sender;
 - (void)pageDownAndModifySelection:(id)sender;
+
+// Implementing these four requires implementing a mark.
+// We can't just keep a DOM range on the WebKit side because the mark needs
+// to stay in the document as the document is edited.
+- (void)setMark:(id)sender;
+- (void)deleteToMark:(id)sender;
+- (void)selectToMark:(id)sender;
+- (void)swapWithMark:(id)sender;
+
+// This is part of table support, which may be in NSTextView for Tiger.
+// It's probably simple to do the equivalent thing for WebKit.
 - (void)insertTable:(id)sender;
 
+// These methods are not implemented in NSTextView yet, so perhaps there's no rush.
+- (void)changeCaseOfLetter:(id)sender;
+- (void)indent:(id)sender;
+- (void)transposeWords:(id)sender;
+
+// CSS does not have a way to specify an outline font, which may make this difficult to implement.
+// Maybe a special case of text-shadow?
+- (void)outline:(id)sender;
+
+// These could be important.
+- (void)toggleBaseWritingDirection:(id)sender;
+- (void)toggleTraditionalCharacterShape:(id)sender;
+- (void)changeBaseWritingDirection:(id)sender;
+
+// I'm not sure what the equivalents of these in the web world are; we use <br> as a paragraph break.
+- (void)insertLineBreak:(id)sender;
+- (void)insertLineSeparator:(id)sender;
+- (void)insertPageBreak:(id)sender;
+
 #endif
 
+// Super-hack alert.
+// Workaround for bug 3789278.
+
+// Returns a selector only if called while:
+//   1) first responder is self
+//   2) handling a key down event
+//   3) not yet inside keyDown: method
+//   4) key is an arrow key
+// The selector is the one that gets sent by -[NSWindow _processKeyboardUIKey] for this key.
+- (SEL)_arrowKeyDownEventSelectorIfPreprocessing
+{
+    NSWindow *w = [self window];
+    if ([w firstResponder] != self)
+        return NULL;
+    NSEvent *e = [w currentEvent];
+    if ([e type] != NSKeyDown)
+        return NULL;
+    if (e == _private->keyDownEvent)
+        return NULL;
+    NSString *s = [e charactersIgnoringModifiers];
+    if ([s length] == 0)
+        return NULL;
+    switch ([s characterAtIndex:0]) {
+        case NSDownArrowFunctionKey:
+            return @selector(moveDown:);
+        case NSLeftArrowFunctionKey:
+            return @selector(moveLeft:);
+        case NSRightArrowFunctionKey:
+            return @selector(moveRight:);
+        case NSUpArrowFunctionKey:
+            return @selector(moveUp:);
+        default:
+            return NULL;
+    }
+}
+
+// Returns NO instead of YES if called on the selector that the
+// _arrowKeyDownEventSelectorIfPreprocessing method returns.
+// This should only happen inside -[NSWindow _processKeyboardUIKey],
+// and together with the change below should cause that method
+// to return NO rather than handling the key.
+// Also set a 1-shot flag for the nextResponder check below.
+- (BOOL)respondsToSelector:(SEL)selector
+{
+    if (![super respondsToSelector:selector])
+        return NO;
+    SEL arrowKeySelector = [self _arrowKeyDownEventSelectorIfPreprocessing];
+    if (selector != arrowKeySelector)
+        return YES;
+    _private->nextResponderDisabledOnce = YES;
+    return NO;
+}
+
+// Returns nil instead of the next responder if called when the
+// one-shot flag is set, and _arrowKeyDownEventSelectorIfPreprocessing
+// returns something other than NULL. This should only happen inside
+// -[NSWindow _processKeyboardUIKey] and together with the change above
+// should cause that method to return NO rather than handling the key.
+- (NSResponder *)nextResponder
+{
+    BOOL disabled = _private->nextResponderDisabledOnce;
+    _private->nextResponderDisabledOnce = NO;
+    if (disabled && [self _arrowKeyDownEventSelectorIfPreprocessing] != NULL) {
+        return nil;
+    }
+    return [super nextResponder];
+}
+
 @end
 
 @implementation WebHTMLView (WebTextSizing)
@@ -3400,28 +3685,13 @@ static WebHTMLView *lastHitView = nil;
 
 @end
 
-@implementation WebElementOrTextFilter
-
-+ (WebElementOrTextFilter *)filter 
-{
-    if (!elementOrTextFilterInstance)
-        elementOrTextFilterInstance = [[WebElementOrTextFilter alloc] init];
-    return elementOrTextFilterInstance;
-}
-
-- (short)acceptNode:(DOMNode *)n
-{
-    return ([n isKindOfClass:[DOMElement class]] || [n isKindOfClass:[DOMText class]]) ? DOM_FILTER_ACCEPT : DOM_FILTER_SKIP;
-}
-
-@end
-
 @implementation WebHTMLView (WebInternal)
 
 - (void)_selectionChanged
 {
     [self _updateSelectionForInputManager];
     [self _updateFontPanel];
+    _private->startNewKillRingSequence = YES;
 }
 
 - (void)_updateFontPanel
@@ -3441,42 +3711,9 @@ static WebHTMLView *lastHitView = nil;
         return;
     }
     
-    BOOL onlyOneFontInSelection = YES;
-    NSFont *font = nil;
-    
-    if (![self _hasSelection]) {
-        font = [bridge fontForCurrentPosition];
-    } 
-    else {
-        DOMRange *selection = [bridge selectedDOMRange];
-        DOMNode *startContainer = [selection startContainer];
-        DOMNode *endContainer = [selection endContainer];
-        
-        ASSERT(startContainer);
-        ASSERT(endContainer);
-        ASSERT([[WebElementOrTextFilter filter] acceptNode:startContainer] == DOM_FILTER_ACCEPT);
-        ASSERT([[WebElementOrTextFilter filter] acceptNode:endContainer] == DOM_FILTER_ACCEPT);
-        
-        font = [bridge renderedFontForNode:startContainer];
-        
-        if (startContainer != endContainer) {
-            DOMDocument *document = [bridge DOMDocument];
-            DOMTreeWalker *treeWalker = [document createTreeWalker:document :DOM_SHOW_ALL :[WebElementOrTextFilter filter] :NO];
-            DOMNode *node = startContainer;
-            [treeWalker setCurrentNode:node];
-            while (node) {
-                NSFont *otherFont = [bridge renderedFontForNode:node];
-                if (![font isEqual:otherFont]) {
-                    onlyOneFontInSelection = NO;
-                    break;
-                }
-                if (node == endContainer)
-                    break;
-                node = [treeWalker nextNode];
-            }
-        }
-    }
-    
+    BOOL multiple = NO;
+    NSFont *font = [bridge fontForSelection:&multiple];
+
     // FIXME: for now, return a bogus font that distinguishes the empty selection from the non-empty
     // selection. We should be able to remove this once the rest of this code works properly.
     if (font == nil) {
@@ -3487,9 +3724,10 @@ static WebHTMLView *lastHitView = nil;
         }
     }
     ASSERT(font != nil);
-        
+
     NSFontManager *fm = [NSFontManager sharedFontManager];
-    [fm setSelectedFont:font isMultiple:!onlyOneFontInSelection];
+    [fm setSelectedFont:font isMultiple:multiple];
+
     // FIXME: we don't keep track of selected attributes, or set them on the font panel. This
     // appears to have no effect on the UI. E.g., underlined text in Mail or TextEdit is
     // not reflected in the font panel. Maybe someday this will change.
@@ -3505,7 +3743,6 @@ static WebHTMLView *lastHitView = nil;
 
 @end
 
-
 @implementation WebHTMLView (WebNSTextInputSupport)
 
 - (NSArray *)validAttributesForMarkedText
@@ -3617,7 +3854,7 @@ static WebHTMLView *lastHitView = nil;
     }
 
     [bridge replaceSelectionWithText:text selectReplacement:YES];
-    [bridge setMarkedDOMRange:[bridge selectedDOMRange]];
+    [bridge setMarkedDOMRange:[self _selectedRange]];
     [self _selectRangeInMarkedText:newSelRange];
 
     _private->ignoreMarkedTextSelectionChange = NO;
@@ -3642,23 +3879,19 @@ static WebHTMLView *lastHitView = nil;
 
     _private->ignoreMarkedTextSelectionChange = NO;
 }
-       
-- (void)insertText:(id)string
+
+- (void)_insertText:(NSString *)text selectInsertedText:(BOOL)selectText
 {
+    if (text == nil || [text length] == 0) {
+        return;
+    }
+
     WebBridge *bridge = [self _bridge];
 
     if (![bridge isSelectionEditable] && ![self hasMarkedText]) {
        return;
     }
 
-    NSString *text;
-    if ([string isKindOfClass:[NSAttributedString class]]) {
-       ERROR("TEXTINPUT: requested insert of attributed string");
-       text = [string string];
-    } else {
-       text = string;
-    }
-
     if (![self _shouldReplaceSelectionWithText:text givenAction:WebViewInsertActionTyped]) {
        [self _discardMarkedText];
        return;
@@ -3666,22 +3899,33 @@ static WebHTMLView *lastHitView = nil;
 
     _private->ignoreMarkedTextSelectionChange = YES;
 
-    // if we had marked text, we need to make sure to replace
-    // that, instead of the selection/caret
+    // If we had marked text, we replace that, instead of the selection/caret.
     [self _selectMarkedText];
 
-    [bridge insertText:text];
+    [bridge insertText:text selectInsertedText:selectText];
 
     _private->ignoreMarkedTextSelectionChange = NO;
 
-    // inserting unmarks any marked text
+    // Inserting unmarks any marked text.
     [self unmarkText];
 }
 
+- (void)insertText:(id)string
+{
+    NSString *text;
+    if ([string isKindOfClass:[NSAttributedString class]]) {
+       ERROR("TEXTINPUT: requested insert of attributed string");
+       text = [string string];
+    } else {
+       text = string;
+    }
+    [self _insertText:text selectInsertedText:NO];
+}
+
 - (BOOL)_selectionIsInsideMarkedText
 {
     WebBridge *bridge = [self _bridge];
-    DOMRange *selection = [bridge selectedDOMRange];
+    DOMRange *selection = [self _selectedRange];
     DOMRange *markedRange = [bridge markedDOMRange];
 
     ASSERT([markedRange startContainer] == [markedRange endContainer]);
@@ -3707,7 +3951,7 @@ static WebHTMLView *lastHitView = nil;
        return;
 
     if ([self _selectionIsInsideMarkedText]) {
-       DOMRange *selection = [[self _bridge] selectedDOMRange];
+       DOMRange *selection = [self _selectedRange];
        DOMRange *markedDOMRange = [[self _bridge] markedDOMRange];
 
        unsigned markedSelectionStart = [selection startOffset] - [markedDOMRange startOffset];
@@ -3750,7 +3994,7 @@ static WebHTMLView *lastHitView = nil;
 
 - (void)_insertMatch:(NSString *)match
 {
-    //FIXME: 3769654 - We should preserve case of string being inserted, even in prefix (but then also be
+    // FIXME: 3769654 - We should preserve case of string being inserted, even in prefix (but then also be
     // able to revert that).  Mimic NSText.
     WebBridge *bridge = [_view _bridge];
     NSString *newText = [match substringFromIndex:prefixLength];
@@ -3921,7 +4165,8 @@ static WebHTMLView *lastHitView = nil;
 
 // WebHTMLView gives us a crack at key events it sees.  Return whether we consumed the event.
 // The features for the various keys mimic NSTextView.
-- (BOOL)filterKeyDown:(NSEvent *)event {
+- (BOOL)filterKeyDown:(NSEvent *)event
+{
     if (_popupWindow) {
         NSString *string = [event charactersIgnoringModifiers];
         unichar c = [string characterAtIndex:0];
@@ -3963,7 +4208,8 @@ static WebHTMLView *lastHitView = nil;
     [self _insertMatch:[_completions objectAtIndex:selectedRow]];
 }
 
-- (void)tableAction:(id)sender {
+- (void)tableAction:(id)sender
+{
     [self _reflectSelection];
     [self endRevertingChange:NO moveLeft:NO];
 }
index fcee48c3e0a5df899b6e2d4a86b20ab3d43bf717..6fca00eb9f13960b550c20c985904d3f135402d6 100644 (file)
@@ -22,7 +22,8 @@
     id savedSubviews;
     BOOL subviewsSetAside;
 
-    NSEvent *mouseDownEvent;
+    NSEvent *mouseDownEvent; // Kept after handling the event.
+    NSEvent *keyDownEvent; // Kept only during handling of the event.
 
     NSURL *draggingImageURL;
     unsigned int dragSourceActionMask;
@@ -45,8 +46,9 @@
     NSArray* pageRects;
 
     BOOL resigningFirstResponder;
-
     BOOL ignoreMarkedTextSelectionChange;
+    BOOL startNewKillRingSequence;
+    BOOL nextResponderDisabledOnce;
     
     WebTextCompleteController *compController;
 }
index 48dd1fdfb23f5d102adf0470e40e8e8a7f0b8861..aa2ae08a7ec4746a4a45a0e3e5d482b72070b445 100644 (file)
@@ -2144,10 +2144,8 @@ static WebFrame *incrementFrame(WebFrame *curr, BOOL forward, BOOL wrapFlag)
     return YES;
 }
 
-
 @end
 
-
 @implementation WebView (WebPendingPublic)
 
 - (void)setMainFrameURL:(NSString *)URLString
@@ -2621,420 +2619,100 @@ static WebFrame *incrementFrame(WebFrame *curr, BOOL forward, BOOL wrapFlag)
     reentered = NO;
 }
 
-- (void)centerSelectionInVisibleArea:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveBackward:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveBackwardAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveDown:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveDownAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveForward:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveForwardAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveLeft:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveLeftAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveRight:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveRightAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToBeginningOfDocument:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToBeginningOfLine:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToBeginningOfParagraph:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToEndOfDocument:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToEndOfLine:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToEndOfParagraph:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveUp:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveUpAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveWordBackward:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveWordBackwardAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveWordForward:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveWordForwardAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveWordLeft:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveWordLeftAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveWordRight:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveWordRightAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToBeginningOfParagraphAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToEndOfParagraphAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToBeginningOfLineAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToEndOfLineAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToBeginningOfDocumentAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)moveToEndOfDocumentAndModifySelection:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)pageDown:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)pageUp:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)scrollLineDown:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)scrollLineUp:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)scrollPageDown:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)scrollPageUp:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)selectAll:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)selectParagraph:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)selectLine:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)selectWord:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)copy:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)cut:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)paste:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)copyFont:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)pasteFont:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)delete:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)pasteAsPlainText:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)pasteAsRichText:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)changeFont:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)changeAttributes:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)changeDocumentBackgroundColor:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)changeColor:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)alignCenter:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)alignJustified:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)alignLeft:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)alignRight:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)indent:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)insertTab:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)insertBacktab:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)insertNewline:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)insertParagraphSeparator:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)changeCaseOfLetter:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)uppercaseWord:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)lowercaseWord:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)capitalizeWord:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)deleteForward:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)deleteBackward:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)deleteBackwardByDecomposingPreviousCharacter:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)deleteWordForward:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)deleteWordBackward:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)deleteToBeginningOfLine:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)deleteToEndOfLine:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)deleteToBeginningOfParagraph:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)deleteToEndOfParagraph:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)complete:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)checkSpelling:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)showGuessPanel:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)performFindPanelAction:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)startSpeaking:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
-
-- (void)stopSpeaking:(id)sender
-{
-    [self _performResponderOperation:_cmd with:sender];
-}
+#define FORWARD(name) \
+    - (void)name:(id)sender { [self _performResponderOperation:_cmd with:sender]; }
+
+FORWARD(alignCenter)
+FORWARD(alignJustified)
+FORWARD(alignLeft)
+FORWARD(alignRight)
+FORWARD(capitalizeWord)
+FORWARD(centerSelectionInVisibleArea)
+FORWARD(changeAttributes)
+FORWARD(changeColor)
+FORWARD(changeDocumentBackgroundColor)
+FORWARD(changeFont)
+FORWARD(checkSpelling)
+FORWARD(complete)
+FORWARD(copy)
+FORWARD(copyFont)
+FORWARD(cut)
+FORWARD(delete)
+FORWARD(deleteBackward)
+FORWARD(deleteBackwardByDecomposingPreviousCharacter)
+FORWARD(deleteForward)
+FORWARD(deleteToBeginningOfLine)
+FORWARD(deleteToBeginningOfParagraph)
+FORWARD(deleteToEndOfLine)
+FORWARD(deleteToEndOfParagraph)
+FORWARD(deleteWordBackward)
+FORWARD(deleteWordForward)
+FORWARD(ignoreSpelling)
+FORWARD(indent)
+FORWARD(insertBacktab)
+FORWARD(insertNewline)
+FORWARD(insertNewlineIgnoringFieldEditor)
+FORWARD(insertParagraphSeparator)
+FORWARD(insertTab)
+FORWARD(insertTabIgnoringFieldEditor)
+FORWARD(lowercaseWord)
+FORWARD(moveBackward)
+FORWARD(moveBackwardAndModifySelection)
+FORWARD(moveDown)
+FORWARD(moveDownAndModifySelection)
+FORWARD(moveForward)
+FORWARD(moveForwardAndModifySelection)
+FORWARD(moveLeft)
+FORWARD(moveLeftAndModifySelection)
+FORWARD(moveRight)
+FORWARD(moveRightAndModifySelection)
+FORWARD(moveToBeginningOfDocument)
+FORWARD(moveToBeginningOfDocumentAndModifySelection)
+FORWARD(moveToBeginningOfLine)
+FORWARD(moveToBeginningOfLineAndModifySelection)
+FORWARD(moveToBeginningOfParagraph)
+FORWARD(moveToBeginningOfParagraphAndModifySelection)
+FORWARD(moveToEndOfDocument)
+FORWARD(moveToEndOfDocumentAndModifySelection)
+FORWARD(moveToEndOfLine)
+FORWARD(moveToEndOfLineAndModifySelection)
+FORWARD(moveToEndOfParagraph)
+FORWARD(moveToEndOfParagraphAndModifySelection)
+FORWARD(moveUp)
+FORWARD(moveUpAndModifySelection)
+FORWARD(moveWordBackward)
+FORWARD(moveWordBackwardAndModifySelection)
+FORWARD(moveWordForward)
+FORWARD(moveWordForwardAndModifySelection)
+FORWARD(moveWordLeft)
+FORWARD(moveWordLeftAndModifySelection)
+FORWARD(moveWordRight)
+FORWARD(moveWordRightAndModifySelection)
+FORWARD(pageDown)
+FORWARD(pageUp)
+FORWARD(paste)
+FORWARD(pasteAsPlainText)
+FORWARD(pasteAsRichText)
+FORWARD(pasteFont)
+FORWARD(performFindPanelAction)
+FORWARD(scrollLineDown)
+FORWARD(scrollLineUp)
+FORWARD(scrollPageDown)
+FORWARD(scrollPageUp)
+FORWARD(selectAll)
+FORWARD(selectLine)
+FORWARD(selectParagraph)
+FORWARD(selectWord)
+FORWARD(showGuessPanel)
+FORWARD(startSpeaking)
+FORWARD(stopSpeaking)
+FORWARD(subscript)
+FORWARD(superscript)
+FORWARD(underline)
+FORWARD(unscript)
+FORWARD(uppercaseWord)
+FORWARD(yank)
+FORWARD(yankAndSelect)
 
 - (void)insertText:(NSString *)text
 {
@@ -3052,7 +2730,7 @@ static WebFrame *incrementFrame(WebFrame *curr, BOOL forward, BOOL wrapFlag)
 
 - (void)_preflightSpellChecker
 {
-    // As AppKit does, we wish to delay tickling the shared spellc hecker into existence on application launch.
+    // As AppKit does, we wish to delay tickling the shared spellchecker into existence on application launch.
     if ([NSSpellChecker sharedSpellCheckerExists]) {
         [self _preflightSpellCheckerNow:self];
     } else {