WebCore:
authorkocienda <kocienda@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 6 Apr 2004 15:14:30 +0000 (15:14 +0000)
committerkocienda <kocienda@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 6 Apr 2004 15:14:30 +0000 (15:14 +0000)
        Reviewed by Dave

        Added execCommand feature.
        Added Javascript selection object.
        This lays the groundwork for layout tests for editing.

        * khtml/dom/dom_doc.cpp:
        (DOM::Document::execCommand): Added. Calls through to impl's execCommand.
        * khtml/dom/dom_doc.h: Added execCommand declaration.
        * khtml/ecma/kjs_dom.cpp:
        (DOMDocumentProtoFunc::tryCall): Switch on new ExecCommand constant and call through to the document.
        * khtml/ecma/kjs_dom.h: Added ExecCommand constant.
        (KJS::DOMDocument::):
        * khtml/ecma/kjs_dom.lut.h: Generated file.
        * khtml/ecma/kjs_window.cpp:
        (Window::Window): Initialize selection object.
        (Window::selection): Return window's selection object.
        (Window::mark): Mark selection object.
        (WindowFunc::tryCall): Return selection object on GetSelection.
        (LocationFunc::tryCall): Added.
        (Selection::Selection): Added.
        (Selection::~Selection): Added.
        (Selection::get): Added.
        (Selection::put): Added.
        (Selection::toPrimitive): Added.
        (Selection::toString): Added.
        (SelectionFunc::tryCall): Added.
        * khtml/ecma/kjs_window.h:
        (KJS::Selection::): Added.
        (KJS::Selection::part): Added.
        (KJS::Selection::classInfo): Added.
        * khtml/ecma/kjs_window.lut.h: Generated file.
        * khtml/editing/htmlediting_impl.cpp:
        (debugPosition): Debugging aid.
        (DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace): Move the ending position
        if you are about to delete it. Fixes a crasher I discovered while writing tests.
        (DeleteSelectionCommandImpl::doApply): Move to containing editable block position 0
        instead of 1 in a block in delete case 1.
        (InputTextCommandImpl::prepareForTextInsertion):
        (TypingCommandImpl::issueCommandForDeleteKey):
        * khtml/khtml_part.h:
        * khtml/khtml_selection.cpp:
        (KHTMLSelection::validate): Now adjusts the selection down to leaf nodes if needed.
        (KHTMLSelection::debugPosition): Debugging aid.
        * khtml/xml/dom_docimpl.cpp:
        (DocumentImpl::execCommand): Added. Supports five different commands.
        * khtml/xml/dom_docimpl.h:
        * khtml/xml/dom_nodeimpl.cpp:
        (NodeImpl::previousEditable): Use false instead of 0 for equality check.
        (NodeImpl::nextEditable): Ditto.
        * khtml/xml/dom_position.cpp:
        (DOMPosition::equivalentLeafPosition): New function
        (DOMPosition::previousRenderedEditablePosition): New function
        (DOMPosition::nextRenderedEditablePosition): New function
        (DOMPosition::equivalentUpstreamPosition): Refined behavior to handle more cases correctly.
        (DOMPosition::equivalentDownstreamPosition): Ditto.
        (DOMPosition::atStartOfContainingEditableBlock):New function
        (DOMPosition::atStartOfRootEditableBlock):New function
        * khtml/xml/dom_position.h:
        * kwq/KWQKHTMLPart.h:
        * kwq/KWQKHTMLPart.mm:
        (KWQKHTMLPart::issueUndoCommand): New function for calling undo programatically.
        (KWQKHTMLPart::issueRedoCommand): Ditto, but for redo.
        * kwq/KWQRenderTreeDebug.cpp:
        (nodePositionRelativeToRoot): New function to generate log information for the selection.
        (writeSelection): Writes the selection if there is one.
        (externalRepresentation): Calls writeSelection
        * kwq/WebCoreBridge.h: New declarations for issueUndoCommand and issueRedoCommand.

WebKit:

        Reviewed by Dave

        * WebCoreSupport.subproj/WebBridge.m:
        (-[WebBridge issueUndoCommand]): New method. Forwards call to the undo manager. Added
        to support undo called via Javascript execCommand.
        (-[WebBridge issueRedoCommand]): Ditto.

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

28 files changed:
WebCore/ChangeLog-2005-08-23
WebCore/ForwardingHeaders/xml/dom_position.h [new file with mode: 0644]
WebCore/WebCore.pbproj/project.pbxproj
WebCore/khtml/dom/dom_doc.cpp
WebCore/khtml/dom/dom_doc.h
WebCore/khtml/ecma/kjs_dom.cpp
WebCore/khtml/ecma/kjs_dom.h
WebCore/khtml/ecma/kjs_dom.lut.h
WebCore/khtml/ecma/kjs_window.cpp
WebCore/khtml/ecma/kjs_window.h
WebCore/khtml/ecma/kjs_window.lut.h
WebCore/khtml/editing/SelectionController.cpp
WebCore/khtml/editing/htmlediting_impl.cpp
WebCore/khtml/editing/selection.cpp
WebCore/khtml/khtml_part.h
WebCore/khtml/khtml_selection.cpp
WebCore/khtml/xml/dom_docimpl.cpp
WebCore/khtml/xml/dom_docimpl.h
WebCore/khtml/xml/dom_nodeimpl.cpp
WebCore/khtml/xml/dom_position.cpp
WebCore/khtml/xml/dom_position.h
WebCore/khtml/xml/dom_selection.cpp
WebCore/kwq/KWQKHTMLPart.h
WebCore/kwq/KWQKHTMLPart.mm
WebCore/kwq/KWQRenderTreeDebug.cpp
WebCore/kwq/WebCoreBridge.h
WebKit/ChangeLog
WebKit/WebCoreSupport.subproj/WebBridge.m

index a8c0a1d271d03770d6d605fc2b9d16e899e46a81..bb134fbe699b198bb679f5e591ec7b8270cc2470 100644 (file)
@@ -1,3 +1,74 @@
+2004-04-06  Ken Kocienda  <kocienda@apple.com>
+
+        Reviewed by Dave
+
+        Added execCommand feature. 
+        Added Javascript selection object.
+        This lays the groundwork for layout tests for editing.
+
+        * khtml/dom/dom_doc.cpp:
+        (DOM::Document::execCommand): Added. Calls through to impl's execCommand.
+        * khtml/dom/dom_doc.h: Added execCommand declaration.
+        * khtml/ecma/kjs_dom.cpp:
+        (DOMDocumentProtoFunc::tryCall): Switch on new ExecCommand constant and call through to the document.
+        * khtml/ecma/kjs_dom.h: Added ExecCommand constant.
+        (KJS::DOMDocument::):
+        * khtml/ecma/kjs_dom.lut.h: Generated file.
+        * khtml/ecma/kjs_window.cpp:
+        (Window::Window): Initialize selection object.
+        (Window::selection): Return window's selection object.
+        (Window::mark): Mark selection object.
+        (WindowFunc::tryCall): Return selection object on GetSelection.
+        (LocationFunc::tryCall): Added.
+        (Selection::Selection): Added.
+        (Selection::~Selection): Added.
+        (Selection::get): Added.
+        (Selection::put): Added.
+        (Selection::toPrimitive): Added.
+        (Selection::toString): Added.
+        (SelectionFunc::tryCall): Added.
+        * khtml/ecma/kjs_window.h:
+        (KJS::Selection::): Added.
+        (KJS::Selection::part): Added.
+        (KJS::Selection::classInfo): Added.
+        * khtml/ecma/kjs_window.lut.h: Generated file.
+        * khtml/editing/htmlediting_impl.cpp:
+        (debugPosition): Debugging aid.
+        (DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace): Move the ending position
+        if you are about to delete it. Fixes a crasher I discovered while writing tests.
+        (DeleteSelectionCommandImpl::doApply): Move to containing editable block position 0
+        instead of 1 in a block in delete case 1.
+        (InputTextCommandImpl::prepareForTextInsertion):
+        (TypingCommandImpl::issueCommandForDeleteKey):
+        * khtml/khtml_part.h:
+        * khtml/khtml_selection.cpp:
+        (KHTMLSelection::validate): Now adjusts the selection down to leaf nodes if needed.
+        (KHTMLSelection::debugPosition): Debugging aid.
+        * khtml/xml/dom_docimpl.cpp:
+        (DocumentImpl::execCommand): Added. Supports five different commands.
+        * khtml/xml/dom_docimpl.h:
+        * khtml/xml/dom_nodeimpl.cpp:
+        (NodeImpl::previousEditable): Use false instead of 0 for equality check.
+        (NodeImpl::nextEditable): Ditto.
+        * khtml/xml/dom_position.cpp:
+        (DOMPosition::equivalentLeafPosition): New function
+        (DOMPosition::previousRenderedEditablePosition): New function
+        (DOMPosition::nextRenderedEditablePosition): New function
+        (DOMPosition::equivalentUpstreamPosition): Refined behavior to handle more cases correctly.
+        (DOMPosition::equivalentDownstreamPosition): Ditto.
+        (DOMPosition::atStartOfContainingEditableBlock):New function
+        (DOMPosition::atStartOfRootEditableBlock):New function
+        * khtml/xml/dom_position.h:
+        * kwq/KWQKHTMLPart.h:
+        * kwq/KWQKHTMLPart.mm:
+        (KWQKHTMLPart::issueUndoCommand): New function for calling undo programatically.
+        (KWQKHTMLPart::issueRedoCommand): Ditto, but for redo.
+        * kwq/KWQRenderTreeDebug.cpp:
+        (nodePositionRelativeToRoot): New function to generate log information for the selection.
+        (writeSelection): Writes the selection if there is one.
+        (externalRepresentation): Calls writeSelection
+        * kwq/WebCoreBridge.h: New declarations for issueUndoCommand and issueRedoCommand.
+
 2004-04-05  Darin Adler  <darin@apple.com>
 
         * khtml/html/kentities.gperf: Added &COPY; and &REG;, both supported by Gecko
diff --git a/WebCore/ForwardingHeaders/xml/dom_position.h b/WebCore/ForwardingHeaders/xml/dom_position.h
new file mode 100644 (file)
index 0000000..144c1ce
--- /dev/null
@@ -0,0 +1 @@
+#include <dom_position.h>
index d7f374fa5517670480047f3849b991535f4a2c51..5f826cc605813c024e39a2033ddbf94fd31c9b18 100644 (file)
                        refType = 4;
                        sourceTree = "<group>";
                };
-               832557C4061E3172007B8054 = {
-                       fileRef = 832557C3061E3172007B8054;
-                       isa = PBXBuildFile;
-                       settings = {
-                       };
-               };
 //830
 //831
 //832
index a4cfaa46f64ff0c39fa1ebbb8bd7c2b0ff687c07..fa5abcc5b628a2a55ab18ede27a20dfce0fd5248 100644 (file)
@@ -482,6 +482,14 @@ CSSStyleDeclaration Document::getOverrideStyle(const Element &elt, const DOMStri
     return r;
 }
 
+bool Document::execCommand(const DOMString &command, bool userInterface, const DOMString &value)
+{
+    if (!impl)
+       throw DOMException(DOMException::NOT_FOUND_ERR);
+
+    return static_cast<DocumentImpl*>(impl)->execCommand(command, userInterface, value);
+}
+
 // ----------------------------------------------------------------------------
 
 DocumentFragment::DocumentFragment() : Node()
index b8b333e3db0b73a32a2bb0df17cb309aaa12ac35..62063e951bfd12b0d71983b07e8fcd6cafc234b9 100644 (file)
@@ -791,6 +791,14 @@ public:
 
     DOMString toString() const;
 
+
+    /**
+     * not part of the DOM
+     *
+     * executes an editing command
+     */
+    bool execCommand(const DOMString &command, bool userInterface, const DOMString &value);
+
     Document( DocumentImpl *i);
 
 protected:
index ba37766bb356491aa1f639c426e5bd8fb08301b3..9950d6a0c4a4b7a7f981dd36cd4ab1f4e9671b49 100644 (file)
@@ -714,6 +714,7 @@ void DOMAttr::putValue(ExecState *exec, int token, const Value& value, int /*att
   createTreeWalker   DOMDocument::CreateTreeWalker             DontDelete|Function 4
   createEvent        DOMDocument::CreateEvent                  DontDelete|Function 1
   getOverrideStyle   DOMDocument::GetOverrideStyle             DontDelete|Function 2
+  execCommand        DOMDocument::ExecCommand                  DontDelete|Function 3
 @end
 */
 DEFINE_PROTOTYPE("DOMDocument", DOMDocumentProto)
@@ -893,6 +894,9 @@ Value DOMDocumentProtoFunc::tryCall(ExecState *exec, Object &thisObj, const List
     else
       return getDOMCSSStyleDeclaration(exec,doc.getOverrideStyle(static_cast<DOM::Element>(arg0),args[1].toString(exec).string()));
   }
+  case DOMDocument::ExecCommand: {
+    return Boolean(doc.execCommand(args[0].toString(exec).string(), args[1].toBoolean(exec), args[2].toString(exec).string()));
+  }
   default:
     break;
   }
index 19d4371eb91dffa7828adbae6a6cec5aa99a264b..2f3a0ee414780f5f4b69b9cff2a2f3259e104dae 100644 (file)
@@ -122,7 +122,7 @@ namespace KJS {
            CreateAttributeNS, GetElementsByTagNameNS, GetElementById,
            CreateRange, CreateNodeIterator, CreateTreeWalker, DefaultView,
            CreateEvent, StyleSheets, PreferredStylesheetSet, 
-           SelectedStylesheetSet, GetOverrideStyle, ReadyState };
+           SelectedStylesheetSet, GetOverrideStyle, ReadyState, ExecCommand };
   };
 
   class DOMAttr : public DOMNode {
index 28ade16a682b0eb02547ec3ec2b35a265b774757..07fa70467c16ecd336264d3e7b7198ac06bd85bf 100644 (file)
@@ -148,7 +148,7 @@ const struct HashEntry DOMDocumentProtoTableEntries[] = {
    { "createComment", DOMDocument::CreateComment, DontDelete|Function, 1, &DOMDocumentProtoTableEntries[27] },
    { 0, 0, 0, 0, 0 },
    { 0, 0, 0, 0, 0 },
-   { "createNodeIterator", DOMDocument::CreateNodeIterator, DontDelete|Function, 3, 0 },
+   { "createNodeIterator", DOMDocument::CreateNodeIterator, DontDelete|Function, 3, &DOMDocumentProtoTableEntries[30] },
    { 0, 0, 0, 0, 0 },
    { "importNode", DOMDocument::ImportNode, DontDelete|Function, 2, 0 },
    { "createElementNS", DOMDocument::CreateElementNS, DontDelete|Function, 2, 0 },
@@ -157,10 +157,11 @@ const struct HashEntry DOMDocumentProtoTableEntries[] = {
    { "getElementById", DOMDocument::GetElementById, DontDelete|Function, 1, 0 },
    { "createRange", DOMDocument::CreateRange, DontDelete|Function, 0, 0 },
    { "createEvent", DOMDocument::CreateEvent, DontDelete|Function, 1, 0 },
-   { "getOverrideStyle", DOMDocument::GetOverrideStyle, DontDelete|Function, 2, 0 }
+   { "getOverrideStyle", DOMDocument::GetOverrideStyle, DontDelete|Function, 2, 0 },
+   { "execCommand", DOMDocument::ExecCommand, DontDelete|Function, 3, 0 }
 };
 
-const struct HashTable DOMDocumentProtoTable = { 2, 30, DOMDocumentProtoTableEntries, 23 };
+const struct HashTable DOMDocumentProtoTable = { 2, 31, DOMDocumentProtoTableEntries, 23 };
 
 } // namespace
 
index 5f5ef038f9e57cde27d086c4423365414b63d4e1..5a33e60aa67ba26c13a3d353fe32458a4f255259 100644 (file)
 
 #include "khtmlview.h"
 #include "khtml_part.h"
+#include "khtml_selection.h"
+#include "dom/dom_string.h"
+#include "dom/dom_node.h"
+#include "editing/htmlediting.h"
 #include "xml/dom2_eventsimpl.h"
 #include "xml/dom_docimpl.h"
 #include "html/html_documentimpl.h"
 
+using DOM::DocumentImpl;
+using DOM::DOMString;
+using DOM::Node;
+using khtml::TypingCommand;
+
 using namespace KJS;
 
 namespace KJS {
@@ -279,7 +288,7 @@ const ClassInfo Window::info = { "Window", 0, &WindowTable, 0 };
 IMPLEMENT_PROTOFUNC(WindowFunc)
 
 Window::Window(KHTMLPart *p)
-  : ObjectImp(/*no proto*/), m_part(p), screen(0), history(0), frames(0), loc(0), m_evt(0)
+  : ObjectImp(/*no proto*/), m_part(p), screen(0), history(0), frames(0), loc(0), m_selection(0), m_evt(0)
 {
   winq = new WindowQObject(this);
   //kdDebug(6070) << "Window::Window this=" << this << " part=" << m_part << " " << m_part->name() << endl;
@@ -339,6 +348,13 @@ Location *Window::location() const
   return loc;
 }
 
+Selection *Window::selection() const
+{
+  if (!m_selection)
+    const_cast<Window*>(this)->m_selection = new Selection(m_part);
+  return m_selection;
+}
+
 // reference our special objects during garbage collection
 void Window::mark()
 {
@@ -352,6 +368,8 @@ void Window::mark()
   //kdDebug(6070) << "Window::mark " << this << " marking loc=" << loc << endl;
   if (loc && !loc->marked())
     loc->mark();
+  if (m_selection && !m_selection->marked())
+    m_selection->mark();
 }
 
 bool Window::hasProperty(ExecState * /*exec*/, const Identifier &/*p*/) const
@@ -1545,7 +1563,7 @@ Value WindowFunc::tryCall(ExecState *exec, Object &thisObj, const List &args)
   case Window::GetSelection:
     if (!window->isSafeScript(exec))
         return Undefined();
-    return String(part->selectedText());
+    return Value(window->selection());
   case Window::Blur:
 #if APPLE_CHANGES
     KWQ(part)->unfocusWindow();
@@ -2079,6 +2097,196 @@ Value LocationFunc::tryCall(ExecState *exec, Object &thisObj, const List &args)
   return Undefined();
 }
 
+////////////////////// Selection Object ////////////////////////
+
+const ClassInfo Selection::info = { "Selection", 0, 0, 0 };
+/*
+@begin SelectionTable 19
+  anchorNode                Selection::AnchorNode                       DontDelete|ReadOnly
+  anchorOffset              Selection::AnchorOffset                 DontDelete|ReadOnly
+  focusNode                 Selection::FocusNode                        DontDelete|ReadOnly
+  focusOffset               Selection::FocusOffset                      DontDelete|ReadOnly
+  baseNode                  Selection::AnchorNode                       DontDelete|ReadOnly
+  baseOffset                Selection::AnchorOffset              DontDelete|ReadOnly
+  extentNode                Selection::FocusNode                 DontDelete|ReadOnly
+  extentOffset              Selection::FocusOffset                      DontDelete|ReadOnly
+  isCollapsed               Selection::IsCollapsed                      DontDelete|ReadOnly
+  type                      Selection::_Type                     DontDelete|ReadOnly
+  [[==]]                       Selection::EqualEqual                DontDelete|ReadOnly
+  toString                  Selection::ToString                  DontDelete|Function 0
+  collapse                  Selection::Collapse                  DontDelete|Function 2
+  collapseToEnd             Selection::CollapseToEnd             DontDelete|Function 0
+  collapseToStart           Selection::CollapseToStart           DontDelete|Function 0
+  empty                     Selection::Empty                     DontDelete|Function 0
+  setBaseAndExtent          Selection::SetBaseAndExtent          DontDelete|Function 4
+  setPosition               Selection::SetPosition               DontDelete|Function 2
+  modify                    Selection::Modify                    DontDelete|Function 3
+@end
+*/
+IMPLEMENT_PROTOFUNC(SelectionFunc)
+Selection::Selection(KHTMLPart *p) : m_part(p)
+{
+  //kdDebug(6070) << "Selection::Selection " << this << " m_part=" << (void*)m_part << endl;
+}
+
+Selection::~Selection()
+{
+  //kdDebug(6070) << "Selection::~Selection " << this << " m_part=" << (void*)m_part << endl;
+}
+
+Value Selection::get(ExecState *exec, const Identifier &p) const
+{
+#ifdef KJS_VERBOSE
+  kdDebug(6070) << "Selection::get " << p.qstring() << " m_part=" << (void*)m_part << endl;
+#endif
+
+  if (m_part.isNull())
+    return Undefined();
+  
+  const Window* window = Window::retrieveWindow(m_part);
+  if (!window || !window->isSafeScript(exec))
+      return Undefined();
+
+  DocumentImpl *docimpl = m_part->xmlDocImpl();
+  if (docimpl)
+    docimpl->updateLayout();
+
+  KURL url = m_part->url();
+  const HashEntry *entry = Lookup::findEntry(&SelectionTable, p);
+  if (entry)
+    switch (entry->value) {
+        case AnchorNode:
+        case BaseNode:
+            return getDOMNode(exec, Node(m_part->selection().baseNode()));
+        case AnchorOffset:
+        case BaseOffset:
+            return Number(m_part->selection().baseOffset());
+        case FocusNode:
+        case ExtentNode:
+            return getDOMNode(exec, Node(m_part->selection().extentNode()));
+        case FocusOffset:
+        case ExtentOffset:
+            return Number(m_part->selection().extentOffset());
+        case IsCollapsed:
+            return Boolean(m_part->selection().state() == KHTMLSelection::CARET);
+        case _Type: {
+            switch (m_part->selection().state()) {
+                case KHTMLSelection::NONE:
+                    return String("None");
+                case KHTMLSelection::CARET:
+                    return String("Caret");
+                case KHTMLSelection::RANGE:
+                    return String("Range");
+            }
+        }
+        case EqualEqual:
+            return String(toString(exec));
+        case ToString:
+          return lookupOrCreateFunction<SelectionFunc>(exec,p,this,entry->value,entry->params,entry->attr);
+    }
+    // Look for overrides
+    ValueImp * val = ObjectImp::getDirect(p);
+    if (val)
+        return Value(val);
+    if (entry)
+        switch (entry->value) {
+            case Collapse:
+            case CollapseToEnd:
+            case CollapseToStart:
+            case Empty:
+            case SetBaseAndExtent:
+            case SetPosition:
+            case Modify:
+                return lookupOrCreateFunction<SelectionFunc>(exec,p,this,entry->value,entry->params,entry->attr);
+        }
+
+    return Undefined();
+}
+
+void Selection::put(ExecState *exec, const Identifier &p, const Value &v, int attr)
+{
+}
+
+Value Selection::toPrimitive(ExecState *exec, Type) const
+{
+  return String(toString(exec));
+}
+
+UString Selection::toString(ExecState *) const
+{
+    if (m_part->selection().state() != KHTMLSelection::RANGE)
+        return UString("");
+    return UString(m_part->selection().toRange().toString());
+}
+
+Value SelectionFunc::tryCall(ExecState *exec, Object &thisObj, const List &args)
+{
+    if (!thisObj.inherits(&Selection::info)) {
+        Object err = Error::create(exec,TypeError);
+        exec->setException(err);
+        return err;
+    }
+    Selection *selection = static_cast<Selection *>(thisObj.imp());
+    KHTMLPart *part = selection->part();
+    if (part) {
+        DocumentImpl *docimpl = part->xmlDocImpl();
+        if (docimpl)
+            docimpl->updateLayout();
+            
+        switch (id) {
+            case Selection::Collapse:
+                TypingCommand::closeTyping(part->lastEditCommand());
+                part->setSelection(KHTMLSelection(KJS::toNode(args[0]).handle(), args[1].toInt32(exec)));
+                break;
+            case Selection::CollapseToEnd:
+                TypingCommand::closeTyping(part->lastEditCommand());
+                part->setSelection(KHTMLSelection(part->selection().endPosition()));
+                break;
+            case Selection::CollapseToStart:
+                TypingCommand::closeTyping(part->lastEditCommand());
+                part->setSelection(KHTMLSelection(part->selection().startPosition()));
+                break;
+            case Selection::Empty:
+                TypingCommand::closeTyping(part->lastEditCommand());
+                part->clearSelection();
+                break;
+            case Selection::SetBaseAndExtent:
+                TypingCommand::closeTyping(part->lastEditCommand());
+                part->setSelection(KHTMLSelection(KJS::toNode(args[0]).handle(), args[1].toInt32(exec), KJS::toNode(args[2]).handle(), args[3].toInt32(exec)));
+                break;
+            case Selection::SetPosition:
+                TypingCommand::closeTyping(part->lastEditCommand());
+                part->setSelection(KHTMLSelection(KJS::toNode(args[0]).handle(), args[1].toInt32(exec)));
+                break;
+            case Selection::Modify: {
+                TypingCommand::closeTyping(part->lastEditCommand());
+                KHTMLSelection s(part->selection());
+                KHTMLSelection::EAlter alter = KHTMLSelection::MOVE;
+                if (args[0].toString(exec).string().lower() == "extend")
+                    alter = KHTMLSelection::EXTEND;
+                DOMString directionString = args[1].toString(exec).string().lower();
+                KHTMLSelection::EDirection direction = KHTMLSelection::FORWARD;
+                if (directionString == "backward")
+                    direction = KHTMLSelection::BACKWARD;
+                else if (directionString == "left")
+                    direction = KHTMLSelection::LEFT;
+                if (directionString == "right")
+                    direction = KHTMLSelection::RIGHT;
+                KHTMLSelection::ETextGranularity granularity = KHTMLSelection::CHARACTER;
+                DOMString granularityString = args[2].toString(exec).string().lower();
+                if (granularityString == "word")
+                    granularity = KHTMLSelection::WORD;
+                else if (granularityString == "line")
+                    granularity = KHTMLSelection::LINE;
+                s.modify(alter, direction, granularity);
+                part->setSelection(s);
+            }
+        }
+    }
+
+    return Undefined();
+}
+
 ////////////////////// History Object ////////////////////////
 
 const ClassInfo History::info = { "History", 0, 0, 0 };
index 793d2c9aeb8ee7e04cbe6ac84150f032eecc94f2..af210dedddca596a793cd918c9435fecc9e3ffe5 100644 (file)
@@ -38,6 +38,7 @@ namespace KJS {
   class WindowFunc;
   class WindowQObject;
   class Location;
+  class Selection;
   class History;
   class FrameArray;
   class JSEventListener;
@@ -100,6 +101,7 @@ namespace KJS {
     void scheduleClose();
     bool isSafeScript(ExecState *exec) const;
     Location *location() const;
+    Selection *selection() const;
     JSEventListener *getJSEventListener(const Value &val, bool html = false);
     JSLazyEventListener *getJSLazyEventListener(const QString &code, bool html = false);
     void clear( ExecState *exec );
@@ -136,6 +138,7 @@ namespace KJS {
     History *history;
     FrameArray *frames;
     Location *loc;
+    Selection *m_selection;
     WindowQObject *winq;
     DOM::Event *m_evt;
   };
@@ -203,6 +206,25 @@ namespace KJS {
     QGuardedPtr<KHTMLPart> m_part;
   };
 
+  class Selection : public ObjectImp {
+  public:
+    ~Selection();
+    virtual Value get(ExecState *exec, const Identifier &propertyName) const;
+    virtual void put(ExecState *exec, const Identifier &propertyName, const Value &value, int attr = None);
+    virtual Value toPrimitive(ExecState *exec, Type preferred) const;
+    virtual UString toString(ExecState *exec) const;
+    enum { AnchorNode, AnchorOffset, FocusNode, FocusOffset, BaseNode, BaseOffset, ExtentNode, ExtentOffset, 
+           IsCollapsed, _Type, EqualEqual, Collapse, CollapseToEnd, CollapseToStart, Empty, ToString, 
+           SetBaseAndExtent, SetPosition, Modify };
+    KHTMLPart *part() const { return m_part; }
+    virtual const ClassInfo* classInfo() const { return &info; }
+    static const ClassInfo info;
+  private:
+    friend class Window;
+    Selection(KHTMLPart *p);
+    QGuardedPtr<KHTMLPart> m_part;
+  };
+
 #ifdef Q_WS_QWS
   class Konqueror : public ObjectImp {
     friend class KonquerorFunc;
index b5f017c141fde972f8ac3c2dd6c2b84976bec56e..a3efca1e2c327482e791ead34fb769ed4c1b6a05 100644 (file)
@@ -176,6 +176,42 @@ const struct HashTable LocationTable = { 2, 16, LocationTableEntries, 11 };
 
 namespace KJS {
 
+const struct HashEntry SelectionTableEntries[] = {
+   { "focusOffset", Selection::FocusOffset, DontDelete|ReadOnly, 0, &SelectionTableEntries[20] },
+   { 0, 0, 0, 0, 0 },
+   { "modify", Selection::Modify, DontDelete|Function, 3, 0 },
+   { "focusNode", Selection::FocusNode, DontDelete|ReadOnly, 0, &SelectionTableEntries[19] },
+   { 0, 0, 0, 0, 0 },
+   { 0, 0, 0, 0, 0 },
+   { "extentOffset", Selection::FocusOffset, DontDelete|ReadOnly, 0, 0 },
+   { "setPosition", Selection::SetPosition, DontDelete|Function, 2, 0 },
+   { "empty", Selection::Empty, DontDelete|Function, 0, 0 },
+   { "extentNode", Selection::FocusNode, DontDelete|ReadOnly, 0, 0 },
+   { 0, 0, 0, 0, 0 },
+   { 0, 0, 0, 0, 0 },
+   { 0, 0, 0, 0, 0 },
+   { "type", Selection::_Type, DontDelete|ReadOnly, 0, 0 },
+   { "collapseToEnd", Selection::CollapseToEnd, DontDelete|Function, 0, &SelectionTableEntries[25] },
+   { "anchorOffset", Selection::AnchorOffset, DontDelete|ReadOnly, 0, &SelectionTableEntries[22] },
+   { 0, 0, 0, 0, 0 },
+   { 0, 0, 0, 0, 0 },
+   { "anchorNode", Selection::AnchorNode, DontDelete|ReadOnly, 0, &SelectionTableEntries[21] },
+   { "baseNode", Selection::AnchorNode, DontDelete|ReadOnly, 0, &SelectionTableEntries[23] },
+   { "baseOffset", Selection::AnchorOffset, DontDelete|ReadOnly, 0, 0 },
+   { "isCollapsed", Selection::IsCollapsed, DontDelete|ReadOnly, 0, 0 },
+   { "[[==]]", Selection::EqualEqual, DontDelete|ReadOnly, 0, &SelectionTableEntries[24] },
+   { "toString", Selection::ToString, DontDelete|Function, 0, &SelectionTableEntries[26] },
+   { "collapse", Selection::Collapse, DontDelete|Function, 2, 0 },
+   { "collapseToStart", Selection::CollapseToStart, DontDelete|Function, 0, 0 },
+   { "setBaseAndExtent", Selection::SetBaseAndExtent, DontDelete|Function, 4, 0 }
+};
+
+const struct HashTable SelectionTable = { 2, 27, SelectionTableEntries, 19 };
+
+} // namespace
+
+namespace KJS {
+
 const struct HashEntry HistoryTableEntries[] = {
    { 0, 0, 0, 0, 0 },
    { "back", History::Back, DontDelete|Function, 0, &HistoryTableEntries[4] },
index 46cb6edfbe6772cc3c4b75e2e338673b8dc06148..6107244e7985488c6a058228ea2f019417852f68 100644 (file)
@@ -528,6 +528,23 @@ void KHTMLSelection::setEndOffset(long offset)
 
 void KHTMLSelection::validate(ETextGranularity expandTo)
 {
+    // move the base and extent nodes to their equivalent leaf positions
+    bool baseAndExtentEqual = m_baseNode == m_extentNode && m_baseOffset == m_extentOffset;
+    if (m_baseNode) {
+        DOMPosition pos = basePosition().equivalentLeafPosition();
+        m_baseNode = pos.node();
+        m_baseOffset = pos.offset();
+        if (baseAndExtentEqual) {
+            m_extentNode = pos.node();
+            m_extentOffset = pos.offset();
+        }
+    }
+    if (m_extentNode && !baseAndExtentEqual) {
+        DOMPosition pos = extentPosition().equivalentLeafPosition();
+        m_extentNode = pos.node();
+        m_extentOffset = pos.offset();
+    }
+
     // make sure we do not have a dangling start or end
        if (!m_baseNode && !m_extentNode) {
         setBaseOffset(0);
@@ -1012,14 +1029,14 @@ void KHTMLSelection::debugPosition() const
         fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
     }
     else {
-        DOMPosition pos = endPosition();
+        DOMPosition pos = startPosition();
         DOMPosition upstream = pos.equivalentUpstreamPosition();
         DOMPosition downstream = pos.equivalentDownstreamPosition();
         fprintf(stderr, "upstream:   %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
         fprintf(stderr, "start:      %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
         fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
         fprintf(stderr, "-----------------------------------\n");
-        pos = startPosition();
+        pos = endPosition();
         upstream = pos.equivalentUpstreamPosition();
         downstream = pos.equivalentDownstreamPosition();
         fprintf(stderr, "upstream:   %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
index 88839845570e2e5e2155be908dba6da0822de932..a1f193055e36a5c952053d5299bee46233411a77 100644 (file)
@@ -232,6 +232,11 @@ static DOMString &nonBreakingSpaceString()
     return nonBreakingSpaceString;
 }
 
+static void debugPosition(const char *prefix, const DOMPosition &pos)
+{
+    LOG(Editing, "%s%s %p : %d", prefix, getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
+}
+
 //------------------------------------------------------------------------------------------
 // EditCommandImpl
 
@@ -672,6 +677,8 @@ DOMPosition DeleteCollapsibleWhitespaceCommandImpl::deleteWhitespace(const DOMPo
             unsigned long count = it.current().offset() - deleteStart.offset();
             if (count == textNode->length()) {
                 LOG(Editing, "   removeNodeAndPrune 1: [%p]\n", textNode);
+                if (textNode == endingPosition.node())
+                    endingPosition = DOMPosition(next.node(), next.node()->caretMinOffset());
                 removeNodeAndPrune(textNode);
             }
             else {
@@ -794,13 +801,6 @@ void DeleteSelectionCommandImpl::joinTextNodesWithSameStyle()
     }
 }
 
-static void debugPosition(const char *prefix, const DOMPosition &pos)
-{
-    LOG(Editing, "%s%s %p : %d", prefix, getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
-}
-
-enum { NoPositionModification, MoveDownstreamPositionModification, MoveToNextCharacterModification };
-
 void DeleteSelectionCommandImpl::doApply()
 {
     if (m_selectionToDelete.state() != KHTMLSelection::RANGE)
@@ -846,7 +846,7 @@ void DeleteSelectionCommandImpl::doApply()
     // Start is not completely selected
     if (startAtStartOfBlock) {
         LOG(Editing,  "ending position case 1");
-        endingPosition = DOMPosition(downstreamStart.node()->containingEditableBlock(), 1);
+        endingPosition = DOMPosition(downstreamStart.node()->containingEditableBlock(), 0);
         adjustEndingPositionDownstream = true;
     }
     else if (!startCompletelySelected) {
@@ -1123,6 +1123,8 @@ DOMPosition InputTextCommandImpl::prepareForTextInsertion()
     ASSERT(selection.state() == KHTMLSelection::CARET);
     
     DOMPosition pos = selection.startPosition().equivalentUpstreamPosition();
+    if (!pos.inRenderedContent())
+        pos = pos.nextRenderedEditablePosition();
     if (!pos.node()->inSameContainingEditableBlock(selection.startNode()))
         pos = selection.startPosition();
     
@@ -1135,12 +1137,14 @@ DOMPosition InputTextCommandImpl::prepareForTextInsertion()
         
         if (pos.node()->isEditableBlock())
             appendNode(pos.node(), m_insertedTextNode);
-        else if (pos.node()->id() == ID_BR || pos.offset() == 1)
-            insertNodeAfter(m_insertedTextNode, pos.node());
-        else {
-            ASSERT(pos.offset() == 0);
+        else if (pos.node()->id() == ID_BR && pos.offset() == 1)
             insertNodeBefore(m_insertedTextNode, pos.node());
-        }
+        else if (pos.node()->caretMinOffset() == pos.offset())
+            insertNodeBefore(m_insertedTextNode, pos.node());
+        else if (pos.node()->caretMaxOffset() == pos.offset())
+            insertNodeAfter(m_insertedTextNode, pos.node());
+        else
+            ASSERT_NOT_REACHED();
         
         pos = DOMPosition(m_insertedTextNode, 0);
     }
@@ -1153,7 +1157,10 @@ void InputTextCommandImpl::execute(const DOMString &text)
     KHTMLSelection selection = currentSelection();
 
     // Delete the current selection
-    deleteSelection();
+    if (selection.state() == KHTMLSelection::RANGE)
+        deleteSelection();
+    else
+        deleteCollapsibleWhitespace();
     
     // Make sure the document is set up to receive text
     DOMPosition pos = prepareForTextInsertion();
@@ -1711,15 +1718,12 @@ void TypingCommandImpl::issueCommandForDeleteKey()
     
     if (selection.state() == KHTMLSelection::CARET) {
         KHTMLSelection selectionToDelete(selection.startPosition().previousCharacterPosition(), selection.startPosition());
-        setEndingSelection(selectionToDelete);
-        deleteCollapsibleWhitespace();
-        selection = currentSelection();
-        deleteSelection(selection);
+        deleteCollapsibleWhitespace(selectionToDelete);
     }
     else { // selection.state() == KHTMLSelection::RANGE
         deleteCollapsibleWhitespace();
-        deleteSelection();
     }
+    deleteSelection(endingSelection());
 }
 
 void TypingCommandImpl::deleteKeyPressed()
index 46cb6edfbe6772cc3c4b75e2e338673b8dc06148..6107244e7985488c6a058228ea2f019417852f68 100644 (file)
@@ -528,6 +528,23 @@ void KHTMLSelection::setEndOffset(long offset)
 
 void KHTMLSelection::validate(ETextGranularity expandTo)
 {
+    // move the base and extent nodes to their equivalent leaf positions
+    bool baseAndExtentEqual = m_baseNode == m_extentNode && m_baseOffset == m_extentOffset;
+    if (m_baseNode) {
+        DOMPosition pos = basePosition().equivalentLeafPosition();
+        m_baseNode = pos.node();
+        m_baseOffset = pos.offset();
+        if (baseAndExtentEqual) {
+            m_extentNode = pos.node();
+            m_extentOffset = pos.offset();
+        }
+    }
+    if (m_extentNode && !baseAndExtentEqual) {
+        DOMPosition pos = extentPosition().equivalentLeafPosition();
+        m_extentNode = pos.node();
+        m_extentOffset = pos.offset();
+    }
+
     // make sure we do not have a dangling start or end
        if (!m_baseNode && !m_extentNode) {
         setBaseOffset(0);
@@ -1012,14 +1029,14 @@ void KHTMLSelection::debugPosition() const
         fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
     }
     else {
-        DOMPosition pos = endPosition();
+        DOMPosition pos = startPosition();
         DOMPosition upstream = pos.equivalentUpstreamPosition();
         DOMPosition downstream = pos.equivalentDownstreamPosition();
         fprintf(stderr, "upstream:   %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
         fprintf(stderr, "start:      %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
         fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
         fprintf(stderr, "-----------------------------------\n");
-        pos = startPosition();
+        pos = endPosition();
         upstream = pos.equivalentUpstreamPosition();
         downstream = pos.equivalentDownstreamPosition();
         fprintf(stderr, "upstream:   %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
index 22678e559311a078eb04135dc96addf0b8228f8c..4fb2241dbc852a8d572d60d256f92a09c625a5e5 100644 (file)
@@ -87,6 +87,8 @@ namespace khtml
 };
 
 namespace KJS {
+    class Selection;
+    class SelectionFunc;
     class Window;
     class WindowFunc;
     class JSEventListener;
@@ -152,6 +154,8 @@ class KHTMLPart : public KParts::ReadOnlyPart
   friend class KHTMLRun;
   friend class DOM::HTMLFormElementImpl;
   friend class khtml::RenderPartObject;
+  friend class KJS::Selection;
+  friend class KJS::SelectionFunc;
   friend class KJS::Window;
   friend class KJS::WindowFunc;
   friend class KJS::JSEventListener;
index 46cb6edfbe6772cc3c4b75e2e338673b8dc06148..6107244e7985488c6a058228ea2f019417852f68 100644 (file)
@@ -528,6 +528,23 @@ void KHTMLSelection::setEndOffset(long offset)
 
 void KHTMLSelection::validate(ETextGranularity expandTo)
 {
+    // move the base and extent nodes to their equivalent leaf positions
+    bool baseAndExtentEqual = m_baseNode == m_extentNode && m_baseOffset == m_extentOffset;
+    if (m_baseNode) {
+        DOMPosition pos = basePosition().equivalentLeafPosition();
+        m_baseNode = pos.node();
+        m_baseOffset = pos.offset();
+        if (baseAndExtentEqual) {
+            m_extentNode = pos.node();
+            m_extentOffset = pos.offset();
+        }
+    }
+    if (m_extentNode && !baseAndExtentEqual) {
+        DOMPosition pos = extentPosition().equivalentLeafPosition();
+        m_extentNode = pos.node();
+        m_extentOffset = pos.offset();
+    }
+
     // make sure we do not have a dangling start or end
        if (!m_baseNode && !m_extentNode) {
         setBaseOffset(0);
@@ -1012,14 +1029,14 @@ void KHTMLSelection::debugPosition() const
         fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
     }
     else {
-        DOMPosition pos = endPosition();
+        DOMPosition pos = startPosition();
         DOMPosition upstream = pos.equivalentUpstreamPosition();
         DOMPosition downstream = pos.equivalentDownstreamPosition();
         fprintf(stderr, "upstream:   %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
         fprintf(stderr, "start:      %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
         fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
         fprintf(stderr, "-----------------------------------\n");
-        pos = startPosition();
+        pos = endPosition();
         upstream = pos.equivalentUpstreamPosition();
         downstream = pos.equivalentDownstreamPosition();
         fprintf(stderr, "upstream:   %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
index 42592445ad7a9de261012a5c27eec3d2d3be1226..c351083ebdbcb8058ee01d5b2ce55415dcc670a1 100644 (file)
@@ -2641,6 +2641,51 @@ DOMString DocumentImpl::toString() const
 
 #endif // APPLE_CHANGES
 
+bool DocumentImpl::execCommand(const DOMString &command, bool userInterface, const DOMString &value)
+{
+    static AtomicString selectAllCommand("selectall");
+    static AtomicString insertTextCommand("inserttext");
+    static AtomicString undoCommand("undo");
+    static AtomicString redoCommand("redo");
+    static AtomicString deleteCommand("delete");
+
+    updateLayout();
+
+    AtomicString atom(command.lower());
+    if (atom == selectAllCommand) {
+        if (!part())
+            return false;
+        part()->selectAll();
+        return true;
+    }
+    else if (atom == insertTextCommand) {
+        if (!part() || part()->selection().isEmpty())
+            return false;
+        TypingCommand::insertText(this, value);
+        return true;
+    }
+    else if (atom == undoCommand) {
+        if (!part())
+            return false;
+        KWQ(part())->issueUndoCommand();
+        return true;
+    }
+    else if (atom == redoCommand) {
+        if (!part())
+            return false;
+        KWQ(part())->issueRedoCommand();
+        return true;
+    }
+    else if (atom == deleteCommand) {
+        if (!part() || part()->selection().isEmpty())
+            return false;
+        TypingCommand::deleteKeyPressed(this);
+        return true;
+    }
+
+    return false;
+}
+
 // ----------------------------------------------------------------------------
 
 DocumentFragmentImpl::DocumentFragmentImpl(DocumentPtr *doc) : NodeBaseImpl(doc)
index 1f7ce9cc59fce7efb101077bf26e5d4e5421c860..09f587dfe30acd59e69ec56bea2ec424d95a044a 100644 (file)
@@ -497,6 +497,8 @@ public:
 
     DOMString toString() const;
     
+    bool execCommand(const DOMString &command, bool userInterface, const DOMString &value);
+    
 #ifndef KHTML_NO_XBL
     // XBL methods
     XBL::XBLBindingManager* bindingManager() const { return m_bindingManager; }
index 6493a1eee4e9ae114683a37aff7941dcc3395e5d..f526e916e3db651995ebe460d0544d4715e7f3c2 100644 (file)
@@ -1156,7 +1156,7 @@ NodeImpl *NodeImpl::previousEditable() const
     while (node) {
         if (!node->isContentEditable())
             return 0;
-        if (node->hasChildNodes() == 0)
+        if (node->hasChildNodes() == false)
             return node;
         node = node->traversePreviousNode();
     }
@@ -1169,7 +1169,7 @@ NodeImpl *NodeImpl::nextEditable() const
     while (node) {
         if (!node->isContentEditable())
             return 0;
-        if (node->hasChildNodes() == 0)
+        if (node->hasChildNodes() == false)
             return node;
         node = node->traverseNextNode();
     }
index cee853553746700d54a7c1974298710d9719150c..e2ef22adb72ce0bd28c1b40e9f790eaa1fc9385b 100644 (file)
@@ -118,6 +118,66 @@ long DOMPosition::renderedOffset() const
     return result;
 }
 
+DOMPosition DOMPosition::equivalentLeafPosition() const
+{
+    if (node()->hasChildNodes() == false)
+        return *this;
+    
+    NodeImpl *n = node();
+    int count = 0;
+    while (1) {
+        n = n->nextLeafNode();
+        if (!n)
+            return *this;
+        if (count + n->maxOffset() >= offset()) {
+            count = offset() - count;
+            break;
+        }
+        count += n->maxOffset();
+    }
+    return DOMPosition(n, count);
+}
+
+DOMPosition DOMPosition::previousRenderedEditablePosition() const
+{
+    if (isEmpty())
+        return DOMPosition();
+
+    if (node()->isContentEditable() && node()->hasChildNodes() == false && inRenderedContent())
+        return *this;
+
+    NodeImpl *n = node();
+    while (1) {
+        n = n->previousEditable();
+        if (!n)
+            return DOMPosition();
+        if (n->renderer() && n->renderer()->style()->visibility() == khtml::VISIBLE)
+            break;
+    }
+    
+    return DOMPosition(n, 0);
+}
+
+DOMPosition DOMPosition::nextRenderedEditablePosition() const
+{
+    if (isEmpty())
+        return DOMPosition();
+
+    if (node()->isContentEditable() && node()->hasChildNodes() == false && inRenderedContent())
+        return *this;
+
+    NodeImpl *n = node();
+    while (1) {
+        n = n->nextEditable();
+        if (!n)
+            return DOMPosition();
+        if (n->renderer() && n->renderer()->style()->visibility() == khtml::VISIBLE)
+            break;
+    }
+    
+    return DOMPosition(n, 0);
+}
+
 DOMPosition DOMPosition::previousCharacterPosition() const
 {
     if (isEmpty())
@@ -177,27 +237,47 @@ DOMPosition DOMPosition::equivalentUpstreamPosition() const
     if (!node())
         return DOMPosition();
 
-    if (!node()->isTextNode() && offset() > node()->caretMinOffset())
-        return *this;
-    
     NodeImpl *block = node()->containingEditableBlock();
-                
-    EditIterator it(*this);
-    DOMPosition prev = it.peekPrevious();
-    if (validUpstreamDownstreamPosition() && prev.validUpstreamDownstreamPosition()) {
-        if (node() == prev.node())
-            return *this;
-        else
-            return prev;
-    }
-    while (!it.atStart()) {
-        it.previous();
-        if (it.current().validUpstreamDownstreamPosition())
-            return it.current();
+    
+    EditIterator it(*this);            
+    for (; !it.atStart(); it.previous()) {   
         if (block != it.current().node()->containingEditableBlock())
             return it.next();
+
+        if (!node()->isContentEditable())
+            return it.next();
+            
+        RenderObject *renderer = it.current().node()->renderer();
+        if (!renderer)
+            continue;
+
+        if (renderer->style()->visibility() != khtml::VISIBLE)
+            continue;
+
+        if (renderer->isBlockFlow() || renderer->isReplaced() || renderer->isBR()) {
+            if (it.current().offset() >= renderer->caretMaxOffset())
+                return DOMPosition(it.current().node(), renderer->caretMaxOffset());
+            else
+                continue;
+        }
+
+        if (renderer->isText() && static_cast<RenderText *>(renderer)->firstTextBox()) {
+            if (it.current().node() != node())
+                return DOMPosition(it.current().node(), renderer->caretMaxOffset());
+
+            if (it.current().offset() < 0)
+                continue;
+            uint textOffset = it.current().offset();
+
+            RenderText *textRenderer = static_cast<RenderText *>(renderer);
+            for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+                if (textOffset > box->start() && textOffset <= box->start() + box->len())
+                    return it.current();
+            }
+        }
     }
-    return *this;
+    
+    return it.current();
 }
 
 DOMPosition DOMPosition::equivalentDownstreamPosition() const
@@ -205,69 +285,57 @@ DOMPosition DOMPosition::equivalentDownstreamPosition() const
     if (!node())
         return DOMPosition();
 
-    if (!node()->isTextNode() && offset() < node()->caretMaxOffset())
-        return *this;
-
     NodeImpl *block = node()->containingEditableBlock();
-        
-    EditIterator it(*this);
-    DOMPosition next = it.peekNext();
-    if (validUpstreamDownstreamPosition() && next.validUpstreamDownstreamPosition()) {
-        if (node() == next.node())
-            return *this;
-        else
-            return next;
-    }
-    while (!it.atEnd()) {
-        if (it.next().validUpstreamDownstreamPosition())
-            return it.current();
+    
+    EditIterator it(*this);            
+    for (; !it.atEnd(); it.next()) {   
         if (block != it.current().node()->containingEditableBlock())
             return it.previous();
-    }
-    return *this;
-}
 
-bool DOMPosition::validUpstreamDownstreamPosition() const
-{
-    if (isEmpty())
-        return false;
-        
-    RenderObject *renderer = node()->renderer();
-    if (!renderer || !renderer->isEditable())
-        return false;
+        if (!node()->isContentEditable())
+            return it.next();
+            
+        RenderObject *renderer = it.current().node()->renderer();
+        if (!renderer)
+            continue;
 
-    if (renderer->style()->visibility() != khtml::VISIBLE)
-        return false;
+        if (renderer->style()->visibility() != khtml::VISIBLE)
+            continue;
 
-    if (renderer->isBR() || renderer->isBlockFlow())
-        return true;
-    
-    if (renderer->isText()) {
-        RenderText *textRenderer = static_cast<RenderText *>(renderer);
-        InlineTextBox *lastTextBox = textRenderer->lastTextBox();
-        for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
-            if (offset() >= box->m_start) {
-                if (box == lastTextBox) {
-                    if (offset() <= box->m_start + box->m_len)
-                        return true;
-                }
-                else if (offset() < box->m_start + box->m_len)
-                    return true;
-            }
-            else if (offset() < box->m_start) {
-                // The offset we're looking for is before this node
-                // this means the offset must be in content that is
-                // not rendered. Return false.
-                return false;
+        if (renderer->isBlockFlow() || renderer->isReplaced() || renderer->isBR()) {
+            if (it.current().offset() <= renderer->caretMinOffset())
+                return DOMPosition(it.current().node(), renderer->caretMinOffset());
+            else
+                continue;
+        }
+
+        if (renderer->isText() && static_cast<RenderText *>(renderer)->firstTextBox()) {
+            if (it.current().node() != node())
+                return DOMPosition(it.current().node(), renderer->caretMinOffset());
+
+            if (it.current().offset() < 0)
+                continue;
+            uint textOffset = it.current().offset();
+
+            RenderText *textRenderer = static_cast<RenderText *>(renderer);
+            for (InlineTextBox *box = textRenderer->firstTextBox(); box; box = box->nextTextBox()) {
+                if (textOffset >= box->start() && textOffset <= box->end())
+                    return it.current();
             }
         }
-        return false;
     }
     
-    if (offset() >= renderer->caretMinOffset() && offset() <= renderer->caretMaxOffset())
-        return true;
-    
-    return false;
+    return it.current();
+}
+
+bool DOMPosition::atStartOfContainingEditableBlock() const
+{
+    return renderedOffset() == 0 && inFirstEditableInContainingEditableBlock();
+}
+
+bool DOMPosition::atStartOfRootEditableBlock() const
+{
+    return renderedOffset() == 0 && inFirstEditableInRootEditableBlock();
 }
 
 bool DOMPosition::inRenderedContent() const
index 8e700eea20f00ba222402018fd38db7b5a303201..ecf4980472f5d3c80929024585a4cde9c24a40a1 100644 (file)
@@ -46,11 +46,15 @@ public:
     bool isEmpty() const { return m_node == 0; }
     bool notEmpty() const { return m_node != 0; }
     
+    DOMPosition equivalentLeafPosition() const;
+    DOMPosition previousRenderedEditablePosition() const;
+    DOMPosition nextRenderedEditablePosition() const;
     DOMPosition previousCharacterPosition() const;
     DOMPosition nextCharacterPosition() const;
     DOMPosition equivalentUpstreamPosition() const;
     DOMPosition equivalentDownstreamPosition() const;
-    bool validUpstreamDownstreamPosition() const;
+    bool atStartOfContainingEditableBlock() const;
+    bool atStartOfRootEditableBlock() const;
     bool inRenderedContent() const;
     bool inRenderedText() const;
     bool rendersOnSameLine(const DOMPosition &pos) const;
index 46cb6edfbe6772cc3c4b75e2e338673b8dc06148..6107244e7985488c6a058228ea2f019417852f68 100644 (file)
@@ -528,6 +528,23 @@ void KHTMLSelection::setEndOffset(long offset)
 
 void KHTMLSelection::validate(ETextGranularity expandTo)
 {
+    // move the base and extent nodes to their equivalent leaf positions
+    bool baseAndExtentEqual = m_baseNode == m_extentNode && m_baseOffset == m_extentOffset;
+    if (m_baseNode) {
+        DOMPosition pos = basePosition().equivalentLeafPosition();
+        m_baseNode = pos.node();
+        m_baseOffset = pos.offset();
+        if (baseAndExtentEqual) {
+            m_extentNode = pos.node();
+            m_extentOffset = pos.offset();
+        }
+    }
+    if (m_extentNode && !baseAndExtentEqual) {
+        DOMPosition pos = extentPosition().equivalentLeafPosition();
+        m_extentNode = pos.node();
+        m_extentOffset = pos.offset();
+    }
+
     // make sure we do not have a dangling start or end
        if (!m_baseNode && !m_extentNode) {
         setBaseOffset(0);
@@ -1012,14 +1029,14 @@ void KHTMLSelection::debugPosition() const
         fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
     }
     else {
-        DOMPosition pos = endPosition();
+        DOMPosition pos = startPosition();
         DOMPosition upstream = pos.equivalentUpstreamPosition();
         DOMPosition downstream = pos.equivalentDownstreamPosition();
         fprintf(stderr, "upstream:   %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
         fprintf(stderr, "start:      %s %p:%d\n", getTagName(pos.node()->id()).string().latin1(), pos.node(), pos.offset());
         fprintf(stderr, "downstream: %s %p:%d\n", getTagName(downstream.node()->id()).string().latin1(), downstream.node(), downstream.offset());
         fprintf(stderr, "-----------------------------------\n");
-        pos = startPosition();
+        pos = endPosition();
         upstream = pos.equivalentUpstreamPosition();
         downstream = pos.equivalentDownstreamPosition();
         fprintf(stderr, "upstream:   %s %p:%d\n", getTagName(upstream.node()->id()).string().latin1(), upstream.node(), upstream.offset());
index ef7feeb11b071765eeae93e91bf42d39feccef28..b8118f8f442bee65e25f17f6dd3b0f2f10d574e2 100644 (file)
@@ -272,6 +272,8 @@ public:
     void registerCommandForRedo(const khtml::EditCommand &);
     void clearUndoRedoOperations();
     void editingKeyEvent();
+    void issueUndoCommand();
+    void issueRedoCommand();
     
 private:
     virtual void khtmlMousePressEvent(khtml::MousePressEvent *);
index 9861a8c90bcad7e0472c9033a3c922ca6801ea7b..e8e707dcadbd843f422a79f822cc0c98df42226e 100644 (file)
@@ -2885,3 +2885,12 @@ void KWQKHTMLPart::editingKeyEvent()
     [_bridge editingKeyDown:_currentEvent];
 }
 
+void KWQKHTMLPart::issueUndoCommand()
+{
+    [_bridge issueUndoCommand];
+}
+
+void KWQKHTMLPart::issueRedoCommand()
+{
+    [_bridge issueRedoCommand];
+}
index 94442088823e4f07c001077421bf4950952e5c27..5a3e21d698e6adfc5ccf48c9f61434f25ff92f43 100644 (file)
 
 #include "htmltags.h"
 #include "khtmlview.h"
+#include "khtml_selection.h"
 #include "render_replaced.h"
 #include "render_table.h"
 #include "render_text.h"
 #include "render_canvas.h"
+#include "xml/dom_docimpl.h"
+#include "xml/dom_nodeimpl.h"
+#include "xml/dom_position.h"
 
 #include "KWQKHTMLPart.h"
 #include "KWQTextStream.h"
 
+using DOM::DocumentImpl;
+using DOM::DOMPosition;
+using DOM::NodeImpl;
 using khtml::RenderLayer;
 using khtml::RenderObject;
 using khtml::RenderTableCell;
@@ -318,6 +325,82 @@ static void writeLayers(QTextStream &ts, const RenderLayer* rootLayer, RenderLay
     }
 }
 
+static QString nodePositionRelativeToRoot(NodeImpl *node, NodeImpl *root)
+{
+    QString result;
+
+    NodeImpl *n = node;
+    while (1) {
+        NodeImpl *p = n->parentNode();
+        if (!p || n == root) {
+            result += " of root {" + getTagName(n->id()).string() + "}";
+            break;
+        }
+        if (n != node)
+            result +=  " of ";
+        int count = 1;
+        for (NodeImpl *search = p->firstChild(); search != n; search = search->nextSibling())
+            count++;
+        result +=  "child " + QString::number(count) + " {" + getTagName(n->id()).string() + "}";
+        n = p;
+    }
+    
+    return result;
+}
+
+static void writeSelection(QTextStream &ts, const RenderObject *o)
+{
+    DocumentImpl *doc = dynamic_cast<DocumentImpl *>(o->element());
+    if (!doc || !doc->part())
+        return;
+        
+    KHTMLSelection selection = doc->part()->selection();
+    if (selection.state() == KHTMLSelection::NONE)
+        return;
+
+    if (!selection.startPosition().node()->isContentEditable() || !selection.endPosition().node()->isContentEditable())
+        return;
+
+    DOMPosition startPosition = selection.startPosition();
+    DOMPosition endPosition = selection.endPosition();
+
+    QString startNodeTagName(getTagName(startPosition.node()->id()).string());
+    QString endNodeTagName(getTagName(endPosition.node()->id()).string());
+    
+    NodeImpl *rootNode = doc->getElementById("root");
+    
+    if (selection.state() == KHTMLSelection::CARET) {
+        DOMPosition upstream = startPosition.equivalentUpstreamPosition();
+        DOMPosition downstream = startPosition.equivalentDownstreamPosition();
+        QString positionString = nodePositionRelativeToRoot(startPosition.node(), rootNode);
+        QString upstreamString = nodePositionRelativeToRoot(upstream.node(), rootNode);
+        QString downstreamString = nodePositionRelativeToRoot(downstream.node(), rootNode);
+        ts << "selection is CARET:\n" << 
+            "start:      position " << startPosition.offset() << " of " << positionString << "\n"
+            "upstream:   position " << upstream.offset() << " of " << upstreamString << "\n"
+            "downstream: position " << downstream.offset() << " of " << downstreamString << "\n"; 
+    }
+    else if (selection.state() == KHTMLSelection::RANGE) {
+        QString startString = nodePositionRelativeToRoot(startPosition.node(), rootNode);
+        DOMPosition upstreamStart = startPosition.equivalentUpstreamPosition();
+        QString upstreamStartString = nodePositionRelativeToRoot(upstreamStart.node(), rootNode);
+        DOMPosition downstreamStart = startPosition.equivalentDownstreamPosition();
+        QString downstreamStartString = nodePositionRelativeToRoot(downstreamStart.node(), rootNode);
+        QString endString = nodePositionRelativeToRoot(endPosition.node(), rootNode);
+        DOMPosition upstreamEnd = endPosition.equivalentUpstreamPosition();
+        QString upstreamEndString = nodePositionRelativeToRoot(upstreamEnd.node(), rootNode);
+        DOMPosition downstreamEnd = endPosition.equivalentDownstreamPosition();
+        QString downstreamEndString = nodePositionRelativeToRoot(downstreamEnd.node(), rootNode);
+        ts << "selection is RANGE:\n" <<
+            "start:      position " << startPosition.offset() << " of " << startString << "\n" <<
+            "upstream:   position " << upstreamStart.offset() << " of " << upstreamStartString << "\n"
+            "downstream: position " << downstreamStart.offset() << " of " << downstreamStartString << "\n"
+            "end:        position " << endPosition.offset() << " of " << endString << "\n"
+            "upstream:   position " << upstreamEnd.offset() << " of " << upstreamEndString << "\n"
+            "downstream: position " << downstreamEnd.offset() << " of " << downstreamEndString << "\n"; 
+    }
+}
+
 QString externalRepresentation(RenderObject *o)
 {
     QString s;
@@ -329,8 +412,10 @@ QString externalRepresentation(RenderObject *o)
             o->canvas()->view()->setVScrollBarMode(QScrollView::AlwaysOff);
             o->canvas()->view()->layout();
             RenderLayer* l = o->layer();
-            if (l)
+            if (l) {
                 writeLayers(ts, l, l, QRect(l->xPos(), l->yPos(), l->width(), l->height()));
+                writeSelection(ts, o);
+            }
         }
     }
     return s;
index d67316b23a95911d935e2e4836b961876e93da8b..60fefea9536c979cea1fe3bf7632bb3c673a9da6 100644 (file)
@@ -421,6 +421,8 @@ typedef enum {
 - (void)registerCommandForUndo:(id)arg;
 - (void)registerCommandForRedo:(id)arg;
 - (void)clearUndoRedoOperations;
+- (void)issueUndoCommand;
+- (void)issueRedoCommand;
 
 - (void)editingKeyDown:(NSEvent *)event;
 
index 9282041d1ae37351ddd3b59d5d0aa7215f023c9f..eddb63d484eb6ddf1fbb78075f40c3e81c542a7e 100644 (file)
@@ -1,3 +1,12 @@
+2004-04-06  Ken Kocienda  <kocienda@apple.com>
+
+        Reviewed by Dave
+
+        * WebCoreSupport.subproj/WebBridge.m:
+        (-[WebBridge issueUndoCommand]): New method. Forwards call to the undo manager. Added
+        to support undo called via Javascript execCommand.
+        (-[WebBridge issueRedoCommand]): Ditto.
+
 2004-04-05  Chris Blumenberg  <cblu@apple.com>
 
        Fixed: <rdar://problem/3612580>: SPI: WebPlugin selection
index 330c459e63574df92bedbbf3572747ac5ea0db73..e0981e0c6f0a61e038adb2f9408a6438847f9746 100644 (file)
@@ -1183,6 +1183,20 @@ static id <WebFormDelegate> formDelegate(WebBridge *self)
     [[_frame webView] editingKeyDown:event];
 }
 
+- (void)issueUndoCommand
+{
+    NSUndoManager *undoManager = [[_frame webView] undoManager];
+    if ([undoManager canUndo])
+        [undoManager undo];
+}
+
+- (void)issueRedoCommand
+{
+    NSUndoManager *undoManager = [[_frame webView] undoManager];
+    if ([undoManager canRedo])
+        [undoManager redo];
+}
+
 - (void)setIsSelected:(BOOL)isSelected forView:(NSView *)view
 {
     if ([view conformsToProtocol:@protocol(WebPluginSelection)]) {