+2005-05-23 Maciej Stachowiak <mjs@apple.com>
+
+ Reviewed by John.
+
+ - split remaining editing command classes out of htmlediting.cpp
+ - rename InsertParagraphSeparatorInQuotedContentCommand to BreakBlockquoteCommand
+
+ No layout tests needed - this is a pure refactoring change.
+
+ * WebCore.pbproj/project.pbxproj:
+ * khtml/editing/apply_style_command.cpp:
+ * khtml/editing/break_blockquote_command.cpp: Added.
+ * khtml/editing/break_blockquote_command.h: Added.
+ * khtml/editing/composite_edit_command.cpp:
+ * khtml/editing/delete_selection_command.cpp:
+ * khtml/editing/edit_command.cpp:
+ * khtml/editing/htmlediting.cpp:
+ * khtml/editing/htmlediting.h:
+ * khtml/editing/insert_line_break_command.cpp: Added.
+ * khtml/editing/insert_line_break_command.h: Added.
+ * khtml/editing/insert_paragraph_separator_command.cpp: Added.
+ * khtml/editing/insert_paragraph_separator_command.h: Added.
+ * khtml/editing/insert_text_command.cpp: Added.
+ * khtml/editing/insert_text_command.h: Added.
+ * khtml/editing/join_text_nodes_command.cpp: Added.
+ * khtml/editing/join_text_nodes_command.h: Added.
+ * khtml/editing/merge_identical_elements_command.cpp: Added.
+ * khtml/editing/merge_identical_elements_command.h: Added.
+ * khtml/editing/move_selection_command.cpp: Added.
+ * khtml/editing/move_selection_command.h: Added.
+ * khtml/editing/rebalance_whitespace_command.cpp: Added.
+ * khtml/editing/rebalance_whitespace_command.h: Added.
+ * khtml/editing/remove_css_property_command.cpp: Added.
+ * khtml/editing/remove_css_property_command.h: Added.
+ * khtml/editing/remove_node_attribute_command.cpp: Added.
+ * khtml/editing/remove_node_attribute_command.h: Added.
+ * khtml/editing/remove_node_command.cpp: Added.
+ * khtml/editing/remove_node_command.h: Added.
+ * khtml/editing/remove_node_preserving_children_command.cpp: Added.
+ * khtml/editing/remove_node_preserving_children_command.h: Added.
+ * khtml/editing/replace_selection_command.cpp: Added.
+ * khtml/editing/replace_selection_command.h: Added.
+ * khtml/editing/set_node_attribute_command.cpp: Added.
+ * khtml/editing/set_node_attribute_command.h: Added.
+ * khtml/editing/split_element_command.cpp: Added.
+ * khtml/editing/split_element_command.h: Added.
+ * khtml/editing/split_text_node_command.cpp: Added.
+ * khtml/editing/split_text_node_command.h: Added.
+ * khtml/editing/split_text_node_containing_element_command.h: Added.
+ * khtml/editing/typing_command.cpp: Added.
+ * khtml/editing/typing_command.h: Added.
+ * khtml/editing/wrap_contents_in_dummy_span_command.cpp: Added.
+ * khtml/editing/wrap_contents_in_dummy_span_command.h: Added.
+
2005-05-23 Darin Adler <darin@apple.com>
* WebCore.exp: Sorted file for more readable diffs.
settings = {
};
};
+ 65436035083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = break_blockquote_command.h;
+ path = editing/break_blockquote_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436036083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = insert_line_break_command.h;
+ path = editing/insert_line_break_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436037083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = insert_text_command.h;
+ path = editing/insert_text_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436038083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = join_text_nodes_command.h;
+ path = editing/join_text_nodes_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436039083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = merge_identical_elements_command.h;
+ path = editing/merge_identical_elements_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543603A083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = move_selection_command.h;
+ path = editing/move_selection_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543603B083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = rebalance_whitespace_command.h;
+ path = editing/rebalance_whitespace_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543603C083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = remove_css_property_command.h;
+ path = editing/remove_css_property_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543603D083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = remove_node_attribute_command.h;
+ path = editing/remove_node_attribute_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543603E083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = remove_node_command.h;
+ path = editing/remove_node_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543603F083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = remove_node_preserving_children_command.h;
+ path = editing/remove_node_preserving_children_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436040083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = replace_selection_command.h;
+ path = editing/replace_selection_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436041083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = set_node_attribute_command.h;
+ path = editing/set_node_attribute_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436042083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = split_element_command.h;
+ path = editing/split_element_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436043083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = split_text_node_command.h;
+ path = editing/split_text_node_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436044083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = split_text_node_containing_element_command.h;
+ path = editing/split_text_node_containing_element_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436045083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = typing_command.h;
+ path = editing/typing_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436046083D129100C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = wrap_contents_in_dummy_span_command.h;
+ path = editing/wrap_contents_in_dummy_span_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436047083D129100C20475 = {
+ fileRef = 65436035083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436048083D129100C20475 = {
+ fileRef = 65436036083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436049083D129100C20475 = {
+ fileRef = 65436037083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543604A083D129100C20475 = {
+ fileRef = 65436038083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543604B083D129100C20475 = {
+ fileRef = 65436039083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543604C083D129100C20475 = {
+ fileRef = 6543603A083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543604D083D129100C20475 = {
+ fileRef = 6543603B083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543604E083D129100C20475 = {
+ fileRef = 6543603C083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543604F083D129100C20475 = {
+ fileRef = 6543603D083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436050083D129100C20475 = {
+ fileRef = 6543603E083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436051083D129100C20475 = {
+ fileRef = 6543603F083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436052083D129100C20475 = {
+ fileRef = 65436040083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436053083D129100C20475 = {
+ fileRef = 65436041083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436054083D129100C20475 = {
+ fileRef = 65436042083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436055083D129100C20475 = {
+ fileRef = 65436043083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436056083D129100C20475 = {
+ fileRef = 65436044083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436057083D129100C20475 = {
+ fileRef = 65436045083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436058083D129100C20475 = {
+ fileRef = 65436046083D129100C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543605B083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = break_blockquote_command.cpp;
+ path = editing/break_blockquote_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543605C083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = insert_line_break_command.cpp;
+ path = editing/insert_line_break_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543605D083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = insert_paragraph_separator_command.cpp;
+ path = editing/insert_paragraph_separator_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543605E083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.c.h;
+ name = insert_paragraph_separator_command.h;
+ path = editing/insert_paragraph_separator_command.h;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543605F083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = insert_text_command.cpp;
+ path = editing/insert_text_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436060083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = join_text_nodes_command.cpp;
+ path = editing/join_text_nodes_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436061083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = merge_identical_elements_command.cpp;
+ path = editing/merge_identical_elements_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436062083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = move_selection_command.cpp;
+ path = editing/move_selection_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436063083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = rebalance_whitespace_command.cpp;
+ path = editing/rebalance_whitespace_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436064083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = remove_css_property_command.cpp;
+ path = editing/remove_css_property_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436065083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = remove_node_attribute_command.cpp;
+ path = editing/remove_node_attribute_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436066083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = remove_node_command.cpp;
+ path = editing/remove_node_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436067083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = remove_node_preserving_children_command.cpp;
+ path = editing/remove_node_preserving_children_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436068083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = replace_selection_command.cpp;
+ path = editing/replace_selection_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 65436069083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = set_node_attribute_command.cpp;
+ path = editing/set_node_attribute_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543606A083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = split_element_command.cpp;
+ path = editing/split_element_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543606B083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = split_text_node_command.cpp;
+ path = editing/split_text_node_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543606C083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = split_text_node_containing_element.cpp;
+ path = editing/split_text_node_containing_element.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543606D083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = typing_command.cpp;
+ path = editing/typing_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543606E083DB9FB00C20475 = {
+ fileEncoding = 30;
+ isa = PBXFileReference;
+ lastKnownFileType = sourcecode.cpp.cpp;
+ name = wrap_contents_in_dummy_span_command.cpp;
+ path = editing/wrap_contents_in_dummy_span_command.cpp;
+ refType = 4;
+ sourceTree = "<group>";
+ };
+ 6543606F083DB9FB00C20475 = {
+ fileRef = 6543605B083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436070083DB9FB00C20475 = {
+ fileRef = 6543605C083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436071083DB9FB00C20475 = {
+ fileRef = 6543605D083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436072083DB9FB00C20475 = {
+ fileRef = 6543605E083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436073083DB9FB00C20475 = {
+ fileRef = 6543605F083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436074083DB9FB00C20475 = {
+ fileRef = 65436060083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436075083DB9FC00C20475 = {
+ fileRef = 65436061083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436076083DB9FC00C20475 = {
+ fileRef = 65436062083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436077083DB9FC00C20475 = {
+ fileRef = 65436063083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436078083DB9FC00C20475 = {
+ fileRef = 65436064083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436079083DB9FC00C20475 = {
+ fileRef = 65436065083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543607A083DB9FC00C20475 = {
+ fileRef = 65436066083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543607B083DB9FC00C20475 = {
+ fileRef = 65436067083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543607C083DB9FC00C20475 = {
+ fileRef = 65436068083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543607D083DB9FC00C20475 = {
+ fileRef = 65436069083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543607E083DB9FC00C20475 = {
+ fileRef = 6543606A083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 6543607F083DB9FC00C20475 = {
+ fileRef = 6543606B083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436080083DB9FC00C20475 = {
+ fileRef = 6543606C083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436081083DB9FC00C20475 = {
+ fileRef = 6543606D083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
+ 65436082083DB9FC00C20475 = {
+ fileRef = 6543606E083DB9FB00C20475;
+ isa = PBXBuildFile;
+ settings = {
+ };
+ };
654D87B50831973B0082DCA1 = {
fileEncoding = 30;
isa = PBXFileReference;
652C6E9E08331AE400714969,
652C6EA4083328A700714969,
652C6EA6083328A700714969,
+ 65436047083D129100C20475,
+ 65436048083D129100C20475,
+ 65436049083D129100C20475,
+ 6543604A083D129100C20475,
+ 6543604B083D129100C20475,
+ 6543604C083D129100C20475,
+ 6543604D083D129100C20475,
+ 6543604E083D129100C20475,
+ 6543604F083D129100C20475,
+ 65436050083D129100C20475,
+ 65436051083D129100C20475,
+ 65436052083D129100C20475,
+ 65436053083D129100C20475,
+ 65436054083D129100C20475,
+ 65436055083D129100C20475,
+ 65436056083D129100C20475,
+ 65436057083D129100C20475,
+ 65436058083D129100C20475,
+ 65436072083DB9FB00C20475,
);
isa = PBXHeadersBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
652C6E9D08331AE400714969,
652C6EA3083328A700714969,
652C6EA5083328A700714969,
+ 6543606F083DB9FB00C20475,
+ 65436070083DB9FB00C20475,
+ 65436071083DB9FB00C20475,
+ 65436073083DB9FB00C20475,
+ 65436074083DB9FB00C20475,
+ 65436075083DB9FC00C20475,
+ 65436076083DB9FC00C20475,
+ 65436077083DB9FC00C20475,
+ 65436078083DB9FC00C20475,
+ 65436079083DB9FC00C20475,
+ 6543607A083DB9FC00C20475,
+ 6543607B083DB9FC00C20475,
+ 6543607C083DB9FC00C20475,
+ 6543607D083DB9FC00C20475,
+ 6543607E083DB9FC00C20475,
+ 6543607F083DB9FC00C20475,
+ 65436080083DB9FC00C20475,
+ 65436081083DB9FC00C20475,
+ 65436082083DB9FC00C20475,
);
isa = PBXSourcesBuildPhase;
runOnlyForDeploymentPostprocessing = 0;
};
BEB1DD0805C197DF00DD1F43 = {
children = (
+ 6543605B083DB9FB00C20475,
+ 6543605C083DB9FB00C20475,
+ 6543605D083DB9FB00C20475,
+ 6543605E083DB9FB00C20475,
+ 6543605F083DB9FB00C20475,
+ 65436060083DB9FB00C20475,
+ 65436061083DB9FB00C20475,
+ 65436062083DB9FB00C20475,
+ 65436063083DB9FB00C20475,
+ 65436064083DB9FB00C20475,
+ 65436065083DB9FB00C20475,
+ 65436066083DB9FB00C20475,
+ 65436067083DB9FB00C20475,
+ 65436068083DB9FB00C20475,
+ 65436069083DB9FB00C20475,
+ 6543606A083DB9FB00C20475,
+ 6543606B083DB9FB00C20475,
+ 6543606C083DB9FB00C20475,
+ 6543606D083DB9FB00C20475,
+ 6543606E083DB9FB00C20475,
+ 65436035083D129100C20475,
+ 65436036083D129100C20475,
+ 65436037083D129100C20475,
+ 65436038083D129100C20475,
+ 65436039083D129100C20475,
+ 6543603A083D129100C20475,
+ 6543603B083D129100C20475,
+ 6543603C083D129100C20475,
+ 6543603D083D129100C20475,
+ 6543603E083D129100C20475,
+ 6543603F083D129100C20475,
+ 65436040083D129100C20475,
+ 65436041083D129100C20475,
+ 65436042083D129100C20475,
+ 65436043083D129100C20475,
+ 65436044083D129100C20475,
+ 65436045083D129100C20475,
+ 65436046083D129100C20475,
65AC79A00831ED6D009385CE,
65AC799F0831ED6D009385CE,
65AC79A80831F006009385CE,
}
}
-//------------------------------------------------------------------------------------------
-// ApplyStyleCommand: style-removal helpers
-
bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclarationImpl *style, HTMLElementImpl *elem)
{
QValueListConstIterator<CSSProperty> end;
}
-//------------------------------------------------------------------------------------------
-// ApplyStyleCommand: style-application helpers
-
bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end)
{
if (start.node()->isTextNode() && start.offset() > start.node()->caretMinOffset() && start.offset() < start.node()->caretMaxOffset()) {
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "break_blockquote_command.h"
+
+#include "htmlediting.h"
+#include "visible_position.h"
+
+#include "misc/htmltags.h"
+#include "xml/dom_elementimpl.h"
+#include "xml/dom_textimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::ElementImpl;
+using DOM::NodeImpl;
+using DOM::Position;
+using DOM::TextImpl;
+
+namespace khtml {
+
+BreakBlockquoteCommand::BreakBlockquoteCommand(DocumentImpl *document)
+ : CompositeEditCommand(document), m_breakNode(0)
+{
+}
+
+BreakBlockquoteCommand::~BreakBlockquoteCommand()
+{
+ derefNodesInList(clonedNodes);
+ if (m_breakNode)
+ m_breakNode->deref();
+}
+
+void BreakBlockquoteCommand::doApply()
+{
+ Selection selection = endingSelection();
+ if (selection.isNone())
+ return;
+
+ // Delete the current selection.
+ Position pos = selection.start();
+ EAffinity affinity = selection.startAffinity();
+ if (selection.isRange()) {
+ deleteSelection(false, false);
+ pos = endingSelection().start().upstream();
+ affinity = endingSelection().startAffinity();
+ }
+
+ // Find the top-most blockquote from the start.
+ NodeImpl *startNode = pos.node();
+ NodeImpl *topBlockquote = 0;
+ for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
+ if (isMailBlockquote(n))
+ topBlockquote = n;
+ }
+ if (!topBlockquote || !topBlockquote->parentNode())
+ return;
+
+ // Insert a break after the top blockquote.
+ m_breakNode = createBreakElement(document());
+ m_breakNode->ref();
+ insertNodeAfter(m_breakNode, topBlockquote);
+
+ if (!isLastVisiblePositionInNode(VisiblePosition(pos, affinity), topBlockquote)) {
+
+ NodeImpl *newStartNode = 0;
+ // Split at pos if in the middle of a text node.
+ if (startNode->isTextNode()) {
+ TextImpl *textNode = static_cast<TextImpl *>(startNode);
+ bool atEnd = (unsigned long)pos.offset() >= textNode->length();
+ if (pos.offset() > 0 && !atEnd) {
+ splitTextNode(textNode, pos.offset());
+ pos = Position(startNode, 0);
+ }
+ else if (atEnd) {
+ newStartNode = startNode->traverseNextNode();
+ ASSERT(newStartNode);
+ }
+ }
+ else if (pos.offset() > 0) {
+ newStartNode = startNode->traverseNextNode();
+ ASSERT(newStartNode);
+ }
+
+ // If a new start node was determined, find a new top block quote.
+ if (newStartNode) {
+ startNode = newStartNode;
+ for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
+ if (isMailBlockquote(n))
+ topBlockquote = n;
+ }
+ if (!topBlockquote || !topBlockquote->parentNode())
+ return;
+ }
+
+ // Build up list of ancestors in between the start node and the top blockquote.
+ if (startNode != topBlockquote) {
+ for (NodeImpl *n = startNode->parentNode(); n && n != topBlockquote; n = n->parentNode())
+ ancestors.prepend(n);
+ }
+
+ // Insert a clone of the top blockquote after the break.
+ NodeImpl *clonedBlockquote = topBlockquote->cloneNode(false);
+ clonedBlockquote->ref();
+ clonedNodes.append(clonedBlockquote);
+ insertNodeAfter(clonedBlockquote, m_breakNode);
+
+ // Make clones of ancestors in between the start node and the top blockquote.
+ NodeImpl *parent = clonedBlockquote;
+ for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
+ NodeImpl *child = it.current()->cloneNode(false); // shallow clone
+ child->ref();
+ clonedNodes.append(child);
+ appendNode(child, parent);
+ parent = child;
+ }
+
+ // Move the start node and the siblings of the start node.
+ bool startIsBR = false;
+ if (startNode != topBlockquote) {
+ NodeImpl *n = startNode;
+ startIsBR = n->id() == ID_BR;
+ if (startIsBR)
+ n = n->nextSibling();
+ while (n) {
+ NodeImpl *next = n->nextSibling();
+ removeNode(n);
+ appendNode(n, parent);
+ n = next;
+ }
+ }
+
+ // Move everything after the start node.
+ NodeImpl *leftParent = ancestors.last();
+
+ // Insert an extra new line when the start is at the beginning of a line.
+ if (!newStartNode && !startIsBR) {
+ if (!leftParent)
+ leftParent = topBlockquote;
+ ElementImpl *b = createBreakElement(document());
+ b->ref();
+ clonedNodes.append(b);
+ appendNode(b, leftParent);
+ }
+
+ leftParent = ancestors.last();
+ while (leftParent && leftParent != topBlockquote) {
+ parent = parent->parentNode();
+ NodeImpl *n = leftParent->nextSibling();
+ while (n) {
+ NodeImpl *next = n->nextSibling();
+ removeNode(n);
+ appendNode(n, parent);
+ n = next;
+ }
+ leftParent = leftParent->parentNode();
+ }
+
+ // Make sure the cloned block quote renders.
+ addBlockPlaceholderIfNeeded(clonedBlockquote);
+ }
+
+ // Put the selection right before the break.
+ setEndingSelection(Position(m_breakNode, 0), DOWNSTREAM);
+ rebalanceWhitespace();
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __break_blockquote_command_h__
+#define __break_blockquote_command_h__
+
+#include "composite_edit_command.h"
+#include "qptrlist.h"
+
+namespace khtml {
+
+class BreakBlockquoteCommand : public CompositeEditCommand
+{
+public:
+ BreakBlockquoteCommand(DOM::DocumentImpl *);
+ virtual ~BreakBlockquoteCommand();
+
+ virtual void doApply();
+
+private:
+ QPtrList<DOM::NodeImpl> ancestors;
+ QPtrList<DOM::NodeImpl> clonedNodes;
+ DOM::ElementImpl *m_breakNode;
+};
+
+} // namespace khtml
+
+#endif // __break_blockquote_command_h__
#include "append_node_command.h"
#include "delete_from_text_node_command.h"
+#include "htmlediting.h"
#include "insert_into_text_node_command.h"
#include "insert_node_before_command.h"
-#include "htmlediting.h"
+#include "insert_paragraph_separator_command.h"
+#include "insert_text_command.h"
+#include "join_text_nodes_command.h"
+#include "merge_identical_elements_command.h"
+#include "rebalance_whitespace_command.h"
+#include "remove_css_property_command.h"
+#include "remove_node_attribute_command.h"
+#include "remove_node_command.h"
+#include "remove_node_preserving_children_command.h"
+#include "set_node_attribute_command.h"
+#include "split_element_command.h"
+#include "split_text_node_command.h"
+#include "split_text_node_containing_element_command.h"
#include "visible_units.h"
+#include "wrap_contents_in_dummy_span_command.h"
#include "misc/htmlattrs.h"
#include "misc/htmltags.h"
static const DOMString &blockPlaceholderClassString();
-//------------------------------------------------------------------------------------------
-// CompositeEditCommand
-
CompositeEditCommand::CompositeEditCommand(DocumentImpl *document)
: EditCommand(document)
{
}
}
-static DOMString &nonBreakingSpaceString()
-{
- static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
- return nonBreakingSpaceString;
-}
-
// FIXME: Can't really determine this without taking white-space mode into account.
static inline bool nextCharacterIsCollapsibleWhitespace(const Position &pos)
{
namespace khtml {
-//------------------------------------------------------------------------------------------
-// EditCommandPtr
-
EditCommandPtr::EditCommandPtr()
{
}
return m_emptyCommand;
}
-//------------------------------------------------------------------------------------------
-// EditCommand
-
EditCommand::EditCommand(DocumentImpl *document)
: m_document(document), m_state(NotApplied), m_typingStyle(0), m_parent(0)
{
namespace khtml {
-static inline bool isNBSP(const QChar &c)
-{
- return c.unicode() == 0xa0;
-}
-
-static const int spacesPerTab = 4;
-
bool isTableStructureNode(const NodeImpl *node)
{
RenderObject *r = node->renderer();
return (r && (r->isTableCell() || r->isTableRow() || r->isTableSection() || r->isTableCol()));
}
-static DOMString &nonBreakingSpaceString()
+DOMString &nonBreakingSpaceString()
{
static DOMString nonBreakingSpaceString = QString(QChar(0xa0));
return nonBreakingSpaceString;
}
-static DOMString &matchNearestBlockquoteColorString()
-{
- static DOMString matchNearestBlockquoteColorString = "match";
- return matchNearestBlockquoteColorString;
-}
-
-static void derefNodesInList(QPtrList<NodeImpl> &list)
+void derefNodesInList(QPtrList<NodeImpl> &list)
{
for (QPtrListIterator<NodeImpl> it(list); it.current(); ++it)
it.current()->deref();
return 1;
}
-static bool isSpecialElement(NodeImpl *n)
+bool isSpecialElement(NodeImpl *n)
{
if (!n->isHTMLElement())
return false;
return false;
}
-// This version of the function is meant to be called on positions in a document fragment,
-// so it does not check for a root editable element, it is assumed these nodes will be put
-// somewhere editable in the future
-bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos)
-{
- VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
-
- for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
- if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
- return false;
- if (isSpecialElement(n))
- return true;
- }
-
- return false;
-}
-
bool isFirstVisiblePositionInSpecialElement(const Position& pos)
{
VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
return pos;
}
-//------------------------------------------------------------------------------------------
-// InsertLineBreakCommand
-
-InsertLineBreakCommand::InsertLineBreakCommand(DocumentImpl *document)
- : CompositeEditCommand(document)
-{
-}
-
-bool InsertLineBreakCommand::preservesTypingStyle() const
-{
- return true;
-}
-
-void InsertLineBreakCommand::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
-{
- // Insert the BR after the caret position. In the case the
- // position is a block, do an append. We don't want to insert
- // the BR *after* the block.
- Position upstream(pos.upstream());
- NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
- if (cb == pos.node())
- appendNode(node, cb);
- else
- insertNodeAfter(node, pos.node());
-}
-
-void InsertLineBreakCommand::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
-{
- // Insert the BR after the caret position. In the case the
- // position is a block, do an append. We don't want to insert
- // the BR *before* the block.
- Position upstream(pos.upstream());
- NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
- if (cb == pos.node())
- appendNode(node, cb);
- else
- insertNodeBefore(node, pos.node());
-}
-
-void InsertLineBreakCommand::doApply()
-{
- deleteSelection();
- Selection selection = endingSelection();
-
- ElementImpl *breakNode = createBreakElement(document());
- NodeImpl *nodeToInsert = breakNode;
-
- Position pos(selection.start().upstream());
-
- pos = positionOutsideContainingSpecialElement(pos);
-
- bool atStart = pos.offset() <= pos.node()->caretMinOffset();
- bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
- bool atEndOfBlock = isEndOfBlock(VisiblePosition(pos, selection.startAffinity()));
-
- if (atEndOfBlock) {
- LOG(Editing, "input newline case 1");
- // Check for a trailing BR. If there isn't one, we'll need to insert an "extra" one.
- // This makes the "real" BR we want to insert appear in the rendering without any
- // significant side effects (and no real worries either since you can't arrow past
- // this extra one.
- if (pos.node()->id() == ID_BR && pos.offset() == 0) {
- // Already placed in a trailing BR. Insert "real" BR before it and leave the selection alone.
- insertNodeBefore(nodeToInsert, pos.node());
- }
- else {
- NodeImpl *next = pos.node()->traverseNextNode();
- bool hasTrailingBR = next && next->id() == ID_BR && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
- insertNodeAfterPosition(nodeToInsert, pos);
- if (hasTrailingBR) {
- setEndingSelection(Selection(Position(next, 0), DOWNSTREAM));
- }
- else if (!document()->inStrictMode()) {
- // Insert an "extra" BR at the end of the block.
- ElementImpl *extraBreakNode = createBreakElement(document());
- insertNodeAfter(extraBreakNode, nodeToInsert);
- setEndingSelection(Position(extraBreakNode, 0), DOWNSTREAM);
- }
- }
- }
- else if (atStart) {
- LOG(Editing, "input newline case 2");
- // Insert node before downstream position, and place caret there as well.
- Position endingPosition = pos.downstream();
- insertNodeBeforePosition(nodeToInsert, endingPosition);
- setEndingSelection(endingPosition, DOWNSTREAM);
- }
- else if (atEnd) {
- LOG(Editing, "input newline case 3");
- // Insert BR after this node. Place caret in the position that is downstream
- // of the current position, reckoned before inserting the BR in between.
- Position endingPosition = pos.downstream();
- insertNodeAfterPosition(nodeToInsert, pos);
- setEndingSelection(endingPosition, DOWNSTREAM);
- }
- else {
- // Split a text node
- LOG(Editing, "input newline case 4");
- ASSERT(pos.node()->isTextNode());
-
- // Do the split
- int exceptionCode = 0;
- TextImpl *textNode = static_cast<TextImpl *>(pos.node());
- TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
- deleteTextFromNode(textNode, 0, pos.offset());
- insertNodeBefore(textBeforeNode, textNode);
- insertNodeBefore(nodeToInsert, textNode);
- Position endingPosition = Position(textNode, 0);
-
- // Handle whitespace that occurs after the split
- document()->updateLayout();
- if (!endingPosition.isRenderedCharacter()) {
- // Clear out all whitespace and insert one non-breaking space
- deleteInsignificantTextDownstream(endingPosition);
- insertTextIntoNode(textNode, 0, nonBreakingSpaceString());
- }
-
- setEndingSelection(endingPosition, DOWNSTREAM);
- }
-
- // Handle the case where there is a typing style.
- // FIXME: Improve typing style.
- // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
-
- CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
-
- if (typingStyle && typingStyle->length() > 0) {
- Selection selectionBeforeStyle = endingSelection();
-
- DOM::RangeImpl *rangeAroundNode = document()->createRange();
- int exception = 0;
- rangeAroundNode->selectNode(nodeToInsert, exception);
-
- // affinity is not really important since this is a temp selection
- // just for calling applyStyle
- setEndingSelection(Selection(rangeAroundNode, khtml::SEL_DEFAULT_AFFINITY, khtml::SEL_DEFAULT_AFFINITY));
- applyStyle(typingStyle);
-
- setEndingSelection(selectionBeforeStyle);
- }
-
- rebalanceWhitespace();
-}
-
-//------------------------------------------------------------------------------------------
-// InsertParagraphSeparatorCommand
-
-InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(DocumentImpl *document)
- : CompositeEditCommand(document), m_style(0)
-{
-}
-
-InsertParagraphSeparatorCommand::~InsertParagraphSeparatorCommand()
-{
- derefNodesInList(clonedNodes);
- if (m_style)
- m_style->deref();
-}
-
-bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
-{
- return true;
-}
-
-ElementImpl *InsertParagraphSeparatorCommand::createParagraphElement()
-{
- ElementImpl *element = createDefaultParagraphElement(document());
- element->ref();
- clonedNodes.append(element);
- return element;
-}
-
-void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
-{
- // It is only important to set a style to apply later if we're at the boundaries of
- // a paragraph. Otherwise, content that is moved as part of the work of the command
- // will lend their styles to the new paragraph without any extra work needed.
- VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
- if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos))
- return;
-
- if (m_style)
- m_style->deref();
- m_style = styleAtPosition(pos);
- m_style->ref();
-}
-
-void InsertParagraphSeparatorCommand::applyStyleAfterInsertion()
-{
- // FIXME: Improve typing style.
- // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
- if (!m_style)
- return;
-
- CSSComputedStyleDeclarationImpl endingStyle(endingSelection().start().node());
- endingStyle.diff(m_style);
- if (m_style->length() > 0) {
- applyStyle(m_style);
- }
-}
-
-void InsertParagraphSeparatorCommand::doApply()
-{
- bool splitText = false;
- Selection selection = endingSelection();
- if (selection.isNone())
- return;
-
- Position pos = selection.start();
- EAffinity affinity = selection.startAffinity();
-
- // Delete the current selection.
- if (selection.isRange()) {
- calculateStyleBeforeInsertion(pos);
- deleteSelection(false, false);
- pos = endingSelection().start();
- affinity = endingSelection().startAffinity();
- }
-
- pos = positionOutsideContainingSpecialElement(pos);
-
- calculateStyleBeforeInsertion(pos);
-
- // Find the start block.
- NodeImpl *startNode = pos.node();
- NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
- if (!startBlock || !startBlock->parentNode())
- return;
-
- VisiblePosition visiblePos(pos, affinity);
- bool isFirstInBlock = isStartOfBlock(visiblePos);
- bool isLastInBlock = isEndOfBlock(visiblePos);
- bool startBlockIsRoot = startBlock == startBlock->rootEditableElement();
-
- // This is the block that is going to be inserted.
- NodeImpl *blockToInsert = startBlockIsRoot ? createParagraphElement() : startBlock->cloneNode(false);
-
- //---------------------------------------------------------------------
- // Handle empty block case.
- if (isFirstInBlock && isLastInBlock) {
- LOG(Editing, "insert paragraph separator: empty block case");
- if (startBlockIsRoot) {
- NodeImpl *extraBlock = createParagraphElement();
- appendNode(extraBlock, startBlock);
- appendBlockPlaceholder(extraBlock);
- appendNode(blockToInsert, startBlock);
- }
- else {
- insertNodeAfter(blockToInsert, startBlock);
- }
- appendBlockPlaceholder(blockToInsert);
- setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
- applyStyleAfterInsertion();
- return;
- }
-
- //---------------------------------------------------------------------
- // Handle case when position is in the last visible position in its block.
- if (isLastInBlock) {
- LOG(Editing, "insert paragraph separator: last in block case");
- if (startBlockIsRoot)
- appendNode(blockToInsert, startBlock);
- else
- insertNodeAfter(blockToInsert, startBlock);
- appendBlockPlaceholder(blockToInsert);
- setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
- applyStyleAfterInsertion();
- return;
- }
-
- //---------------------------------------------------------------------
- // Handle case when position is in the first visible position in its block.
- // and similar case where upstream position is in another block.
- bool prevInDifferentBlock = !inSameBlock(visiblePos, visiblePos.previous());
-
- if (prevInDifferentBlock || isFirstInBlock) {
- LOG(Editing, "insert paragraph separator: first in block case");
- pos = pos.downstream();
- pos = positionOutsideContainingSpecialElement(pos);
- Position refPos;
- NodeImpl *refNode;
- if (isFirstInBlock && !startBlockIsRoot) {
- refNode = startBlock;
- } else if (pos.node() == startBlock && startBlockIsRoot) {
- ASSERT(startBlock->childNode(pos.offset())); // must be true or we'd be in the end of block case
- refNode = startBlock->childNode(pos.offset());
- } else {
- refNode = pos.node();
- }
-
- insertNodeBefore(blockToInsert, refNode);
- appendBlockPlaceholder(blockToInsert);
- setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
- applyStyleAfterInsertion();
- setEndingSelection(pos, DOWNSTREAM);
- return;
- }
-
- //---------------------------------------------------------------------
- // Handle the (more complicated) general case,
-
- LOG(Editing, "insert paragraph separator: general case");
-
- // Check if pos.node() is a <br>. If it is, and the document is in quirks mode,
- // then this <br> will collapse away when we add a block after it. Add an extra <br>.
- if (!document()->inStrictMode()) {
- Position upstreamPos = pos.upstream();
- if (upstreamPos.node()->id() == ID_BR)
- insertNodeAfter(createBreakElement(document()), upstreamPos.node());
- }
-
- // Move downstream. Typing style code will take care of carrying along the
- // style of the upstream position.
- pos = pos.downstream();
- startNode = pos.node();
-
- // Build up list of ancestors in between the start node and the start block.
- if (startNode != startBlock) {
- for (NodeImpl *n = startNode->parentNode(); n && n != startBlock; n = n->parentNode())
- ancestors.prepend(n);
- }
-
- // Make sure we do not cause a rendered space to become unrendered.
- // FIXME: We need the affinity for pos, but pos.downstream() does not give it
- Position leadingWhitespace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
- if (leadingWhitespace.isNotNull()) {
- TextImpl *textNode = static_cast<TextImpl *>(leadingWhitespace.node());
- replaceTextInNode(textNode, leadingWhitespace.offset(), 1, nonBreakingSpaceString());
- }
-
- // Split at pos if in the middle of a text node.
- if (startNode->isTextNode()) {
- TextImpl *textNode = static_cast<TextImpl *>(startNode);
- bool atEnd = (unsigned long)pos.offset() >= textNode->length();
- if (pos.offset() > 0 && !atEnd) {
- splitTextNode(textNode, pos.offset());
- pos = Position(startNode, 0);
- splitText = true;
- }
- }
-
- // Put the added block in the tree.
- if (startBlockIsRoot) {
- appendNode(blockToInsert, startBlock);
- } else {
- insertNodeAfter(blockToInsert, startBlock);
- }
-
- // Make clones of ancestors in between the start node and the start block.
- NodeImpl *parent = blockToInsert;
- for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
- NodeImpl *child = it.current()->cloneNode(false); // shallow clone
- child->ref();
- clonedNodes.append(child);
- appendNode(child, parent);
- parent = child;
- }
-
- // Insert a block placeholder if the next visible position is in a different paragraph,
- // because we know that there will be no content on the first line of the new block
- // before the first block child. So, we need the placeholder to "hold the first line open".
- VisiblePosition next = visiblePos.next();
- if (!next.isNull() && !inSameBlock(visiblePos, next))
- appendBlockPlaceholder(blockToInsert);
-
- // Move the start node and the siblings of the start node.
- if (startNode != startBlock) {
- NodeImpl *n = startNode;
- if (pos.offset() >= startNode->caretMaxOffset()) {
- n = startNode->nextSibling();
- }
- while (n && n != blockToInsert) {
- NodeImpl *next = n->nextSibling();
- removeNode(n);
- appendNode(n, parent);
- n = next;
- }
- }
-
- // Move everything after the start node.
- NodeImpl *leftParent = ancestors.last();
- while (leftParent && leftParent != startBlock) {
- parent = parent->parentNode();
- NodeImpl *n = leftParent->nextSibling();
- while (n && n != blockToInsert) {
- NodeImpl *next = n->nextSibling();
- removeNode(n);
- appendNode(n, parent);
- n = next;
- }
- leftParent = leftParent->parentNode();
- }
-
- // Handle whitespace that occurs after the split
- if (splitText) {
- document()->updateLayout();
- pos = Position(startNode, 0);
- if (!pos.isRenderedCharacter()) {
- // Clear out all whitespace and insert one non-breaking space
- ASSERT(startNode && startNode->isTextNode());
- deleteInsignificantTextDownstream(pos);
- insertTextIntoNode(static_cast<TextImpl *>(startNode), 0, nonBreakingSpaceString());
- }
- }
-
- setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
- rebalanceWhitespace();
- applyStyleAfterInsertion();
-}
-
-//------------------------------------------------------------------------------------------
-// InsertParagraphSeparatorInQuotedContentCommand
-
-InsertParagraphSeparatorInQuotedContentCommand::InsertParagraphSeparatorInQuotedContentCommand(DocumentImpl *document)
- : CompositeEditCommand(document), m_breakNode(0)
-{
-}
-
-InsertParagraphSeparatorInQuotedContentCommand::~InsertParagraphSeparatorInQuotedContentCommand()
-{
- derefNodesInList(clonedNodes);
- if (m_breakNode)
- m_breakNode->deref();
-}
-
-void InsertParagraphSeparatorInQuotedContentCommand::doApply()
-{
- Selection selection = endingSelection();
- if (selection.isNone())
- return;
-
- // Delete the current selection.
- Position pos = selection.start();
- EAffinity affinity = selection.startAffinity();
- if (selection.isRange()) {
- deleteSelection(false, false);
- pos = endingSelection().start().upstream();
- affinity = endingSelection().startAffinity();
- }
-
- // Find the top-most blockquote from the start.
- NodeImpl *startNode = pos.node();
- NodeImpl *topBlockquote = 0;
- for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
- if (isMailBlockquote(n))
- topBlockquote = n;
- }
- if (!topBlockquote || !topBlockquote->parentNode())
- return;
-
- // Insert a break after the top blockquote.
- m_breakNode = createBreakElement(document());
- m_breakNode->ref();
- insertNodeAfter(m_breakNode, topBlockquote);
-
- if (!isLastVisiblePositionInNode(VisiblePosition(pos, affinity), topBlockquote)) {
-
- NodeImpl *newStartNode = 0;
- // Split at pos if in the middle of a text node.
- if (startNode->isTextNode()) {
- TextImpl *textNode = static_cast<TextImpl *>(startNode);
- bool atEnd = (unsigned long)pos.offset() >= textNode->length();
- if (pos.offset() > 0 && !atEnd) {
- splitTextNode(textNode, pos.offset());
- pos = Position(startNode, 0);
- }
- else if (atEnd) {
- newStartNode = startNode->traverseNextNode();
- ASSERT(newStartNode);
- }
- }
- else if (pos.offset() > 0) {
- newStartNode = startNode->traverseNextNode();
- ASSERT(newStartNode);
- }
-
- // If a new start node was determined, find a new top block quote.
- if (newStartNode) {
- startNode = newStartNode;
- for (NodeImpl *n = startNode->parentNode(); n; n = n->parentNode()) {
- if (isMailBlockquote(n))
- topBlockquote = n;
- }
- if (!topBlockquote || !topBlockquote->parentNode())
- return;
- }
-
- // Build up list of ancestors in between the start node and the top blockquote.
- if (startNode != topBlockquote) {
- for (NodeImpl *n = startNode->parentNode(); n && n != topBlockquote; n = n->parentNode())
- ancestors.prepend(n);
- }
-
- // Insert a clone of the top blockquote after the break.
- NodeImpl *clonedBlockquote = topBlockquote->cloneNode(false);
- clonedBlockquote->ref();
- clonedNodes.append(clonedBlockquote);
- insertNodeAfter(clonedBlockquote, m_breakNode);
-
- // Make clones of ancestors in between the start node and the top blockquote.
- NodeImpl *parent = clonedBlockquote;
- for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
- NodeImpl *child = it.current()->cloneNode(false); // shallow clone
- child->ref();
- clonedNodes.append(child);
- appendNode(child, parent);
- parent = child;
- }
-
- // Move the start node and the siblings of the start node.
- bool startIsBR = false;
- if (startNode != topBlockquote) {
- NodeImpl *n = startNode;
- startIsBR = n->id() == ID_BR;
- if (startIsBR)
- n = n->nextSibling();
- while (n) {
- NodeImpl *next = n->nextSibling();
- removeNode(n);
- appendNode(n, parent);
- n = next;
- }
- }
-
- // Move everything after the start node.
- NodeImpl *leftParent = ancestors.last();
-
- // Insert an extra new line when the start is at the beginning of a line.
- if (!newStartNode && !startIsBR) {
- if (!leftParent)
- leftParent = topBlockquote;
- ElementImpl *b = createBreakElement(document());
- b->ref();
- clonedNodes.append(b);
- appendNode(b, leftParent);
- }
-
- leftParent = ancestors.last();
- while (leftParent && leftParent != topBlockquote) {
- parent = parent->parentNode();
- NodeImpl *n = leftParent->nextSibling();
- while (n) {
- NodeImpl *next = n->nextSibling();
- removeNode(n);
- appendNode(n, parent);
- n = next;
- }
- leftParent = leftParent->parentNode();
- }
-
- // Make sure the cloned block quote renders.
- addBlockPlaceholderIfNeeded(clonedBlockquote);
- }
-
- // Put the selection right before the break.
- setEndingSelection(Position(m_breakNode, 0), DOWNSTREAM);
- rebalanceWhitespace();
-}
-
-//------------------------------------------------------------------------------------------
-// InsertTextCommand
-
-InsertTextCommand::InsertTextCommand(DocumentImpl *document)
- : CompositeEditCommand(document), m_charactersAdded(0)
-{
-}
-
-void InsertTextCommand::doApply()
-{
-}
-
-Position InsertTextCommand::prepareForTextInsertion(bool adjustDownstream)
-{
- // Prepare for text input by looking at the current position.
- // It may be necessary to insert a text node to receive characters.
- Selection selection = endingSelection();
- ASSERT(selection.isCaret());
-
- Position pos = selection.start();
- if (adjustDownstream)
- pos = pos.downstream();
- else
- pos = pos.upstream();
-
- Selection typingStyleRange;
-
- pos = positionOutsideContainingSpecialElement(pos);
-
- if (!pos.node()->isTextNode()) {
- NodeImpl *textNode = document()->createEditingTextNode("");
- NodeImpl *nodeToInsert = textNode;
-
- // Now insert the node in the right place
- if (pos.node()->rootEditableElement() != NULL) {
- LOG(Editing, "prepareForTextInsertion case 1");
- insertNodeAt(nodeToInsert, pos.node(), pos.offset());
- }
- else if (pos.node()->caretMinOffset() == pos.offset()) {
- LOG(Editing, "prepareForTextInsertion case 2");
- insertNodeBefore(nodeToInsert, pos.node());
- }
- else if (pos.node()->caretMaxOffset() == pos.offset()) {
- LOG(Editing, "prepareForTextInsertion case 3");
- insertNodeAfter(nodeToInsert, pos.node());
- }
- else
- ASSERT_NOT_REACHED();
-
- pos = Position(textNode, 0);
- }
-
- return pos;
-}
-
-void InsertTextCommand::input(const DOMString &text, bool selectInsertedText)
-{
- assert(text.find('\n') == -1);
-
- Selection selection = endingSelection();
- bool adjustDownstream = isStartOfLine(VisiblePosition(selection.start().downstream(), DOWNSTREAM));
-
- // Delete the current selection, or collapse whitespace, as needed
- if (selection.isRange())
- deleteSelection();
-
- // Delete any insignificant text that could get in the way of whitespace turning
- // out correctly after the insertion.
- selection = endingSelection();
- deleteInsignificantTextDownstream(selection.end().trailingWhitespacePosition(selection.endAffinity()));
-
- // Make sure the document is set up to receive text
- Position startPosition = prepareForTextInsertion(adjustDownstream);
-
- Position endPosition;
-
- TextImpl *textNode = static_cast<TextImpl *>(startPosition.node());
- long offset = startPosition.offset();
-
- // Now that we are about to add content, check to see if a placeholder element
- // can be removed.
- removeBlockPlaceholder(textNode->enclosingBlockFlowElement());
-
- // These are temporary implementations for inserting adjoining spaces
- // into a document. We are working on a CSS-related whitespace solution
- // that will replace this some day. We hope.
- if (text == "\t") {
- // Treat a tab like a number of spaces. This seems to be the HTML editing convention,
- // although the number of spaces varies (we choose four spaces).
- // Note that there is no attempt to make this work like a real tab stop, it is merely
- // a set number of spaces. This also seems to be the HTML editing convention.
- for (int i = 0; i < spacesPerTab; i++) {
- insertSpace(textNode, offset);
- rebalanceWhitespace();
- document()->updateLayout();
- }
-
- endPosition = Position(textNode, offset + spacesPerTab);
-
- m_charactersAdded += spacesPerTab;
- }
- else if (text == " ") {
- insertSpace(textNode, offset);
- endPosition = Position(textNode, offset + 1);
-
- m_charactersAdded++;
- rebalanceWhitespace();
- }
- else {
- const DOMString &existingText = textNode->data();
- if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isCollapsibleWhitespace(existingText[offset - 2])) {
- // DOM looks like this:
- // character nbsp caret
- // As we are about to insert a non-whitespace character at the caret
- // convert the nbsp to a regular space.
- // EDIT FIXME: This needs to be improved some day to convert back only
- // those nbsp's added by the editor to make rendering come out right.
- replaceTextInNode(textNode, offset - 1, 1, " ");
- }
- insertTextIntoNode(textNode, offset, text);
- endPosition = Position(textNode, offset + text.length());
-
- m_charactersAdded += text.length();
- }
-
- setEndingSelection(Selection(startPosition, DOWNSTREAM, endPosition, SEL_DEFAULT_AFFINITY));
-
- // Handle the case where there is a typing style.
- // FIXME: Improve typing style.
- // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
- CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
- if (typingStyle && typingStyle->length() > 0)
- applyStyle(typingStyle);
-
- if (!selectInsertedText)
- setEndingSelection(endingSelection().end(), endingSelection().endAffinity());
-}
-
-void InsertTextCommand::insertSpace(TextImpl *textNode, unsigned long offset)
-{
- ASSERT(textNode);
-
- DOMString text(textNode->data());
-
- // count up all spaces and newlines in front of the caret
- // delete all collapsed ones
- // this will work out OK since the offset we have been passed has been upstream-ized
- int count = 0;
- for (unsigned int i = offset; i < text.length(); i++) {
- if (isCollapsibleWhitespace(text[i]))
- count++;
- else
- break;
- }
- if (count > 0) {
- // By checking the character at the downstream position, we can
- // check if there is a rendered WS at the caret
- Position pos(textNode, offset);
- Position downstream = pos.downstream();
- if (downstream.offset() < (long)text.length() && isCollapsibleWhitespace(text[downstream.offset()]))
- count--; // leave this WS in
- if (count > 0)
- deleteTextFromNode(textNode, offset, count);
- }
-
- if (offset > 0 && offset <= text.length() - 1 && !isCollapsibleWhitespace(text[offset]) && !isCollapsibleWhitespace(text[offset - 1])) {
- // insert a "regular" space
- insertTextIntoNode(textNode, offset, " ");
- return;
- }
-
- if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
- // DOM looks like this:
- // nbsp nbsp caret
- // insert a space between the two nbsps
- insertTextIntoNode(textNode, offset - 1, " ");
- return;
- }
-
- // insert an nbsp
- insertTextIntoNode(textNode, offset, nonBreakingSpaceString());
-}
-
-bool InsertTextCommand::isInsertTextCommand() const
-{
- return true;
-}
-
-//------------------------------------------------------------------------------------------
-// JoinTextNodesCommand
-
-JoinTextNodesCommand::JoinTextNodesCommand(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
- : EditCommand(document), m_text1(text1), m_text2(text2)
-{
- ASSERT(m_text1);
- ASSERT(m_text2);
- ASSERT(m_text1->nextSibling() == m_text2);
- ASSERT(m_text1->length() > 0);
- ASSERT(m_text2->length() > 0);
-
- m_text1->ref();
- m_text2->ref();
-}
-
-JoinTextNodesCommand::~JoinTextNodesCommand()
-{
- ASSERT(m_text1);
- m_text1->deref();
- ASSERT(m_text2);
- m_text2->deref();
-}
-
-void JoinTextNodesCommand::doApply()
+ElementImpl *createDefaultParagraphElement(DocumentImpl *document)
{
- ASSERT(m_text1);
- ASSERT(m_text2);
- ASSERT(m_text1->nextSibling() == m_text2);
-
+ // We would need this margin-zeroing code back if we ever return to using <p> elements for default paragraphs.
+ // static const DOMString defaultParagraphStyle("margin-top: 0; margin-bottom: 0");
int exceptionCode = 0;
- m_text2->insertData(0, m_text1->data(), exceptionCode);
- ASSERT(exceptionCode == 0);
-
- m_text2->parentNode()->removeChild(m_text1, exceptionCode);
+ ElementImpl *element = document->createHTMLElement("div", exceptionCode);
ASSERT(exceptionCode == 0);
-
- m_offset = m_text1->length();
+ return element;
}
-void JoinTextNodesCommand::doUnapply()
+ElementImpl *createBreakElement(DocumentImpl *document)
{
- ASSERT(m_text2);
- ASSERT(m_offset > 0);
-
int exceptionCode = 0;
-
- m_text2->deleteData(0, m_offset, exceptionCode);
- ASSERT(exceptionCode == 0);
-
- m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
+ ElementImpl *breakNode = document->createHTMLElement("br", exceptionCode);
ASSERT(exceptionCode == 0);
-
- ASSERT(m_text2->previousSibling()->isTextNode());
- ASSERT(m_text2->previousSibling() == m_text1);
-}
-
-//------------------------------------------------------------------------------------------
-// MoveSelectionCommand
-
-MoveSelectionCommand::MoveSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, Position &position, bool smartMove)
- : CompositeEditCommand(document), m_fragment(fragment), m_position(position), m_smartMove(smartMove)
-{
- ASSERT(m_fragment);
- m_fragment->ref();
-}
-
-MoveSelectionCommand::~MoveSelectionCommand()
-{
- ASSERT(m_fragment);
- m_fragment->deref();
+ return breakNode;
}
-void MoveSelectionCommand::doApply()
+bool isNodeRendered(const NodeImpl *node)
{
- Selection selection = endingSelection();
- ASSERT(selection.isRange());
-
- Position pos = m_position;
- if (pos.isNull())
- return;
-
- // Update the position otherwise it may become invalid after the selection is deleted.
- NodeImpl *positionNode = m_position.node();
- long positionOffset = m_position.offset();
- Position selectionEnd = selection.end();
- long selectionEndOffset = selectionEnd.offset();
- if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) {
- positionOffset -= selectionEndOffset;
- Position selectionStart = selection.start();
- if (selectionStart.node() == positionNode) {
- positionOffset += selectionStart.offset();
- }
- pos = Position(positionNode, positionOffset);
- }
-
- deleteSelection(m_smartMove);
+ if (!node)
+ return false;
- // If the node for the destination has been removed as a result of the deletion,
- // set the destination to the ending point after the deletion.
- // Fixes: <rdar://problem/3910425> REGRESSION (Mail): Crash in ReplaceSelectionCommand;
- // selection is empty, leading to null deref
- if (!pos.node()->inDocument())
- pos = endingSelection().start();
+ RenderObject *renderer = node->renderer();
+ if (!renderer)
+ return false;
- setEndingSelection(pos, endingSelection().startAffinity());
- EditCommandPtr cmd(new ReplaceSelectionCommand(document(), m_fragment, true, m_smartMove));
- applyCommandToComposite(cmd);
+ return renderer->style()->visibility() == VISIBLE;
}
-EditAction MoveSelectionCommand::editingAction() const
+NodeImpl *nearestMailBlockquote(const NodeImpl *node)
{
- return EditActionDrag;
+ for (NodeImpl *n = const_cast<NodeImpl *>(node); n; n = n->parentNode()) {
+ if (isMailBlockquote(n))
+ return n;
+ }
+ return 0;
}
-//------------------------------------------------------------------------------------------
-// RebalanceWhitespaceCommand
-
-RebalanceWhitespaceCommand::RebalanceWhitespaceCommand(DocumentImpl *document, const Position &pos)
- : EditCommand(document), m_position(pos), m_upstreamOffset(InvalidOffset), m_downstreamOffset(InvalidOffset)
-{
-}
-
-RebalanceWhitespaceCommand::~RebalanceWhitespaceCommand()
-{
-}
-
-void RebalanceWhitespaceCommand::doApply()
-{
- static DOMString space(" ");
-
- if (m_position.isNull() || !m_position.node()->isTextNode())
- return;
-
- TextImpl *textNode = static_cast<TextImpl *>(m_position.node());
- DOMString text = textNode->data();
- if (text.length() == 0)
- return;
-
- // find upstream offset
- long upstream = m_position.offset();
- while (upstream > 0 && isCollapsibleWhitespace(text[upstream - 1]) || isNBSP(text[upstream - 1])) {
- upstream--;
- m_upstreamOffset = upstream;
- }
-
- // find downstream offset
- long downstream = m_position.offset();
- while ((unsigned)downstream < text.length() && isCollapsibleWhitespace(text[downstream]) || isNBSP(text[downstream])) {
- downstream++;
- m_downstreamOffset = downstream;
- }
-
- if (m_upstreamOffset == InvalidOffset && m_downstreamOffset == InvalidOffset)
- return;
-
- m_upstreamOffset = upstream;
- m_downstreamOffset = downstream;
- long length = m_downstreamOffset - m_upstreamOffset;
-
- m_beforeString = text.substring(m_upstreamOffset, length);
-
- // The following loop figures out a "rebalanced" whitespace string for any length
- // string, and takes into account the special cases that need to handled for the
- // start and end of strings (i.e. first and last character must be an nbsp.
- long i = m_upstreamOffset;
- while (i < m_downstreamOffset) {
- long add = (m_downstreamOffset - i) % 3;
- switch (add) {
- case 0:
- m_afterString += nonBreakingSpaceString();
- m_afterString += space;
- m_afterString += nonBreakingSpaceString();
- add = 3;
- break;
- case 1:
- if (i == 0 || (unsigned)i + 1 == text.length()) // at start or end of string
- m_afterString += nonBreakingSpaceString();
- else
- m_afterString += space;
- break;
- case 2:
- if ((unsigned)i + 2 == text.length()) {
- // at end of string
- m_afterString += nonBreakingSpaceString();
- m_afterString += nonBreakingSpaceString();
- }
- else {
- m_afterString += nonBreakingSpaceString();
- m_afterString += space;
- }
- break;
- }
- i += add;
- }
-
- text.remove(m_upstreamOffset, length);
- text.insert(m_afterString, m_upstreamOffset);
-}
-
-void RebalanceWhitespaceCommand::doUnapply()
-{
- if (m_upstreamOffset == InvalidOffset && m_downstreamOffset == InvalidOffset)
- return;
-
- ASSERT(m_position.node()->isTextNode());
- TextImpl *textNode = static_cast<TextImpl *>(m_position.node());
- DOMString text = textNode->data();
- text.remove(m_upstreamOffset, m_afterString.length());
- text.insert(m_beforeString, m_upstreamOffset);
-}
-
-bool RebalanceWhitespaceCommand::preservesTypingStyle() const
-{
- return true;
-}
-
-//------------------------------------------------------------------------------------------
-// RemoveCSSPropertyCommand
-
-RemoveCSSPropertyCommand::RemoveCSSPropertyCommand(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property)
- : EditCommand(document), m_decl(decl->makeMutable()), m_property(property), m_important(false)
-{
- ASSERT(m_decl);
- m_decl->ref();
-}
-
-RemoveCSSPropertyCommand::~RemoveCSSPropertyCommand()
-{
- ASSERT(m_decl);
- m_decl->deref();
-}
-
-void RemoveCSSPropertyCommand::doApply()
-{
- ASSERT(m_decl);
-
- m_oldValue = m_decl->getPropertyValue(m_property);
- ASSERT(!m_oldValue.isNull());
-
- m_important = m_decl->getPropertyPriority(m_property);
- m_decl->removeProperty(m_property);
-}
-
-void RemoveCSSPropertyCommand::doUnapply()
-{
- ASSERT(m_decl);
- ASSERT(!m_oldValue.isNull());
-
- m_decl->setProperty(m_property, m_oldValue, m_important);
-}
-
-//------------------------------------------------------------------------------------------
-// RemoveNodeAttributeCommand
-
-RemoveNodeAttributeCommand::RemoveNodeAttributeCommand(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute)
- : EditCommand(document), m_element(element), m_attribute(attribute)
-{
- ASSERT(m_element);
- m_element->ref();
-}
-
-RemoveNodeAttributeCommand::~RemoveNodeAttributeCommand()
-{
- ASSERT(m_element);
- m_element->deref();
-}
-
-void RemoveNodeAttributeCommand::doApply()
-{
- ASSERT(m_element);
-
- m_oldValue = m_element->getAttribute(m_attribute);
- ASSERT(!m_oldValue.isNull());
-
- int exceptionCode = 0;
- m_element->removeAttribute(m_attribute, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-void RemoveNodeAttributeCommand::doUnapply()
-{
- ASSERT(m_element);
- ASSERT(!m_oldValue.isNull());
-
- int exceptionCode = 0;
- m_element->setAttribute(m_attribute, m_oldValue.implementation(), exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-//------------------------------------------------------------------------------------------
-// RemoveNodeCommand
-
-RemoveNodeCommand::RemoveNodeCommand(DocumentImpl *document, NodeImpl *removeChild)
- : EditCommand(document), m_parent(0), m_removeChild(removeChild), m_refChild(0)
-{
- ASSERT(m_removeChild);
- m_removeChild->ref();
-
- m_parent = m_removeChild->parentNode();
- ASSERT(m_parent);
- m_parent->ref();
-
- m_refChild = m_removeChild->nextSibling();
- if (m_refChild)
- m_refChild->ref();
-}
-
-RemoveNodeCommand::~RemoveNodeCommand()
-{
- ASSERT(m_parent);
- m_parent->deref();
-
- ASSERT(m_removeChild);
- m_removeChild->deref();
-
- if (m_refChild)
- m_refChild->deref();
-}
-
-void RemoveNodeCommand::doApply()
-{
- ASSERT(m_parent);
- ASSERT(m_removeChild);
-
- int exceptionCode = 0;
- m_parent->removeChild(m_removeChild, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-void RemoveNodeCommand::doUnapply()
-{
- ASSERT(m_parent);
- ASSERT(m_removeChild);
-
- int exceptionCode = 0;
- m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-//------------------------------------------------------------------------------------------
-// RemoveNodePreservingChildrenCommand
-
-RemoveNodePreservingChildrenCommand::RemoveNodePreservingChildrenCommand(DocumentImpl *document, NodeImpl *node)
- : CompositeEditCommand(document), m_node(node)
-{
- ASSERT(m_node);
- m_node->ref();
-}
-
-RemoveNodePreservingChildrenCommand::~RemoveNodePreservingChildrenCommand()
-{
- ASSERT(m_node);
- m_node->deref();
-}
-
-void RemoveNodePreservingChildrenCommand::doApply()
-{
- while (NodeImpl* curr = node()->firstChild()) {
- removeNode(curr);
- insertNodeBefore(curr, node());
- }
- removeNode(node());
-}
-
-//------------------------------------------------------------------------------------------
-// ReplaceSelectionCommand
-
-ReplacementFragment::ReplacementFragment(DocumentImpl *document, DocumentFragmentImpl *fragment, bool matchStyle)
- : m_document(document),
- m_fragment(fragment),
- m_matchStyle(matchStyle),
- m_hasInterchangeNewlineAtStart(false),
- m_hasInterchangeNewlineAtEnd(false),
- m_hasMoreThanOneBlock(false)
-{
- if (!m_document)
- return;
-
- if (!m_fragment) {
- m_type = EmptyFragment;
- return;
- }
-
- m_document->ref();
- m_fragment->ref();
-
- NodeImpl *firstChild = m_fragment->firstChild();
- NodeImpl *lastChild = m_fragment->lastChild();
-
- if (!firstChild) {
- m_type = EmptyFragment;
- return;
- }
-
- if (firstChild == lastChild && firstChild->isTextNode()) {
- m_type = SingleTextNodeFragment;
- return;
- }
-
- m_type = TreeFragment;
-
- NodeImpl *node = m_fragment->firstChild();
- NodeImpl *newlineAtStartNode = 0;
- NodeImpl *newlineAtEndNode = 0;
- while (node) {
- NodeImpl *next = node->traverseNextNode();
- if (isInterchangeNewlineNode(node)) {
- if (next || node == m_fragment->firstChild()) {
- m_hasInterchangeNewlineAtStart = true;
- newlineAtStartNode = node;
- }
- else {
- m_hasInterchangeNewlineAtEnd = true;
- newlineAtEndNode = node;
- }
- }
- else if (isInterchangeConvertedSpaceSpan(node)) {
- NodeImpl *n = 0;
- while ((n = node->firstChild())) {
- n->ref();
- removeNode(n);
- insertNodeBefore(n, node);
- n->deref();
- }
- removeNode(node);
- if (n)
- next = n->traverseNextNode();
- }
- node = next;
- }
-
- if (newlineAtStartNode)
- removeNode(newlineAtStartNode);
- if (newlineAtEndNode)
- removeNode(newlineAtEndNode);
-
- NodeImpl *holder = insertFragmentForTestRendering();
- if (holder)
- holder->ref();
- if (!m_matchStyle) {
- computeStylesUsingTestRendering(holder);
- }
- removeUnrenderedNodesUsingTestRendering(holder);
- m_hasMoreThanOneBlock = countRenderedBlocks(holder) > 1;
- restoreTestRenderingNodesToFragment(holder);
- removeNode(holder);
- holder->deref();
- removeStyleNodes();
-}
-
-ReplacementFragment::~ReplacementFragment()
-{
- if (m_document)
- m_document->deref();
- if (m_fragment)
- m_fragment->deref();
-}
-
-NodeImpl *ReplacementFragment::firstChild() const
-{
- return m_fragment->firstChild();
-}
-
-NodeImpl *ReplacementFragment::lastChild() const
-{
- return m_fragment->lastChild();
-}
-
-NodeImpl *ReplacementFragment::mergeStartNode() const
-{
- NodeImpl *node = m_fragment->firstChild();
- while (node && isProbablyBlock(node) && !isMailPasteAsQuotationNode(node))
- node = node->traverseNextNode();
- return node;
-}
-
-void ReplacementFragment::pruneEmptyNodes()
-{
- bool run = true;
- while (run) {
- run = false;
- NodeImpl *node = m_fragment->firstChild();
- while (node) {
- if ((node->isTextNode() && static_cast<TextImpl *>(node)->length() == 0) ||
- (isProbablyBlock(node) && !isProbablyTableStructureNode(node) && node->childNodeCount() == 0)) {
- NodeImpl *next = node->traverseNextSibling();
- removeNode(node);
- node = next;
- run = true;
- }
- else {
- node = node->traverseNextNode();
- }
- }
- }
-}
-
-bool ReplacementFragment::isInterchangeNewlineNode(const NodeImpl *node)
-{
- static DOMString interchangeNewlineClassString(AppleInterchangeNewline);
- return node && node->id() == ID_BR && static_cast<const ElementImpl *>(node)->getAttribute(ATTR_CLASS) == interchangeNewlineClassString;
-}
-
-bool ReplacementFragment::isInterchangeConvertedSpaceSpan(const NodeImpl *node)
-{
- static DOMString convertedSpaceSpanClassString(AppleConvertedSpace);
- return node->isHTMLElement() && static_cast<const HTMLElementImpl *>(node)->getAttribute(ATTR_CLASS) == convertedSpaceSpanClassString;
-}
-
-NodeImpl *ReplacementFragment::enclosingBlock(NodeImpl *node) const
-{
- while (node && !isProbablyBlock(node))
- node = node->parentNode();
- return node ? node : m_fragment;
-}
-
-void ReplacementFragment::removeNodePreservingChildren(NodeImpl *node)
-{
- if (!node)
- return;
-
- while (NodeImpl *n = node->firstChild()) {
- n->ref();
- removeNode(n);
- insertNodeBefore(n, node);
- n->deref();
- }
- removeNode(node);
-}
-
-void ReplacementFragment::removeNode(NodeImpl *node)
-{
- if (!node)
- return;
-
- NodeImpl *parent = node->parentNode();
- if (!parent)
- return;
-
- int exceptionCode = 0;
- parent->removeChild(node, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-void ReplacementFragment::insertNodeBefore(NodeImpl *node, NodeImpl *refNode)
-{
- if (!node || !refNode)
- return;
-
- NodeImpl *parent = refNode->parentNode();
- if (!parent)
- return;
-
- int exceptionCode = 0;
- parent->insertBefore(node, refNode, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-NodeImpl *ReplacementFragment::insertFragmentForTestRendering()
-{
- NodeImpl *body = m_document->body();
- if (!body)
- return 0;
-
- ElementImpl *holder = createDefaultParagraphElement(m_document);
- holder->ref();
-
- int exceptionCode = 0;
- holder->appendChild(m_fragment, exceptionCode);
- ASSERT(exceptionCode == 0);
-
- body->appendChild(holder, exceptionCode);
- ASSERT(exceptionCode == 0);
- holder->deref();
-
- m_document->updateLayout();
-
- return holder;
-}
-
-void ReplacementFragment::restoreTestRenderingNodesToFragment(NodeImpl *holder)
-{
- if (!holder)
- return;
-
- int exceptionCode = 0;
- while (NodeImpl *node = holder->firstChild()) {
- node->ref();
- holder->removeChild(node, exceptionCode);
- ASSERT(exceptionCode == 0);
- m_fragment->appendChild(node, exceptionCode);
- ASSERT(exceptionCode == 0);
- node->deref();
- }
-}
-
-void ReplacementFragment::computeStylesUsingTestRendering(NodeImpl *holder)
-{
- if (!holder)
- return;
-
- m_document->updateLayout();
-
- for (NodeImpl *node = holder->firstChild(); node; node = node->traverseNextNode(holder))
- computeAndStoreNodeDesiredStyle(node, m_styles);
-}
-
-void ReplacementFragment::removeUnrenderedNodesUsingTestRendering(NodeImpl *holder)
-{
- if (!holder)
- return;
-
- QPtrList<NodeImpl> unrendered;
-
- for (NodeImpl *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
- if (!isNodeRendered(node) && !isTableStructureNode(node))
- unrendered.append(node);
- }
-
- for (QPtrListIterator<NodeImpl> it(unrendered); it.current(); ++it)
- removeNode(it.current());
-}
-
-int ReplacementFragment::countRenderedBlocks(NodeImpl *holder)
-{
- if (!holder)
- return 0;
-
- int count = 0;
- NodeImpl *prev = 0;
- for (NodeImpl *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
- if (node->isBlockFlow()) {
- if (!prev) {
- count++;
- prev = node;
- }
- }
- else {
- NodeImpl *block = node->enclosingBlockFlowElement();
- if (block != prev) {
- count++;
- prev = block;
- }
- }
- }
-
- return count;
-}
-
-void ReplacementFragment::removeStyleNodes()
-{
- // Since style information has been computed and cached away in
- // computeStylesForNodes(), these style nodes can be removed, since
- // the correct styles will be added back in fixupNodeStyles().
- NodeImpl *node = m_fragment->firstChild();
- while (node) {
- NodeImpl *next = node->traverseNextNode();
- // This list of tags change the appearance of content
- // in ways we can add back on later with CSS, if necessary.
- if (node->id() == ID_B ||
- node->id() == ID_BIG ||
- node->id() == ID_CENTER ||
- node->id() == ID_FONT ||
- node->id() == ID_I ||
- node->id() == ID_S ||
- node->id() == ID_SMALL ||
- node->id() == ID_STRIKE ||
- node->id() == ID_SUB ||
- node->id() == ID_SUP ||
- node->id() == ID_TT ||
- node->id() == ID_U ||
- isStyleSpan(node)) {
- removeNodePreservingChildren(node);
- }
- else if (node->isHTMLElement()) {
- HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
- CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
- if (inlineStyleDecl) {
- inlineStyleDecl->removeBlockProperties();
- inlineStyleDecl->removeInheritableProperties();
- }
- }
- node = next;
- }
-}
-
-NodeDesiredStyle::NodeDesiredStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style)
- : m_node(node), m_style(style)
-{
- if (m_node)
- m_node->ref();
- if (m_style)
- m_style->ref();
-}
-
-NodeDesiredStyle::NodeDesiredStyle(const NodeDesiredStyle &other)
- : m_node(other.node()), m_style(other.style())
-{
- if (m_node)
- m_node->ref();
- if (m_style)
- m_style->ref();
-}
-
-NodeDesiredStyle::~NodeDesiredStyle()
-{
- if (m_node)
- m_node->deref();
- if (m_style)
- m_style->deref();
-}
-
-NodeDesiredStyle &NodeDesiredStyle::operator=(const NodeDesiredStyle &other)
-{
- NodeImpl *oldNode = m_node;
- CSSMutableStyleDeclarationImpl *oldStyle = m_style;
-
- m_node = other.node();
- m_style = other.style();
-
- if (m_node)
- m_node->ref();
- if (m_style)
- m_style->ref();
-
- if (oldNode)
- oldNode->deref();
- if (oldStyle)
- oldStyle->deref();
-
- return *this;
-}
-
-ReplaceSelectionCommand::ReplaceSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, bool selectReplacement, bool smartReplace, bool matchStyle)
- : CompositeEditCommand(document),
- m_fragment(document, fragment, matchStyle),
- m_firstNodeInserted(0),
- m_lastNodeInserted(0),
- m_lastTopNodeInserted(0),
- m_insertionStyle(0),
- m_selectReplacement(selectReplacement),
- m_smartReplace(smartReplace),
- m_matchStyle(matchStyle)
-{
-}
-
-ReplaceSelectionCommand::~ReplaceSelectionCommand()
-{
- if (m_firstNodeInserted)
- m_firstNodeInserted->deref();
- if (m_lastNodeInserted)
- m_lastNodeInserted->deref();
- if (m_lastTopNodeInserted)
- m_lastTopNodeInserted->deref();
- if (m_insertionStyle)
- m_insertionStyle->deref();
-}
-
-void ReplaceSelectionCommand::doApply()
-{
- // collect information about the current selection, prior to deleting the selection
- Selection selection = endingSelection();
- ASSERT(selection.isCaretOrRange());
-
- VisiblePosition visibleStart(selection.start(), selection.startAffinity());
- VisiblePosition visibleEnd(selection.end(), selection.endAffinity());
- bool startAtStartOfBlock = isStartOfBlock(visibleStart);
- bool startAtEndOfBlock = isEndOfBlock(visibleStart);
- bool startAtBlockBoundary = startAtStartOfBlock || startAtEndOfBlock;
- NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement();
- NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement();
-
- // decide whether to later merge content into the startBlock
- bool mergeStart = false;
- if (startBlock == startBlock->rootEditableElement() && startAtStartOfBlock && startAtEndOfBlock) {
- // empty editable subtree, need to mergeStart so that fragment ends up
- // inside the editable subtree rather than just before it
- mergeStart = false;
- } else {
- // merge if current selection starts inside a paragraph, or there is only one block and no interchange newline to add
- mergeStart = !m_fragment.hasInterchangeNewlineAtStart() &&
- (!isStartOfParagraph(visibleStart) || (!m_fragment.hasInterchangeNewlineAtEnd() && !m_fragment.hasMoreThanOneBlock())) &&
- !isLastVisiblePositionInSpecialElement(selection.start());
-
- // This is a workaround for this bug:
- // <rdar://problem/4013642> REGRESSION (Mail): Copied quoted word does not paste as a quote if pasted at the start of a line
- // We need more powerful logic in this whole mergeStart code for this case to come out right without
- // breaking other cases.
- if (isStartOfParagraph(visibleStart) && isMailBlockquote(m_fragment.firstChild()))
- mergeStart = false;
- }
-
- // decide whether to later append nodes to the end
- NodeImpl *beyondEndNode = 0;
- if (!isEndOfParagraph(visibleEnd) && !m_fragment.hasInterchangeNewlineAtEnd()) {
- Position beyondEndPos = selection.end().downstream();
- if (!isFirstVisiblePositionInSpecialElement(beyondEndPos))
- beyondEndNode = beyondEndPos.node();
- }
- bool moveNodesAfterEnd = beyondEndNode && (startBlock != endBlock || m_fragment.hasMoreThanOneBlock());
-
- Position startPos = selection.start();
-
- // delete the current range selection, or insert paragraph for caret selection, as needed
- if (selection.isRange()) {
- deleteSelection(false, !(m_fragment.hasInterchangeNewlineAtStart() || m_fragment.hasInterchangeNewlineAtEnd() || m_fragment.hasMoreThanOneBlock()));
- document()->updateLayout();
- visibleStart = VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY);
- if (m_fragment.hasInterchangeNewlineAtStart()) {
- if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
- if (!isEndOfDocument(visibleStart))
- setEndingSelection(visibleStart.next());
- }
- else {
- insertParagraphSeparator();
- setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY));
- }
- }
- startPos = endingSelection().start();
- }
- else {
- ASSERT(selection.isCaret());
- if (m_fragment.hasInterchangeNewlineAtStart()) {
- if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
- if (!isEndOfDocument(visibleStart))
- setEndingSelection(visibleStart.next());
- }
- else {
- insertParagraphSeparator();
- setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY));
- }
- }
- if (!m_fragment.hasInterchangeNewlineAtEnd() && m_fragment.hasMoreThanOneBlock() &&
- !startAtBlockBoundary && !isEndOfParagraph(visibleEnd)) {
- // The start and the end need to wind up in separate blocks.
- // Insert a paragraph separator to make that happen.
- insertParagraphSeparator();
- setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY).previous());
- }
- startPos = endingSelection().start();
- }
-
- if (startAtStartOfBlock && startBlock->inDocument())
- startPos = Position(startBlock, 0);
-
- startPos = positionOutsideContainingSpecialElement(startPos);
-
- KHTMLPart *part = document()->part();
- if (m_matchStyle) {
- m_insertionStyle = styleAtPosition(startPos);
- m_insertionStyle->ref();
- }
-
- // FIXME: Improve typing style.
- // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
- part->clearTypingStyle();
- setTypingStyle(0);
-
- // done if there is nothing to add
- if (!m_fragment.firstChild())
- return;
-
- // check for a line placeholder, and store it away for possible removal later.
- NodeImpl *block = startPos.node()->enclosingBlockFlowElement();
- NodeImpl *linePlaceholder = findBlockPlaceholder(block);
- if (!linePlaceholder) {
- Position downstream = startPos.downstream();
- downstream = positionOutsideContainingSpecialElement(downstream);
- if (downstream.node()->id() == ID_BR && downstream.offset() == 0 &&
- m_fragment.hasInterchangeNewlineAtEnd() &&
- isStartOfLine(VisiblePosition(downstream, VP_DEFAULT_AFFINITY)))
- linePlaceholder = downstream.node();
- }
-
- // check whether to "smart replace" needs to add leading and/or trailing space
- bool addLeadingSpace = false;
- bool addTrailingSpace = false;
- // FIXME: We need the affinity for startPos and endPos, but Position::downstream
- // and Position::upstream do not give it
- if (m_smartReplace) {
- VisiblePosition visiblePos = VisiblePosition(startPos, VP_DEFAULT_AFFINITY);
- assert(visiblePos.isNotNull());
- addLeadingSpace = startPos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isStartOfLine(visiblePos);
- if (addLeadingSpace) {
- QChar previousChar = visiblePos.previous().character();
- if (!previousChar.isNull()) {
- addLeadingSpace = !part->isCharacterSmartReplaceExempt(previousChar, true);
- }
- }
- addTrailingSpace = startPos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isEndOfLine(visiblePos);
- if (addTrailingSpace) {
- QChar thisChar = visiblePos.character();
- if (!thisChar.isNull()) {
- addTrailingSpace = !part->isCharacterSmartReplaceExempt(thisChar, false);
- }
- }
- }
-
- // There are five steps to adding the content: merge blocks at start, add remaining blocks,
- // add "smart replace" space, handle trailing newline, clean up.
-
- // initially, we say the insertion point is the start of selection
- document()->updateLayout();
- Position insertionPos = startPos;
-
- // step 1: merge content into the start block, if that is needed
- if (mergeStart && !isFirstVisiblePositionInSpecialElementInFragment(Position(m_fragment.mergeStartNode(), 0))) {
- NodeImpl *refNode = m_fragment.mergeStartNode();
- if (refNode) {
- NodeImpl *node = refNode ? refNode->nextSibling() : 0;
- insertNodeAtAndUpdateNodesInserted(refNode, startPos.node(), startPos.offset());
- while (node && !isProbablyBlock(node)) {
- NodeImpl *next = node->nextSibling();
- insertNodeAfterAndUpdateNodesInserted(node, refNode);
- refNode = node;
- node = next;
- }
- }
-
- // update insertion point to be at the end of the last block inserted
- if (m_lastNodeInserted) {
- document()->updateLayout();
- insertionPos = Position(m_lastNodeInserted, m_lastNodeInserted->caretMaxOffset());
- }
- }
-
- // prune empty nodes from fragment
- // NOTE: why was this not done earlier, before the mergeStart?
- m_fragment.pruneEmptyNodes();
-
- // step 2 : merge everything remaining in the fragment
- if (m_fragment.firstChild()) {
- NodeImpl *refNode = m_fragment.firstChild();
- NodeImpl *node = refNode ? refNode->nextSibling() : 0;
- NodeImpl *insertionBlock = insertionPos.node()->enclosingBlockFlowElement();
- bool insertionBlockIsRoot = insertionBlock == insertionBlock->rootEditableElement();
- VisiblePosition visiblePos(insertionPos, DOWNSTREAM);
- if (!insertionBlockIsRoot && isProbablyBlock(refNode) && isStartOfBlock(visiblePos))
- insertNodeBeforeAndUpdateNodesInserted(refNode, insertionBlock);
- else if (!insertionBlockIsRoot && isProbablyBlock(refNode) && isEndOfBlock(visiblePos)) {
- insertNodeAfterAndUpdateNodesInserted(refNode, insertionBlock);
- } else if (mergeStart && !isProbablyBlock(refNode)) {
- Position pos = visiblePos.next().deepEquivalent().downstream();
- insertNodeAtAndUpdateNodesInserted(refNode, pos.node(), pos.offset());
- } else {
- insertNodeAtAndUpdateNodesInserted(refNode, insertionPos.node(), insertionPos.offset());
- }
-
- while (node) {
- NodeImpl *next = node->nextSibling();
- insertNodeAfterAndUpdateNodesInserted(node, refNode);
- refNode = node;
- node = next;
- }
- document()->updateLayout();
- insertionPos = Position(m_lastNodeInserted, m_lastNodeInserted->caretMaxOffset());
- }
-
- // step 3 : handle "smart replace" whitespace
- if (addTrailingSpace && m_lastNodeInserted) {
- document()->updateLayout();
- Position pos(m_lastNodeInserted, m_lastNodeInserted->caretMaxOffset());
- bool needsTrailingSpace = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull();
- if (needsTrailingSpace) {
- if (m_lastNodeInserted->isTextNode()) {
- TextImpl *text = static_cast<TextImpl *>(m_lastNodeInserted);
- insertTextIntoNode(text, text->length(), nonBreakingSpaceString());
- insertionPos = Position(text, text->length());
- }
- else {
- NodeImpl *node = document()->createEditingTextNode(nonBreakingSpaceString());
- insertNodeAfterAndUpdateNodesInserted(node, m_lastNodeInserted);
- insertionPos = Position(node, 1);
- }
- }
- }
-
- if (addLeadingSpace && m_firstNodeInserted) {
- document()->updateLayout();
- Position pos(m_firstNodeInserted, 0);
- bool needsLeadingSpace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull();
- if (needsLeadingSpace) {
- if (m_firstNodeInserted->isTextNode()) {
- TextImpl *text = static_cast<TextImpl *>(m_firstNodeInserted);
- insertTextIntoNode(text, 0, nonBreakingSpaceString());
- } else {
- NodeImpl *node = document()->createEditingTextNode(nonBreakingSpaceString());
- insertNodeBeforeAndUpdateNodesInserted(node, m_firstNodeInserted);
- }
- }
- }
-
- Position lastPositionToSelect;
-
- // step 4 : handle trailing newline
- if (m_fragment.hasInterchangeNewlineAtEnd()) {
- removeLinePlaceholderIfNeeded(linePlaceholder);
-
- if (!m_lastNodeInserted) {
- lastPositionToSelect = endingSelection().end().downstream();
- }
- else {
- bool insertParagraph = false;
- VisiblePosition pos(insertionPos, VP_DEFAULT_AFFINITY);
-
- if (startBlock == endBlock && !isProbablyBlock(m_lastTopNodeInserted)) {
- insertParagraph = true;
- } else {
- // Handle end-of-document case.
- document()->updateLayout();
- if (isEndOfDocument(pos))
- insertParagraph = true;
- }
- if (insertParagraph) {
- setEndingSelection(insertionPos, DOWNSTREAM);
- insertParagraphSeparator();
- VisiblePosition next = pos.next();
-
- // Select up to the paragraph separator that was added.
- lastPositionToSelect = next.deepEquivalent().downstream();
- updateNodesInserted(lastPositionToSelect.node());
- } else {
- // Select up to the preexising paragraph separator.
- VisiblePosition next = pos.next();
- lastPositionToSelect = next.deepEquivalent().downstream();
- }
- }
- }
- else {
- if (m_lastNodeInserted && m_lastNodeInserted->id() == ID_BR && !document()->inStrictMode()) {
- document()->updateLayout();
- VisiblePosition pos(Position(m_lastNodeInserted, 1), DOWNSTREAM);
- if (isEndOfBlock(pos)) {
- NodeImpl *next = m_lastNodeInserted->traverseNextNode();
- bool hasTrailingBR = next && next->id() == ID_BR && m_lastNodeInserted->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
- if (!hasTrailingBR) {
- // Insert an "extra" BR at the end of the block.
- insertNodeBefore(createBreakElement(document()), m_lastNodeInserted);
- }
- }
- }
-
- if (moveNodesAfterEnd && !isLastVisiblePositionInSpecialElement(Position(m_lastNodeInserted, maxRangeOffset(m_lastNodeInserted)))) {
- document()->updateLayout();
- QValueList<NodeDesiredStyle> styles;
- QPtrList<NodeImpl> blocks;
- NodeImpl *node = beyondEndNode;
- NodeImpl *refNode = m_lastNodeInserted;
- while (node) {
- RenderObject *renderer = node->renderer();
- // Stop at the first table or block.
- if (renderer && (renderer->isBlockFlow() || renderer->isTable()))
- break;
- NodeImpl *next = node->nextSibling();
- blocks.append(node->enclosingBlockFlowElement());
- computeAndStoreNodeDesiredStyle(node, styles);
- removeNode(node);
- // No need to update inserted node variables.
- insertNodeAfter(node, refNode);
- refNode = node;
- // We want to move the first BR we see, so check for that here.
- if (node->id() == ID_BR)
- break;
- node = next;
- }
- document()->updateLayout();
- for (QPtrListIterator<NodeImpl> it(blocks); it.current(); ++it) {
- NodeImpl *blockToRemove = it.current();
- if (!blockToRemove->inDocument())
- continue;
- if (!blockToRemove->renderer() || !blockToRemove->renderer()->firstChild()) {
- if (blockToRemove->parentNode())
- blocks.append(blockToRemove->parentNode()->enclosingBlockFlowElement());
- removeNode(blockToRemove);
- document()->updateLayout();
- }
- }
-
- fixupNodeStyles(styles);
- }
- }
-
- if (!m_matchStyle)
- fixupNodeStyles(m_fragment.desiredStyles());
- completeHTMLReplacement(lastPositionToSelect);
-
- // step 5 : mop up
- removeLinePlaceholderIfNeeded(linePlaceholder);
-}
-
-void ReplaceSelectionCommand::removeLinePlaceholderIfNeeded(NodeImpl *linePlaceholder)
-{
- if (!linePlaceholder)
- return;
-
- document()->updateLayout();
- if (linePlaceholder->inDocument()) {
- VisiblePosition placeholderPos(linePlaceholder, linePlaceholder->renderer()->caretMinOffset(), DOWNSTREAM);
- if (placeholderPos.next().isNull() ||
- !(isStartOfLine(placeholderPos) && isEndOfLine(placeholderPos))) {
- NodeImpl *block = linePlaceholder->enclosingBlockFlowElement();
- removeNode(linePlaceholder);
- document()->updateLayout();
- if (!block->renderer() || block->renderer()->height() == 0)
- removeNode(block);
- }
- }
-}
-
-void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
-{
- Position start;
- Position end;
-
- if (m_firstNodeInserted && m_firstNodeInserted->inDocument() &&
- m_lastNodeInserted && m_lastNodeInserted->inDocument()) {
-
- // Find the last leaf.
- NodeImpl *lastLeaf = m_lastNodeInserted;
- while (1) {
- NodeImpl *nextChild = lastLeaf->lastChild();
- if (!nextChild)
- break;
- lastLeaf = nextChild;
- }
-
- // Find the first leaf.
- NodeImpl *firstLeaf = m_firstNodeInserted;
- while (1) {
- NodeImpl *nextChild = firstLeaf->firstChild();
- if (!nextChild)
- break;
- firstLeaf = nextChild;
- }
-
- // Call updateLayout so caretMinOffset and caretMaxOffset return correct values.
- document()->updateLayout();
- start = Position(firstLeaf, firstLeaf->caretMinOffset());
- end = Position(lastLeaf, lastLeaf->caretMaxOffset());
-
- if (m_matchStyle) {
- assert(m_insertionStyle);
- setEndingSelection(Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY));
- applyStyle(m_insertionStyle);
- }
-
- if (lastPositionToSelect.isNotNull())
- end = lastPositionToSelect;
- }
- else if (lastPositionToSelect.isNotNull()) {
- start = end = lastPositionToSelect;
- }
- else {
- return;
- }
-
- if (m_selectReplacement)
- setEndingSelection(Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY));
- else
- setEndingSelection(end, SEL_DEFAULT_AFFINITY);
-
- rebalanceWhitespace();
-}
-
-EditAction ReplaceSelectionCommand::editingAction() const
-{
- return EditActionPaste;
-}
-
-void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(NodeImpl *insertChild, NodeImpl *refChild)
-{
- insertNodeAfter(insertChild, refChild);
- updateNodesInserted(insertChild);
-}
-
-void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(NodeImpl *insertChild, NodeImpl *refChild, long offset)
-{
- insertNodeAt(insertChild, refChild, offset);
- updateNodesInserted(insertChild);
-}
-
-void ReplaceSelectionCommand::insertNodeBeforeAndUpdateNodesInserted(NodeImpl *insertChild, NodeImpl *refChild)
-{
- insertNodeBefore(insertChild, refChild);
- updateNodesInserted(insertChild);
-}
-
-void ReplaceSelectionCommand::updateNodesInserted(NodeImpl *node)
-{
- if (!node)
- return;
-
- // update m_lastTopNodeInserted
- node->ref();
- if (m_lastTopNodeInserted)
- m_lastTopNodeInserted->deref();
- m_lastTopNodeInserted = node;
-
- // update m_firstNodeInserted
- if (!m_firstNodeInserted) {
- m_firstNodeInserted = node;
- m_firstNodeInserted->ref();
- }
-
- if (node == m_lastNodeInserted)
- return;
-
- // update m_lastNodeInserted
- NodeImpl *old = m_lastNodeInserted;
- m_lastNodeInserted = node->lastDescendent();
- m_lastNodeInserted->ref();
- if (old)
- old->deref();
-}
-
-void ReplaceSelectionCommand::fixupNodeStyles(const QValueList<NodeDesiredStyle> &list)
-{
- // This function uses the mapped "desired style" to apply the additional style needed, if any,
- // to make the node have the desired style.
-
- document()->updateLayout();
-
- QValueListConstIterator<NodeDesiredStyle> it;
- for (it = list.begin(); it != list.end(); ++it) {
- NodeImpl *node = (*it).node();
- CSSMutableStyleDeclarationImpl *desiredStyle = (*it).style();
- ASSERT(desiredStyle);
-
- if (!node->inDocument())
- continue;
-
- // The desiredStyle declaration tells what style this node wants to be.
- // Compare that to the style that it is right now in the document.
- Position pos(node, 0);
- CSSComputedStyleDeclarationImpl *currentStyle = pos.computedStyle();
- currentStyle->ref();
-
- // Check for the special "match nearest blockquote color" property and resolve to the correct
- // color if necessary.
- DOMString matchColorCheck = desiredStyle->getPropertyValue(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
- if (matchColorCheck == matchNearestBlockquoteColorString()) {
- NodeImpl *blockquote = nearestMailBlockquote(node);
- Position pos(blockquote ? blockquote : node->getDocument()->documentElement(), 0);
- CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
- DOMString desiredColor = desiredStyle->getPropertyValue(CSS_PROP_COLOR);
- DOMString nearestColor = style->getPropertyValue(CSS_PROP_COLOR);
- if (desiredColor != nearestColor)
- desiredStyle->setProperty(CSS_PROP_COLOR, nearestColor);
- }
- desiredStyle->removeProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
-
- currentStyle->diff(desiredStyle);
-
- // Only add in block properties if the node is at the start of a
- // paragraph. This matches AppKit.
- if (!isStartOfParagraph(VisiblePosition(pos, DOWNSTREAM)))
- desiredStyle->removeBlockProperties();
-
- // If the desiredStyle is non-zero length, that means the current style differs
- // from the desired by the styles remaining in the desiredStyle declaration.
- if (desiredStyle->length() > 0) {
- DOM::RangeImpl *rangeAroundNode = document()->createRange();
- rangeAroundNode->ref();
- int exceptionCode = 0;
- rangeAroundNode->selectNode(node, exceptionCode);
- ASSERT(exceptionCode == 0);
- // affinity is not really important since this is a temp selection
- // just for calling applyStyle
- setEndingSelection(Selection(rangeAroundNode, SEL_DEFAULT_AFFINITY, SEL_DEFAULT_AFFINITY));
- applyStyle(desiredStyle);
- rangeAroundNode->deref();
- }
-
- currentStyle->deref();
- }
-}
-
-void computeAndStoreNodeDesiredStyle(DOM::NodeImpl *node, QValueList<NodeDesiredStyle> &list)
-{
- if (!node || !node->inDocument())
- return;
-
- CSSComputedStyleDeclarationImpl *computedStyle = Position(node, 0).computedStyle();
- computedStyle->ref();
- CSSMutableStyleDeclarationImpl *style = computedStyle->copyInheritableProperties();
- list.append(NodeDesiredStyle(node, style));
- computedStyle->deref();
-
- // In either of the color-matching tests below, set the color to a pseudo-color that will
- // make the content take on the color of the nearest-enclosing blockquote (if any) after
- // being pasted in.
- if (NodeImpl *blockquote = nearestMailBlockquote(node)) {
- CSSComputedStyleDeclarationImpl *blockquoteStyle = Position(blockquote, 0).computedStyle();
- if (blockquoteStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR)) {
- style->setProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
- return;
- }
- }
- NodeImpl *documentElement = node->getDocument() ? node->getDocument()->documentElement() : 0;
- if (documentElement) {
- CSSComputedStyleDeclarationImpl *documentStyle = Position(documentElement, 0).computedStyle();
- if (documentStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR)) {
- style->setProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
- }
- }
-}
-
-//------------------------------------------------------------------------------------------
-// SetNodeAttributeCommand
-
-SetNodeAttributeCommand::SetNodeAttributeCommand(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value)
- : EditCommand(document), m_element(element), m_attribute(attribute), m_value(value)
-{
- ASSERT(m_element);
- m_element->ref();
- ASSERT(!m_value.isNull());
-}
-
-SetNodeAttributeCommand::~SetNodeAttributeCommand()
-{
- ASSERT(m_element);
- m_element->deref();
-}
-
-void SetNodeAttributeCommand::doApply()
-{
- ASSERT(m_element);
- ASSERT(!m_value.isNull());
-
- int exceptionCode = 0;
- m_oldValue = m_element->getAttribute(m_attribute);
- m_element->setAttribute(m_attribute, m_value.implementation(), exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-void SetNodeAttributeCommand::doUnapply()
-{
- ASSERT(m_element);
-
- int exceptionCode = 0;
- if (m_oldValue.isNull())
- m_element->removeAttribute(m_attribute, exceptionCode);
- else
- m_element->setAttribute(m_attribute, m_oldValue.implementation(), exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-//------------------------------------------------------------------------------------------
-// SplitTextNodeCommand
-
-SplitTextNodeCommand::SplitTextNodeCommand(DocumentImpl *document, TextImpl *text, long offset)
- : EditCommand(document), m_text1(0), m_text2(text), m_offset(offset)
-{
- ASSERT(m_text2);
- ASSERT(m_text2->length() > 0);
-
- m_text2->ref();
-}
-
-SplitTextNodeCommand::~SplitTextNodeCommand()
-{
- if (m_text1)
- m_text1->deref();
-
- ASSERT(m_text2);
- m_text2->deref();
-}
-
-void SplitTextNodeCommand::doApply()
-{
- ASSERT(m_text2);
- ASSERT(m_offset > 0);
-
- int exceptionCode = 0;
-
- // EDIT FIXME: This should use better smarts for figuring out which portion
- // of the split to copy (based on their comparitive sizes). We should also
- // just use the DOM's splitText function.
-
- if (!m_text1) {
- // create only if needed.
- // if reapplying, this object will already exist.
- m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode));
- ASSERT(exceptionCode == 0);
- ASSERT(m_text1);
- m_text1->ref();
- }
-
- m_text2->deleteData(0, m_offset, exceptionCode);
- ASSERT(exceptionCode == 0);
-
- m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
- ASSERT(exceptionCode == 0);
-
- ASSERT(m_text2->previousSibling()->isTextNode());
- ASSERT(m_text2->previousSibling() == m_text1);
-}
-
-void SplitTextNodeCommand::doUnapply()
-{
- ASSERT(m_text1);
- ASSERT(m_text2);
-
- ASSERT(m_text1->nextSibling() == m_text2);
-
- int exceptionCode = 0;
- m_text2->insertData(0, m_text1->data(), exceptionCode);
- ASSERT(exceptionCode == 0);
-
- m_text2->parentNode()->removeChild(m_text1, exceptionCode);
- ASSERT(exceptionCode == 0);
-
- m_offset = m_text1->length();
-}
-
-//------------------------------------------------------------------------------------------
-// SplitElementCommand
-
-SplitElementCommand::SplitElementCommand(DOM::DocumentImpl *document, DOM::ElementImpl *element, DOM::NodeImpl *atChild)
- : EditCommand(document), m_element1(0), m_element2(element), m_atChild(atChild)
-{
- ASSERT(m_element2);
- ASSERT(m_atChild);
-
- m_element2->ref();
- m_atChild->ref();
-}
-
-SplitElementCommand::~SplitElementCommand()
-{
- if (m_element1)
- m_element1->deref();
-
- ASSERT(m_element2);
- m_element2->deref();
- ASSERT(m_atChild);
- m_atChild->deref();
-}
-
-void SplitElementCommand::doApply()
-{
- ASSERT(m_element2);
- ASSERT(m_atChild);
-
- int exceptionCode = 0;
-
- if (!m_element1) {
- // create only if needed.
- // if reapplying, this object will already exist.
- m_element1 = static_cast<ElementImpl *>(m_element2->cloneNode(false));
- ASSERT(m_element1);
- m_element1->ref();
- }
-
- m_element2->parent()->insertBefore(m_element1, m_element2, exceptionCode);
- ASSERT(exceptionCode == 0);
-
- while (m_element2->firstChild() != m_atChild) {
- ASSERT(m_element2->firstChild());
- m_element1->appendChild(m_element2->firstChild(), exceptionCode);
- ASSERT(exceptionCode == 0);
- }
-}
-
-void SplitElementCommand::doUnapply()
-{
- ASSERT(m_element1);
- ASSERT(m_element2);
- ASSERT(m_atChild);
-
- ASSERT(m_element1->nextSibling() == m_element2);
- ASSERT(m_element2->firstChild() && m_element2->firstChild() == m_atChild);
-
- int exceptionCode = 0;
-
- while (m_element1->lastChild()) {
- m_element2->insertBefore(m_element1->lastChild(), m_element2->firstChild(), exceptionCode);
- ASSERT(exceptionCode == 0);
- }
-
- m_element2->parentNode()->removeChild(m_element1, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-//------------------------------------------------------------------------------------------
-// MergeIdenticalElementsCommand
-
-MergeIdenticalElementsCommand::MergeIdenticalElementsCommand(DOM::DocumentImpl *document, DOM::ElementImpl *first, DOM::ElementImpl *second)
- : EditCommand(document), m_element1(first), m_element2(second), m_atChild(0)
-{
- ASSERT(m_element1);
- ASSERT(m_element2);
-
- m_element1->ref();
- m_element2->ref();
-}
-
-MergeIdenticalElementsCommand::~MergeIdenticalElementsCommand()
-{
- if (m_atChild)
- m_atChild->deref();
-
- ASSERT(m_element1);
- m_element1->deref();
- ASSERT(m_element2);
- m_element2->deref();
-}
-
-void MergeIdenticalElementsCommand::doApply()
-{
- ASSERT(m_element1);
- ASSERT(m_element2);
- ASSERT(m_element1->nextSibling() == m_element2);
-
- int exceptionCode = 0;
-
- if (!m_atChild) {
- m_atChild = m_element2->firstChild();
- m_atChild->ref();
- }
-
- while (m_element1->lastChild()) {
- m_element2->insertBefore(m_element1->lastChild(), m_element2->firstChild(), exceptionCode);
- ASSERT(exceptionCode == 0);
- }
-
- m_element2->parentNode()->removeChild(m_element1, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-void MergeIdenticalElementsCommand::doUnapply()
-{
- ASSERT(m_element1);
- ASSERT(m_element2);
-
- int exceptionCode = 0;
-
- m_element2->parent()->insertBefore(m_element1, m_element2, exceptionCode);
- ASSERT(exceptionCode == 0);
-
- while (m_element2->firstChild() != m_atChild) {
- ASSERT(m_element2->firstChild());
- m_element1->appendChild(m_element2->firstChild(), exceptionCode);
- ASSERT(exceptionCode == 0);
- }
-}
-
-//------------------------------------------------------------------------------------------
-// WrapContentsInDummySpanCommand
-
-WrapContentsInDummySpanCommand::WrapContentsInDummySpanCommand(DOM::DocumentImpl *document, DOM::ElementImpl *element)
- : EditCommand(document), m_element(element), m_dummySpan(0)
-{
- ASSERT(m_element);
-
- m_element->ref();
-}
-
-WrapContentsInDummySpanCommand::~WrapContentsInDummySpanCommand()
-{
- if (m_dummySpan)
- m_dummySpan->deref();
-
- ASSERT(m_element);
- m_element->deref();
-}
-
-void WrapContentsInDummySpanCommand::doApply()
-{
- ASSERT(m_element);
-
- int exceptionCode = 0;
-
- if (!m_dummySpan) {
- m_dummySpan = createStyleSpanElement(document());
- m_dummySpan->ref();
- }
-
- while (m_element->firstChild()) {
- m_dummySpan->appendChild(m_element->firstChild(), exceptionCode);
- ASSERT(exceptionCode == 0);
- }
-
- m_element->appendChild(m_dummySpan, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-void WrapContentsInDummySpanCommand::doUnapply()
-{
- ASSERT(m_element);
- ASSERT(m_dummySpan);
-
- ASSERT(m_element->firstChild() == m_dummySpan);
- ASSERT(!m_element->firstChild()->nextSibling());
-
- int exceptionCode = 0;
-
- while (m_dummySpan->firstChild()) {
- m_element->appendChild(m_dummySpan->firstChild(), exceptionCode);
- ASSERT(exceptionCode == 0);
- }
-
- m_element->removeChild(m_dummySpan, exceptionCode);
- ASSERT(exceptionCode == 0);
-}
-
-//------------------------------------------------------------------------------------------
-// SplitTextNodeContainingElementCommand
-
-SplitTextNodeContainingElementCommand::SplitTextNodeContainingElementCommand(DocumentImpl *document, TextImpl *text, long offset)
- : CompositeEditCommand(document), m_text(text), m_offset(offset)
-{
- ASSERT(m_text);
- ASSERT(m_text->length() > 0);
-
- m_text->ref();
-}
-
-SplitTextNodeContainingElementCommand::~SplitTextNodeContainingElementCommand()
-{
- ASSERT(m_text);
- m_text->deref();
-}
-
-void SplitTextNodeContainingElementCommand::doApply()
-{
- ASSERT(m_text);
- ASSERT(m_offset > 0);
-
- splitTextNode(m_text, m_offset);
-
- NodeImpl *parentNode = m_text->parentNode();
- if (!parentNode->renderer() || !parentNode->renderer()->isInline()) {
- wrapContentsInDummySpan(static_cast<ElementImpl *>(parentNode));
- parentNode = parentNode->firstChild();
- }
-
- splitElement(static_cast<ElementImpl *>(parentNode), m_text);
-}
-
-//------------------------------------------------------------------------------------------
-// TypingCommand
-
-TypingCommand::TypingCommand(DocumentImpl *document, ETypingCommand commandType, const DOMString &textToInsert, bool selectInsertedText)
- : CompositeEditCommand(document),
- m_commandType(commandType),
- m_textToInsert(textToInsert),
- m_openForMoreTyping(true),
- m_applyEditing(false),
- m_selectInsertedText(selectInsertedText),
- m_smartDelete(false)
-{
-}
-
-void TypingCommand::deleteKeyPressed(DocumentImpl *document, bool smartDelete)
-{
- ASSERT(document);
-
- KHTMLPart *part = document->part();
- ASSERT(part);
-
- EditCommandPtr lastEditCommand = part->lastEditCommand();
- if (isOpenForMoreTypingCommand(lastEditCommand)) {
- static_cast<TypingCommand *>(lastEditCommand.get())->deleteKeyPressed();
- }
- else {
- Selection selection = part->selection();
- if (selection.isCaret() && VisiblePosition(selection.start(), selection.startAffinity()).previous().isNull()) {
- // do nothing for a delete key at the start of an editable element.
- }
- else {
- TypingCommand *typingCommand = new TypingCommand(document, DeleteKey);
- typingCommand->setSmartDelete(smartDelete);
- EditCommandPtr cmd(typingCommand);
- cmd.apply();
- }
- }
-}
-
-void TypingCommand::forwardDeleteKeyPressed(DocumentImpl *document, bool smartDelete)
-{
- ASSERT(document);
-
- KHTMLPart *part = document->part();
- ASSERT(part);
-
- EditCommandPtr lastEditCommand = part->lastEditCommand();
- if (isOpenForMoreTypingCommand(lastEditCommand)) {
- static_cast<TypingCommand *>(lastEditCommand.get())->forwardDeleteKeyPressed();
- }
- else {
- Selection selection = part->selection();
- if (selection.isCaret() && isEndOfDocument(VisiblePosition(selection.start(), selection.startAffinity()))) {
- // do nothing for a delete key at the start of an editable element.
- }
- else {
- TypingCommand *typingCommand = new TypingCommand(document, ForwardDeleteKey);
- typingCommand->setSmartDelete(smartDelete);
- EditCommandPtr cmd(typingCommand);
- cmd.apply();
- }
- }
-}
-
-void TypingCommand::insertText(DocumentImpl *document, const DOMString &text, bool selectInsertedText)
-{
- ASSERT(document);
-
- KHTMLPart *part = document->part();
- ASSERT(part);
-
- EditCommandPtr lastEditCommand = part->lastEditCommand();
- if (isOpenForMoreTypingCommand(lastEditCommand)) {
- static_cast<TypingCommand *>(lastEditCommand.get())->insertText(text, selectInsertedText);
- }
- else {
- EditCommandPtr cmd(new TypingCommand(document, InsertText, text, selectInsertedText));
- cmd.apply();
- }
-}
-
-void TypingCommand::insertLineBreak(DocumentImpl *document)
-{
- ASSERT(document);
-
- KHTMLPart *part = document->part();
- ASSERT(part);
-
- EditCommandPtr lastEditCommand = part->lastEditCommand();
- if (isOpenForMoreTypingCommand(lastEditCommand)) {
- static_cast<TypingCommand *>(lastEditCommand.get())->insertLineBreak();
- }
- else {
- EditCommandPtr cmd(new TypingCommand(document, InsertLineBreak));
- cmd.apply();
- }
-}
-
-void TypingCommand::insertParagraphSeparatorInQuotedContent(DocumentImpl *document)
-{
- ASSERT(document);
-
- KHTMLPart *part = document->part();
- ASSERT(part);
-
- EditCommandPtr lastEditCommand = part->lastEditCommand();
- if (isOpenForMoreTypingCommand(lastEditCommand)) {
- static_cast<TypingCommand *>(lastEditCommand.get())->insertParagraphSeparatorInQuotedContent();
- }
- else {
- EditCommandPtr cmd(new TypingCommand(document, InsertParagraphSeparatorInQuotedContent));
- cmd.apply();
- }
-}
-
-void TypingCommand::insertParagraphSeparator(DocumentImpl *document)
-{
- ASSERT(document);
-
- KHTMLPart *part = document->part();
- ASSERT(part);
-
- EditCommandPtr lastEditCommand = part->lastEditCommand();
- if (isOpenForMoreTypingCommand(lastEditCommand)) {
- static_cast<TypingCommand *>(lastEditCommand.get())->insertParagraphSeparator();
- }
- else {
- EditCommandPtr cmd(new TypingCommand(document, InsertParagraphSeparator));
- cmd.apply();
- }
-}
-
-bool TypingCommand::isOpenForMoreTypingCommand(const EditCommandPtr &cmd)
-{
- return cmd.isTypingCommand() &&
- static_cast<const TypingCommand *>(cmd.get())->openForMoreTyping();
-}
-
-void TypingCommand::closeTyping(const EditCommandPtr &cmd)
-{
- if (isOpenForMoreTypingCommand(cmd))
- static_cast<TypingCommand *>(cmd.get())->closeTyping();
-}
-
-void TypingCommand::doApply()
-{
- if (endingSelection().isNone())
- return;
-
- switch (m_commandType) {
- case DeleteKey:
- deleteKeyPressed();
- return;
- case ForwardDeleteKey:
- forwardDeleteKeyPressed();
- return;
- case InsertLineBreak:
- insertLineBreak();
- return;
- case InsertParagraphSeparator:
- insertParagraphSeparator();
- return;
- case InsertParagraphSeparatorInQuotedContent:
- insertParagraphSeparatorInQuotedContent();
- return;
- case InsertText:
- insertText(m_textToInsert, m_selectInsertedText);
- return;
- }
-
- ASSERT_NOT_REACHED();
-}
-
-EditAction TypingCommand::editingAction() const
-{
- return EditActionTyping;
-}
-
-void TypingCommand::markMisspellingsAfterTyping()
-{
- // Take a look at the selection that results after typing and determine whether we need to spellcheck.
- // Since the word containing the current selection is never marked, this does a check to
- // see if typing made a new word that is not in the current selection. Basically, you
- // get this by being at the end of a word and typing a space.
- VisiblePosition start(endingSelection().start(), endingSelection().startAffinity());
- VisiblePosition previous = start.previous();
- if (previous.isNotNull()) {
- VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
- VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
- if (p1 != p2)
- KWQ(document()->part())->markMisspellingsInAdjacentWords(p1);
- }
-}
-
-void TypingCommand::typingAddedToOpenCommand()
-{
- markMisspellingsAfterTyping();
- // Do not apply editing to the part on the first time through.
- // The part will get told in the same way as all other commands.
- // But since this command stays open and is used for additional typing,
- // we need to tell the part here as other commands are added.
- if (m_applyEditing) {
- EditCommandPtr cmd(this);
- document()->part()->appliedEditing(cmd);
- }
- m_applyEditing = true;
-}
-
-void TypingCommand::insertText(const DOMString &text, bool selectInsertedText)
-{
- // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved.
- // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending
- // an existing selection; at the moment they can either put the caret after what's inserted or
- // select what's inserted, but there's no way to "extend selection" to include both an old selection
- // that ends just before where we want to insert text and the newly inserted text.
- int offset = 0;
- int newline;
- while ((newline = text.find('\n', offset)) != -1) {
- if (newline != offset) {
- insertTextRunWithoutNewlines(text.substring(offset, newline - offset), false);
- }
- insertParagraphSeparator();
- offset = newline + 1;
- }
- if (offset == 0) {
- insertTextRunWithoutNewlines(text, selectInsertedText);
- } else {
- int length = text.length();
- if (length != offset) {
- insertTextRunWithoutNewlines(text.substring(offset, length - offset), selectInsertedText);
- }
- }
-}
-
-void TypingCommand::insertTextRunWithoutNewlines(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) {
- InsertTextCommand *impl = new InsertTextCommand(document());
- EditCommandPtr cmd(impl);
- applyCommandToComposite(cmd);
- impl->input(text, selectInsertedText);
- }
- else {
- EditCommandPtr lastCommand = m_cmds.last();
- if (lastCommand.isInsertTextCommand()) {
- InsertTextCommand *impl = static_cast<InsertTextCommand *>(lastCommand.get());
- impl->input(text, selectInsertedText);
- }
- else {
- InsertTextCommand *impl = new InsertTextCommand(document());
- EditCommandPtr cmd(impl);
- applyCommandToComposite(cmd);
- impl->input(text, selectInsertedText);
- }
- }
- typingAddedToOpenCommand();
-}
-
-void TypingCommand::insertLineBreak()
-{
- EditCommandPtr cmd(new InsertLineBreakCommand(document()));
- applyCommandToComposite(cmd);
- typingAddedToOpenCommand();
-}
-
-void TypingCommand::insertParagraphSeparator()
-{
- EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
- applyCommandToComposite(cmd);
- typingAddedToOpenCommand();
-}
-
-void TypingCommand::insertParagraphSeparatorInQuotedContent()
-{
- EditCommandPtr cmd(new InsertParagraphSeparatorInQuotedContentCommand(document()));
- applyCommandToComposite(cmd);
- typingAddedToOpenCommand();
-}
-
-void TypingCommand::deleteKeyPressed()
-{
- Selection selectionToDelete;
-
- switch (endingSelection().state()) {
- case Selection::RANGE:
- selectionToDelete = endingSelection();
- break;
- case Selection::CARET: {
- // Handle delete at beginning-of-block case.
- // Do nothing in the case that the caret is at the start of a
- // root editable element or at the start of a document.
- Position pos(endingSelection().start());
- Position start = VisiblePosition(pos, endingSelection().startAffinity()).previous().deepEquivalent();
- Position end = VisiblePosition(pos, endingSelection().startAffinity()).deepEquivalent();
- if (start.isNotNull() && end.isNotNull() && start.node()->rootEditableElement() == end.node()->rootEditableElement())
- selectionToDelete = Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY);
- break;
- }
- case Selection::NONE:
- ASSERT_NOT_REACHED();
- break;
- }
-
- if (selectionToDelete.isCaretOrRange()) {
- deleteSelection(selectionToDelete, m_smartDelete);
- setSmartDelete(false);
- typingAddedToOpenCommand();
- }
-}
-
-void TypingCommand::forwardDeleteKeyPressed()
-{
- Selection selectionToDelete;
-
- switch (endingSelection().state()) {
- case Selection::RANGE:
- selectionToDelete = endingSelection();
- break;
- case Selection::CARET: {
- // Handle delete at beginning-of-block case.
- // Do nothing in the case that the caret is at the start of a
- // root editable element or at the start of a document.
- Position pos(endingSelection().start());
- Position start = VisiblePosition(pos, endingSelection().startAffinity()).next().deepEquivalent();
- Position end = VisiblePosition(pos, endingSelection().startAffinity()).deepEquivalent();
- if (start.isNotNull() && end.isNotNull() && start.node()->rootEditableElement() == end.node()->rootEditableElement())
- selectionToDelete = Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY);
- break;
- }
- case Selection::NONE:
- ASSERT_NOT_REACHED();
- break;
- }
-
- if (selectionToDelete.isCaretOrRange()) {
- deleteSelection(selectionToDelete, m_smartDelete);
- setSmartDelete(false);
- typingAddedToOpenCommand();
- }
-}
-
-bool TypingCommand::preservesTypingStyle() const
-{
- switch (m_commandType) {
- case DeleteKey:
- case ForwardDeleteKey:
- case InsertParagraphSeparator:
- case InsertLineBreak:
- return true;
- case InsertParagraphSeparatorInQuotedContent:
- case InsertText:
- return false;
- }
- ASSERT_NOT_REACHED();
- return false;
-}
-
-bool TypingCommand::isTypingCommand() const
-{
- return true;
-}
-
-ElementImpl *createDefaultParagraphElement(DocumentImpl *document)
-{
- // We would need this margin-zeroing code back if we ever return to using <p> elements for default paragraphs.
- // static const DOMString defaultParagraphStyle("margin-top: 0; margin-bottom: 0");
- int exceptionCode = 0;
- ElementImpl *element = document->createHTMLElement("div", exceptionCode);
- ASSERT(exceptionCode == 0);
- return element;
-}
-
-ElementImpl *createBreakElement(DocumentImpl *document)
-{
- int exceptionCode = 0;
- ElementImpl *breakNode = document->createHTMLElement("br", exceptionCode);
- ASSERT(exceptionCode == 0);
- return breakNode;
-}
-
-bool isNodeRendered(const NodeImpl *node)
-{
- if (!node)
- return false;
-
- RenderObject *renderer = node->renderer();
- if (!renderer)
- return false;
-
- return renderer->style()->visibility() == VISIBLE;
-}
-
-bool isProbablyBlock(const NodeImpl *node)
-{
- if (!node)
- return false;
-
- switch (node->id()) {
- case ID_BLOCKQUOTE:
- case ID_DD:
- case ID_DIV:
- case ID_DL:
- case ID_DT:
- case ID_H1:
- case ID_H2:
- case ID_H3:
- case ID_H4:
- case ID_H5:
- case ID_H6:
- case ID_HR:
- case ID_LI:
- case ID_OL:
- case ID_P:
- case ID_PRE:
- case ID_TD:
- case ID_TH:
- case ID_UL:
- return true;
- }
-
- return false;
-}
-
-bool isProbablyTableStructureNode(const NodeImpl *node)
-{
- if (!node)
- return false;
-
- switch (node->id()) {
- case ID_TABLE:
- case ID_TBODY:
- case ID_TD:
- case ID_TFOOT:
- case ID_THEAD:
- case ID_TR:
- return true;
- }
- return false;
-}
-
-NodeImpl *nearestMailBlockquote(const NodeImpl *node)
-{
- for (NodeImpl *n = const_cast<NodeImpl *>(node); n; n = n->parentNode()) {
- if (isMailBlockquote(n))
- return n;
- }
- return 0;
-}
-
-bool isMailBlockquote(const NodeImpl *node)
+bool isMailBlockquote(const NodeImpl *node)
{
if (!node || !node->renderer() || !node->isElementNode() && node->id() != ID_BLOCKQUOTE)
return false;
return static_cast<const ElementImpl *>(node)->getAttribute("type") == "cite";
}
-bool isMailPasteAsQuotationNode(const NodeImpl *node)
-{
- if (!node)
- return false;
-
- return static_cast<const ElementImpl *>(node)->getAttribute("class") == ApplePasteAsQuotation;
-}
-
} // namespace khtml
#include "composite_edit_command.h"
#include "apply_style_command.h"
#include "delete_selection_command.h"
+#include "move_selection_command.h"
+#include "replace_selection_command.h"
+#include "typing_command.h"
#include "dom_nodeimpl.h"
#include "editing/edit_actions.h"
namespace khtml {
-class Selection;
-class VisiblePosition;
-
-//------------------------------------------------------------------------------------------
-// InsertLineBreakCommand
-
-class InsertLineBreakCommand : public CompositeEditCommand
-{
-public:
- InsertLineBreakCommand(DOM::DocumentImpl *document);
-
- virtual void doApply();
-
-private:
- virtual bool preservesTypingStyle() const;
- void insertNodeAfterPosition(DOM::NodeImpl *node, const DOM::Position &pos);
- void insertNodeBeforePosition(DOM::NodeImpl *node, const DOM::Position &pos);
-};
-
-//------------------------------------------------------------------------------------------
-// InsertParagraphSeparatorCommand
-
-class InsertParagraphSeparatorCommand : public CompositeEditCommand
-{
-public:
- InsertParagraphSeparatorCommand(DOM::DocumentImpl *document);
- virtual ~InsertParagraphSeparatorCommand();
-
- virtual void doApply();
-
-private:
- DOM::ElementImpl *createParagraphElement();
- void calculateStyleBeforeInsertion(const DOM::Position &);
- void applyStyleAfterInsertion();
-
- virtual bool preservesTypingStyle() const;
-
- QPtrList<DOM::NodeImpl> ancestors;
- QPtrList<DOM::NodeImpl> clonedNodes;
- DOM::CSSMutableStyleDeclarationImpl *m_style;
-};
-
-//------------------------------------------------------------------------------------------
-// InsertParagraphSeparatorInQuotedContentCommand
-
-class InsertParagraphSeparatorInQuotedContentCommand : public CompositeEditCommand
-{
-public:
- InsertParagraphSeparatorInQuotedContentCommand(DOM::DocumentImpl *);
- virtual ~InsertParagraphSeparatorInQuotedContentCommand();
-
- virtual void doApply();
-
-private:
- QPtrList<DOM::NodeImpl> ancestors;
- QPtrList<DOM::NodeImpl> clonedNodes;
- DOM::ElementImpl *m_breakNode;
-};
-
-//------------------------------------------------------------------------------------------
-// InsertTextCommand
-
-class InsertTextCommand : public CompositeEditCommand
-{
-public:
- InsertTextCommand(DOM::DocumentImpl *document);
-
- virtual void doApply();
-
- void deleteCharacter();
- void input(const DOM::DOMString &text, bool selectInsertedText = false);
-
- unsigned long charactersAdded() const { return m_charactersAdded; }
-
-private:
- virtual bool isInsertTextCommand() const;
-
- DOM::Position prepareForTextInsertion(bool adjustDownstream);
- void insertSpace(DOM::TextImpl *textNode, unsigned long offset);
-
- unsigned long m_charactersAdded;
-};
-
-//------------------------------------------------------------------------------------------
-// JoinTextNodesCommand
-
-class JoinTextNodesCommand : public EditCommand
-{
-public:
- JoinTextNodesCommand(DOM::DocumentImpl *, DOM::TextImpl *, DOM::TextImpl *);
- virtual ~JoinTextNodesCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
- DOM::TextImpl *firstNode() const { return m_text1; }
- DOM::TextImpl *secondNode() const { return m_text2; }
-
-private:
- DOM::TextImpl *m_text1;
- DOM::TextImpl *m_text2;
- unsigned long m_offset;
-};
-
-//------------------------------------------------------------------------------------------
-// MoveSelectionCommand
-
-class MoveSelectionCommand : public CompositeEditCommand
-{
-public:
- MoveSelectionCommand(DOM::DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position, bool smartMove=false);
- virtual ~MoveSelectionCommand();
-
- virtual void doApply();
- virtual EditAction editingAction() const;
-
-private:
- DOM::DocumentFragmentImpl *m_fragment;
- DOM::Position m_position;
- bool m_smartMove;
-};
-
-//------------------------------------------------------------------------------------------
-// RebalanceWhitespaceCommand
-
-class RebalanceWhitespaceCommand : public EditCommand
-{
-public:
- RebalanceWhitespaceCommand(DOM::DocumentImpl *, const DOM::Position &);
- virtual ~RebalanceWhitespaceCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
-private:
- enum { InvalidOffset = -1 };
-
- virtual bool preservesTypingStyle() const;
-
- DOM::DOMString m_beforeString;
- DOM::DOMString m_afterString;
- DOM::Position m_position;
- long m_upstreamOffset;
- long m_downstreamOffset;
-};
-
-//------------------------------------------------------------------------------------------
-// RemoveCSSPropertyCommand
-
-class RemoveCSSPropertyCommand : public EditCommand
-{
-public:
- RemoveCSSPropertyCommand(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *, int property);
- virtual ~RemoveCSSPropertyCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
- DOM::CSSMutableStyleDeclarationImpl *styleDeclaration() const { return m_decl; }
- int property() const { return m_property; }
-
-private:
- DOM::CSSMutableStyleDeclarationImpl *m_decl;
- int m_property;
- DOM::DOMString m_oldValue;
- bool m_important;
-};
-
-//------------------------------------------------------------------------------------------
-// RemoveNodeAttributeCommand
-
-class RemoveNodeAttributeCommand : public EditCommand
-{
-public:
- RemoveNodeAttributeCommand(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute);
- virtual ~RemoveNodeAttributeCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
- DOM::ElementImpl *element() const { return m_element; }
- DOM::NodeImpl::Id attribute() const { return m_attribute; }
-
-private:
- DOM::ElementImpl *m_element;
- DOM::NodeImpl::Id m_attribute;
- DOM::DOMString m_oldValue;
-};
-
-//------------------------------------------------------------------------------------------
-// RemoveNodeCommand
-
-class RemoveNodeCommand : public EditCommand
-{
-public:
- RemoveNodeCommand(DOM::DocumentImpl *, DOM::NodeImpl *);
- virtual ~RemoveNodeCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
- DOM::NodeImpl *node() const { return m_removeChild; }
-
-private:
- DOM::NodeImpl *m_parent;
- DOM::NodeImpl *m_removeChild;
- DOM::NodeImpl *m_refChild;
-};
-
-//------------------------------------------------------------------------------------------
-// RemoveNodePreservingChildrenCommand
-
-class RemoveNodePreservingChildrenCommand : public CompositeEditCommand
-{
-public:
- RemoveNodePreservingChildrenCommand(DOM::DocumentImpl *, DOM::NodeImpl *);
- virtual ~RemoveNodePreservingChildrenCommand();
-
- virtual void doApply();
-
- DOM::NodeImpl *node() const { return m_node; }
-
-private:
- DOM::NodeImpl *m_node;
-};
-
-//------------------------------------------------------------------------------------------
-// ReplaceSelectionCommand
-
-// --- NodeDesiredStyle helper class
-
-class NodeDesiredStyle
-{
-public:
- NodeDesiredStyle(DOM::NodeImpl *, DOM::CSSMutableStyleDeclarationImpl *);
- NodeDesiredStyle(const NodeDesiredStyle &);
- ~NodeDesiredStyle();
-
- DOM::NodeImpl *node() const { return m_node; }
- DOM::CSSMutableStyleDeclarationImpl *style() const { return m_style; }
-
- NodeDesiredStyle &operator=(const NodeDesiredStyle &);
-
-private:
- DOM::NodeImpl *m_node;
- DOM::CSSMutableStyleDeclarationImpl *m_style;
-};
-
-// --- ReplacementFragment helper class
-
-class ReplacementFragment
-{
-public:
- ReplacementFragment(DOM::DocumentImpl *, DOM::DocumentFragmentImpl *, bool matchStyle);
- ~ReplacementFragment();
-
- enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
-
- DOM::DocumentFragmentImpl *root() const { return m_fragment; }
- DOM::NodeImpl *firstChild() const;
- DOM::NodeImpl *lastChild() const;
-
- DOM::NodeImpl *mergeStartNode() const;
-
- const QValueList<NodeDesiredStyle> &desiredStyles() { return m_styles; }
-
- void pruneEmptyNodes();
-
- EFragmentType type() const { return m_type; }
- bool isEmpty() const { return m_type == EmptyFragment; }
- bool isSingleTextNode() const { return m_type == SingleTextNodeFragment; }
- bool isTreeFragment() const { return m_type == TreeFragment; }
-
- bool hasMoreThanOneBlock() const { return m_hasMoreThanOneBlock; }
- bool hasInterchangeNewlineAtStart() const { return m_hasInterchangeNewlineAtStart; }
- bool hasInterchangeNewlineAtEnd() const { return m_hasInterchangeNewlineAtEnd; }
-
-private:
- // no copy construction or assignment
- ReplacementFragment(const ReplacementFragment &);
- ReplacementFragment &operator=(const ReplacementFragment &);
-
- static bool isInterchangeNewlineNode(const DOM::NodeImpl *);
- static bool isInterchangeConvertedSpaceSpan(const DOM::NodeImpl *);
-
- DOM::NodeImpl *insertFragmentForTestRendering();
- void restoreTestRenderingNodesToFragment(DOM::NodeImpl *);
- void computeStylesUsingTestRendering(DOM::NodeImpl *);
- void removeUnrenderedNodesUsingTestRendering(DOM::NodeImpl *);
- int countRenderedBlocks(DOM::NodeImpl *holder);
- void removeStyleNodes();
-
- // A couple simple DOM helpers
- DOM::NodeImpl *enclosingBlock(DOM::NodeImpl *) const;
- void removeNode(DOM::NodeImpl *);
- void removeNodePreservingChildren(DOM::NodeImpl *);
- void insertNodeBefore(DOM::NodeImpl *node, DOM::NodeImpl *refNode);
-
- EFragmentType m_type;
- DOM::DocumentImpl *m_document;
- DOM::DocumentFragmentImpl *m_fragment;
- QValueList<NodeDesiredStyle> m_styles;
- bool m_matchStyle;
- bool m_hasInterchangeNewlineAtStart;
- bool m_hasInterchangeNewlineAtEnd;
- bool m_hasMoreThanOneBlock;
-};
-
-class ReplaceSelectionCommand : public CompositeEditCommand
-{
-public:
- ReplaceSelectionCommand(DOM::DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement=true, bool smartReplace=false, bool matchStyle=false);
- virtual ~ReplaceSelectionCommand();
-
- virtual void doApply();
- virtual EditAction editingAction() const;
-
-private:
- void completeHTMLReplacement(const DOM::Position &lastPositionToSelect);
-
- void insertNodeAfterAndUpdateNodesInserted(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
- void insertNodeAtAndUpdateNodesInserted(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild, long offset);
- void insertNodeBeforeAndUpdateNodesInserted(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
-
- void updateNodesInserted(DOM::NodeImpl *);
- void fixupNodeStyles(const QValueList<NodeDesiredStyle> &);
- void removeLinePlaceholderIfNeeded(DOM::NodeImpl *);
-
- ReplacementFragment m_fragment;
- DOM::NodeImpl *m_firstNodeInserted;
- DOM::NodeImpl *m_lastNodeInserted;
- DOM::NodeImpl *m_lastTopNodeInserted;
- DOM::CSSMutableStyleDeclarationImpl *m_insertionStyle;
- bool m_selectReplacement;
- bool m_smartReplace;
- bool m_matchStyle;
-};
-
-void computeAndStoreNodeDesiredStyle(DOM::NodeImpl *, QValueList<NodeDesiredStyle> &);
-
-//------------------------------------------------------------------------------------------
-// SetNodeAttributeCommand
-
-class SetNodeAttributeCommand : public EditCommand
-{
-public:
- SetNodeAttributeCommand(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute, const DOM::DOMString &value);
- virtual ~SetNodeAttributeCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
- DOM::ElementImpl *element() const { return m_element; }
- DOM::NodeImpl::Id attribute() const { return m_attribute; }
- DOM::DOMString value() const { return m_value; }
-
-private:
- DOM::ElementImpl *m_element;
- DOM::NodeImpl::Id m_attribute;
- DOM::DOMString m_value;
- DOM::DOMString m_oldValue;
-};
-
-//------------------------------------------------------------------------------------------
-// SplitTextNodeCommand
-
-class SplitTextNodeCommand : public EditCommand
-{
-public:
- SplitTextNodeCommand(DOM::DocumentImpl *, DOM::TextImpl *, long);
- virtual ~SplitTextNodeCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
- DOM::TextImpl *node() const { return m_text2; }
- long offset() const { return m_offset; }
-
-private:
- DOM::TextImpl *m_text1;
- DOM::TextImpl *m_text2;
- unsigned long m_offset;
-};
-
-//------------------------------------------------------------------------------------------
-// WrapContentsInDummySpanCommand
-
-class WrapContentsInDummySpanCommand : public EditCommand
-{
-public:
- WrapContentsInDummySpanCommand(DOM::DocumentImpl *, DOM::ElementImpl *);
- virtual ~WrapContentsInDummySpanCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
-private:
- DOM::ElementImpl *m_element;
- DOM::ElementImpl *m_dummySpan;
-};
-
-//------------------------------------------------------------------------------------------
-// SplitElementCommand
-
-class SplitElementCommand : public EditCommand
-{
-public:
- SplitElementCommand(DOM::DocumentImpl *, DOM::ElementImpl *element, DOM::NodeImpl *atChild);
- virtual ~SplitElementCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
-private:
- DOM::ElementImpl *m_element1;
- DOM::ElementImpl *m_element2;
- DOM::NodeImpl *m_atChild;
-};
-
-//------------------------------------------------------------------------------------------
-// MergeIdenticalElementsCommand
-
-class MergeIdenticalElementsCommand : public EditCommand
-{
-public:
- MergeIdenticalElementsCommand(DOM::DocumentImpl *, DOM::ElementImpl *first, DOM::ElementImpl *second);
- virtual ~MergeIdenticalElementsCommand();
-
- virtual void doApply();
- virtual void doUnapply();
-
-private:
- DOM::ElementImpl *m_element1;
- DOM::ElementImpl *m_element2;
- DOM::NodeImpl *m_atChild;
-};
-
-//------------------------------------------------------------------------------------------
-// SplitTextNodeContainingElementCommand
-
-class SplitTextNodeContainingElementCommand : public CompositeEditCommand
-{
-public:
- SplitTextNodeContainingElementCommand(DOM::DocumentImpl *, DOM::TextImpl *, long);
- virtual ~SplitTextNodeContainingElementCommand();
-
- virtual void doApply();
-
-private:
- DOM::TextImpl *m_text;
- long m_offset;
-};
-
-
-//------------------------------------------------------------------------------------------
-// TypingCommand
-
-class TypingCommand : public CompositeEditCommand
-{
-public:
- enum ETypingCommand {
- DeleteKey,
- ForwardDeleteKey,
- InsertText,
- InsertLineBreak,
- InsertParagraphSeparator,
- InsertParagraphSeparatorInQuotedContent,
- };
-
- TypingCommand(DOM::DocumentImpl *document, ETypingCommand, const DOM::DOMString &text = "", bool selectInsertedText = false);
-
- static void deleteKeyPressed(DOM::DocumentImpl *, bool smartDelete = false);
- static void forwardDeleteKeyPressed(DOM::DocumentImpl *, bool smartDelete = false);
- static void insertText(DOM::DocumentImpl *, const DOM::DOMString &, bool selectInsertedText = false);
- static void insertLineBreak(DOM::DocumentImpl *);
- static void insertParagraphSeparator(DOM::DocumentImpl *);
- static void insertParagraphSeparatorInQuotedContent(DOM::DocumentImpl *);
- static bool isOpenForMoreTypingCommand(const EditCommandPtr &);
- static void closeTyping(const EditCommandPtr &);
-
- virtual void doApply();
- virtual EditAction editingAction() const;
-
- bool openForMoreTyping() const { return m_openForMoreTyping; }
- void closeTyping() { m_openForMoreTyping = false; }
-
- void insertText(const DOM::DOMString &text, bool selectInsertedText);
- void insertTextRunWithoutNewlines(const DOM::DOMString &text, bool selectInsertedText);
- void insertLineBreak();
- void insertParagraphSeparatorInQuotedContent();
- void insertParagraphSeparator();
- void deleteKeyPressed();
- void forwardDeleteKeyPressed();
-
- bool smartDelete() { return m_smartDelete; }
- void setSmartDelete(bool smartDelete) { m_smartDelete = smartDelete; }
-
-private:
- virtual bool isTypingCommand() const;
- virtual bool preservesTypingStyle() const;
-
- void markMisspellingsAfterTyping();
- void typingAddedToOpenCommand();
-
- ETypingCommand m_commandType;
- DOM::DOMString m_textToInsert;
- bool m_openForMoreTyping;
- bool m_applyEditing;
- bool m_selectInsertedText;
- bool m_smartDelete;
-};
+DOM::DOMString &nonBreakingSpaceString();
+void derefNodesInList(QPtrList<DOM::NodeImpl> &list);
//------------------------------------------------------------------------------------------
DOM::ElementImpl *createBreakElement(DOM::DocumentImpl *document);
bool isNodeRendered(const DOM::NodeImpl *);
-bool isProbablyBlock(const DOM::NodeImpl *);
-bool isProbablyTableStructureNode(const DOM::NodeImpl *);
bool isMailBlockquote(const DOM::NodeImpl *);
DOM::NodeImpl *nearestMailBlockquote(const DOM::NodeImpl *);
-bool isMailPasteAsQuotationNode(const DOM::NodeImpl *node);
+
+bool isSpecialElement(DOM::NodeImpl *n);
//------------------------------------------------------------------------------------------
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "insert_line_break_command.h"
+
+#include "htmlediting.h"
+#include "visible_position.h"
+#include "visible_units.h"
+
+#include "misc/htmltags.h"
+#include "xml/dom_docimpl.h"
+#include "xml/dom_elementimpl.h"
+#include "xml/dom2_rangeimpl.h"
+#include "xml/dom_textimpl.h"
+#include "khtml_part.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#include "KWQLogging.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#define LOG(channel, formatAndArgs...) ((void)0)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::ElementImpl;
+using DOM::NodeImpl;
+using DOM::Position;
+using DOM::TextImpl;
+using DOM::CSSMutableStyleDeclarationImpl;
+
+namespace khtml {
+
+InsertLineBreakCommand::InsertLineBreakCommand(DocumentImpl *document)
+ : CompositeEditCommand(document)
+{
+}
+
+bool InsertLineBreakCommand::preservesTypingStyle() const
+{
+ return true;
+}
+
+void InsertLineBreakCommand::insertNodeAfterPosition(NodeImpl *node, const Position &pos)
+{
+ // Insert the BR after the caret position. In the case the
+ // position is a block, do an append. We don't want to insert
+ // the BR *after* the block.
+ Position upstream(pos.upstream());
+ NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
+ if (cb == pos.node())
+ appendNode(node, cb);
+ else
+ insertNodeAfter(node, pos.node());
+}
+
+void InsertLineBreakCommand::insertNodeBeforePosition(NodeImpl *node, const Position &pos)
+{
+ // Insert the BR after the caret position. In the case the
+ // position is a block, do an append. We don't want to insert
+ // the BR *before* the block.
+ Position upstream(pos.upstream());
+ NodeImpl *cb = pos.node()->enclosingBlockFlowElement();
+ if (cb == pos.node())
+ appendNode(node, cb);
+ else
+ insertNodeBefore(node, pos.node());
+}
+
+void InsertLineBreakCommand::doApply()
+{
+ deleteSelection();
+ Selection selection = endingSelection();
+
+ ElementImpl *breakNode = createBreakElement(document());
+ NodeImpl *nodeToInsert = breakNode;
+
+ Position pos(selection.start().upstream());
+
+ pos = positionOutsideContainingSpecialElement(pos);
+
+ bool atStart = pos.offset() <= pos.node()->caretMinOffset();
+ bool atEnd = pos.offset() >= pos.node()->caretMaxOffset();
+ bool atEndOfBlock = isEndOfBlock(VisiblePosition(pos, selection.startAffinity()));
+
+ if (atEndOfBlock) {
+ LOG(Editing, "input newline case 1");
+ // Check for a trailing BR. If there isn't one, we'll need to insert an "extra" one.
+ // This makes the "real" BR we want to insert appear in the rendering without any
+ // significant side effects (and no real worries either since you can't arrow past
+ // this extra one.
+ if (pos.node()->id() == ID_BR && pos.offset() == 0) {
+ // Already placed in a trailing BR. Insert "real" BR before it and leave the selection alone.
+ insertNodeBefore(nodeToInsert, pos.node());
+ }
+ else {
+ NodeImpl *next = pos.node()->traverseNextNode();
+ bool hasTrailingBR = next && next->id() == ID_BR && pos.node()->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
+ insertNodeAfterPosition(nodeToInsert, pos);
+ if (hasTrailingBR) {
+ setEndingSelection(Selection(Position(next, 0), DOWNSTREAM));
+ }
+ else if (!document()->inStrictMode()) {
+ // Insert an "extra" BR at the end of the block.
+ ElementImpl *extraBreakNode = createBreakElement(document());
+ insertNodeAfter(extraBreakNode, nodeToInsert);
+ setEndingSelection(Position(extraBreakNode, 0), DOWNSTREAM);
+ }
+ }
+ }
+ else if (atStart) {
+ LOG(Editing, "input newline case 2");
+ // Insert node before downstream position, and place caret there as well.
+ Position endingPosition = pos.downstream();
+ insertNodeBeforePosition(nodeToInsert, endingPosition);
+ setEndingSelection(endingPosition, DOWNSTREAM);
+ }
+ else if (atEnd) {
+ LOG(Editing, "input newline case 3");
+ // Insert BR after this node. Place caret in the position that is downstream
+ // of the current position, reckoned before inserting the BR in between.
+ Position endingPosition = pos.downstream();
+ insertNodeAfterPosition(nodeToInsert, pos);
+ setEndingSelection(endingPosition, DOWNSTREAM);
+ }
+ else {
+ // Split a text node
+ LOG(Editing, "input newline case 4");
+ ASSERT(pos.node()->isTextNode());
+
+ // Do the split
+ int exceptionCode = 0;
+ TextImpl *textNode = static_cast<TextImpl *>(pos.node());
+ TextImpl *textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), exceptionCode));
+ deleteTextFromNode(textNode, 0, pos.offset());
+ insertNodeBefore(textBeforeNode, textNode);
+ insertNodeBefore(nodeToInsert, textNode);
+ Position endingPosition = Position(textNode, 0);
+
+ // Handle whitespace that occurs after the split
+ document()->updateLayout();
+ if (!endingPosition.isRenderedCharacter()) {
+ // Clear out all whitespace and insert one non-breaking space
+ deleteInsignificantTextDownstream(endingPosition);
+ insertTextIntoNode(textNode, 0, nonBreakingSpaceString());
+ }
+
+ setEndingSelection(endingPosition, DOWNSTREAM);
+ }
+
+ // Handle the case where there is a typing style.
+ // FIXME: Improve typing style.
+ // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
+
+ CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
+
+ if (typingStyle && typingStyle->length() > 0) {
+ Selection selectionBeforeStyle = endingSelection();
+
+ DOM::RangeImpl *rangeAroundNode = document()->createRange();
+ int exception = 0;
+ rangeAroundNode->selectNode(nodeToInsert, exception);
+
+ // affinity is not really important since this is a temp selection
+ // just for calling applyStyle
+ setEndingSelection(Selection(rangeAroundNode, khtml::SEL_DEFAULT_AFFINITY, khtml::SEL_DEFAULT_AFFINITY));
+ applyStyle(typingStyle);
+
+ setEndingSelection(selectionBeforeStyle);
+ }
+
+ rebalanceWhitespace();
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __insert_line_break_command_h__
+#define __insert_line_break_command_h__
+
+#include "composite_edit_command.h"
+
+namespace khtml {
+
+class InsertLineBreakCommand : public CompositeEditCommand
+{
+public:
+ InsertLineBreakCommand(DOM::DocumentImpl *document);
+
+ virtual void doApply();
+
+private:
+ virtual bool preservesTypingStyle() const;
+ void insertNodeAfterPosition(DOM::NodeImpl *node, const DOM::Position &pos);
+ void insertNodeBeforePosition(DOM::NodeImpl *node, const DOM::Position &pos);
+};
+
+} // namespace khtml
+
+#endif // __insert_line_break_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "insert_paragraph_separator_command.h"
+
+#include "htmlediting.h"
+#include "visible_position.h"
+#include "visible_units.h"
+
+#include "css/css_computedstyle.h"
+#include "css/css_valueimpl.h"
+#include "misc/htmltags.h"
+#include "xml/dom_docimpl.h"
+#include "xml/dom_elementimpl.h"
+#include "xml/dom_textimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#include "KWQLogging.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#define LOG(channel, formatAndArgs...) ((void)0)
+#endif
+
+using DOM::CSSComputedStyleDeclarationImpl;
+using DOM::DocumentImpl;
+using DOM::ElementImpl;
+using DOM::NodeImpl;
+using DOM::Position;
+using DOM::TextImpl;
+
+namespace khtml {
+
+InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(DocumentImpl *document)
+ : CompositeEditCommand(document), m_style(0)
+{
+}
+
+InsertParagraphSeparatorCommand::~InsertParagraphSeparatorCommand()
+{
+ derefNodesInList(clonedNodes);
+ if (m_style)
+ m_style->deref();
+}
+
+bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
+{
+ return true;
+}
+
+ElementImpl *InsertParagraphSeparatorCommand::createParagraphElement()
+{
+ ElementImpl *element = createDefaultParagraphElement(document());
+ element->ref();
+ clonedNodes.append(element);
+ return element;
+}
+
+void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
+{
+ // It is only important to set a style to apply later if we're at the boundaries of
+ // a paragraph. Otherwise, content that is moved as part of the work of the command
+ // will lend their styles to the new paragraph without any extra work needed.
+ VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
+ if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos))
+ return;
+
+ if (m_style)
+ m_style->deref();
+ m_style = styleAtPosition(pos);
+ m_style->ref();
+}
+
+void InsertParagraphSeparatorCommand::applyStyleAfterInsertion()
+{
+ // FIXME: Improve typing style.
+ // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
+ if (!m_style)
+ return;
+
+ CSSComputedStyleDeclarationImpl endingStyle(endingSelection().start().node());
+ endingStyle.diff(m_style);
+ if (m_style->length() > 0) {
+ applyStyle(m_style);
+ }
+}
+
+void InsertParagraphSeparatorCommand::doApply()
+{
+ bool splitText = false;
+ Selection selection = endingSelection();
+ if (selection.isNone())
+ return;
+
+ Position pos = selection.start();
+ EAffinity affinity = selection.startAffinity();
+
+ // Delete the current selection.
+ if (selection.isRange()) {
+ calculateStyleBeforeInsertion(pos);
+ deleteSelection(false, false);
+ pos = endingSelection().start();
+ affinity = endingSelection().startAffinity();
+ }
+
+ pos = positionOutsideContainingSpecialElement(pos);
+
+ calculateStyleBeforeInsertion(pos);
+
+ // Find the start block.
+ NodeImpl *startNode = pos.node();
+ NodeImpl *startBlock = startNode->enclosingBlockFlowElement();
+ if (!startBlock || !startBlock->parentNode())
+ return;
+
+ VisiblePosition visiblePos(pos, affinity);
+ bool isFirstInBlock = isStartOfBlock(visiblePos);
+ bool isLastInBlock = isEndOfBlock(visiblePos);
+ bool startBlockIsRoot = startBlock == startBlock->rootEditableElement();
+
+ // This is the block that is going to be inserted.
+ NodeImpl *blockToInsert = startBlockIsRoot ? createParagraphElement() : startBlock->cloneNode(false);
+
+ //---------------------------------------------------------------------
+ // Handle empty block case.
+ if (isFirstInBlock && isLastInBlock) {
+ LOG(Editing, "insert paragraph separator: empty block case");
+ if (startBlockIsRoot) {
+ NodeImpl *extraBlock = createParagraphElement();
+ appendNode(extraBlock, startBlock);
+ appendBlockPlaceholder(extraBlock);
+ appendNode(blockToInsert, startBlock);
+ }
+ else {
+ insertNodeAfter(blockToInsert, startBlock);
+ }
+ appendBlockPlaceholder(blockToInsert);
+ setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
+ applyStyleAfterInsertion();
+ return;
+ }
+
+ //---------------------------------------------------------------------
+ // Handle case when position is in the last visible position in its block.
+ if (isLastInBlock) {
+ LOG(Editing, "insert paragraph separator: last in block case");
+ if (startBlockIsRoot)
+ appendNode(blockToInsert, startBlock);
+ else
+ insertNodeAfter(blockToInsert, startBlock);
+ appendBlockPlaceholder(blockToInsert);
+ setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
+ applyStyleAfterInsertion();
+ return;
+ }
+
+ //---------------------------------------------------------------------
+ // Handle case when position is in the first visible position in its block.
+ // and similar case where upstream position is in another block.
+ bool prevInDifferentBlock = !inSameBlock(visiblePos, visiblePos.previous());
+
+ if (prevInDifferentBlock || isFirstInBlock) {
+ LOG(Editing, "insert paragraph separator: first in block case");
+ pos = pos.downstream();
+ pos = positionOutsideContainingSpecialElement(pos);
+ Position refPos;
+ NodeImpl *refNode;
+ if (isFirstInBlock && !startBlockIsRoot) {
+ refNode = startBlock;
+ } else if (pos.node() == startBlock && startBlockIsRoot) {
+ ASSERT(startBlock->childNode(pos.offset())); // must be true or we'd be in the end of block case
+ refNode = startBlock->childNode(pos.offset());
+ } else {
+ refNode = pos.node();
+ }
+
+ insertNodeBefore(blockToInsert, refNode);
+ appendBlockPlaceholder(blockToInsert);
+ setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
+ applyStyleAfterInsertion();
+ setEndingSelection(pos, DOWNSTREAM);
+ return;
+ }
+
+ //---------------------------------------------------------------------
+ // Handle the (more complicated) general case,
+
+ LOG(Editing, "insert paragraph separator: general case");
+
+ // Check if pos.node() is a <br>. If it is, and the document is in quirks mode,
+ // then this <br> will collapse away when we add a block after it. Add an extra <br>.
+ if (!document()->inStrictMode()) {
+ Position upstreamPos = pos.upstream();
+ if (upstreamPos.node()->id() == ID_BR)
+ insertNodeAfter(createBreakElement(document()), upstreamPos.node());
+ }
+
+ // Move downstream. Typing style code will take care of carrying along the
+ // style of the upstream position.
+ pos = pos.downstream();
+ startNode = pos.node();
+
+ // Build up list of ancestors in between the start node and the start block.
+ if (startNode != startBlock) {
+ for (NodeImpl *n = startNode->parentNode(); n && n != startBlock; n = n->parentNode())
+ ancestors.prepend(n);
+ }
+
+ // Make sure we do not cause a rendered space to become unrendered.
+ // FIXME: We need the affinity for pos, but pos.downstream() does not give it
+ Position leadingWhitespace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
+ if (leadingWhitespace.isNotNull()) {
+ TextImpl *textNode = static_cast<TextImpl *>(leadingWhitespace.node());
+ replaceTextInNode(textNode, leadingWhitespace.offset(), 1, nonBreakingSpaceString());
+ }
+
+ // Split at pos if in the middle of a text node.
+ if (startNode->isTextNode()) {
+ TextImpl *textNode = static_cast<TextImpl *>(startNode);
+ bool atEnd = (unsigned long)pos.offset() >= textNode->length();
+ if (pos.offset() > 0 && !atEnd) {
+ splitTextNode(textNode, pos.offset());
+ pos = Position(startNode, 0);
+ splitText = true;
+ }
+ }
+
+ // Put the added block in the tree.
+ if (startBlockIsRoot) {
+ appendNode(blockToInsert, startBlock);
+ } else {
+ insertNodeAfter(blockToInsert, startBlock);
+ }
+
+ // Make clones of ancestors in between the start node and the start block.
+ NodeImpl *parent = blockToInsert;
+ for (QPtrListIterator<NodeImpl> it(ancestors); it.current(); ++it) {
+ NodeImpl *child = it.current()->cloneNode(false); // shallow clone
+ child->ref();
+ clonedNodes.append(child);
+ appendNode(child, parent);
+ parent = child;
+ }
+
+ // Insert a block placeholder if the next visible position is in a different paragraph,
+ // because we know that there will be no content on the first line of the new block
+ // before the first block child. So, we need the placeholder to "hold the first line open".
+ VisiblePosition next = visiblePos.next();
+ if (!next.isNull() && !inSameBlock(visiblePos, next))
+ appendBlockPlaceholder(blockToInsert);
+
+ // Move the start node and the siblings of the start node.
+ if (startNode != startBlock) {
+ NodeImpl *n = startNode;
+ if (pos.offset() >= startNode->caretMaxOffset()) {
+ n = startNode->nextSibling();
+ }
+ while (n && n != blockToInsert) {
+ NodeImpl *next = n->nextSibling();
+ removeNode(n);
+ appendNode(n, parent);
+ n = next;
+ }
+ }
+
+ // Move everything after the start node.
+ NodeImpl *leftParent = ancestors.last();
+ while (leftParent && leftParent != startBlock) {
+ parent = parent->parentNode();
+ NodeImpl *n = leftParent->nextSibling();
+ while (n && n != blockToInsert) {
+ NodeImpl *next = n->nextSibling();
+ removeNode(n);
+ appendNode(n, parent);
+ n = next;
+ }
+ leftParent = leftParent->parentNode();
+ }
+
+ // Handle whitespace that occurs after the split
+ if (splitText) {
+ document()->updateLayout();
+ pos = Position(startNode, 0);
+ if (!pos.isRenderedCharacter()) {
+ // Clear out all whitespace and insert one non-breaking space
+ ASSERT(startNode && startNode->isTextNode());
+ deleteInsignificantTextDownstream(pos);
+ insertTextIntoNode(static_cast<TextImpl *>(startNode), 0, nonBreakingSpaceString());
+ }
+ }
+
+ setEndingSelection(Position(blockToInsert, 0), DOWNSTREAM);
+ rebalanceWhitespace();
+ applyStyleAfterInsertion();
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __insert_paragraph_separator_command_h__
+#define __insert_paragraph_separator_command_h__
+
+#include "composite_edit_command.h"
+#include "qptrlist.h"
+
+namespace khtml {
+
+class InsertParagraphSeparatorCommand : public CompositeEditCommand
+{
+public:
+ InsertParagraphSeparatorCommand(DOM::DocumentImpl *document);
+ virtual ~InsertParagraphSeparatorCommand();
+
+ virtual void doApply();
+
+private:
+ DOM::ElementImpl *createParagraphElement();
+ void calculateStyleBeforeInsertion(const DOM::Position &);
+ void applyStyleAfterInsertion();
+
+ virtual bool preservesTypingStyle() const;
+
+ QPtrList<DOM::NodeImpl> ancestors;
+ QPtrList<DOM::NodeImpl> clonedNodes;
+ DOM::CSSMutableStyleDeclarationImpl *m_style;
+};
+
+} // namespace khtml
+
+#endif // __insert_paragraph_separator_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "insert_text_command.h"
+
+#include "khtml_part.h"
+#include "htmlediting.h"
+#include "visible_position.h"
+#include "visible_text.h"
+#include "visible_units.h"
+#include "xml/dom_docimpl.h"
+#include "xml/dom_position.h"
+#include "xml/dom_textimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#include "KWQLogging.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#define LOG(channel, formatAndArgs...) ((void)0)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::NodeImpl;
+using DOM::Position;
+using DOM::TextImpl;
+using DOM::DOMString;
+using DOM::CSSMutableStyleDeclarationImpl;
+
+namespace khtml {
+
+InsertTextCommand::InsertTextCommand(DocumentImpl *document)
+ : CompositeEditCommand(document), m_charactersAdded(0)
+{
+}
+
+void InsertTextCommand::doApply()
+{
+}
+
+Position InsertTextCommand::prepareForTextInsertion(bool adjustDownstream)
+{
+ // Prepare for text input by looking at the current position.
+ // It may be necessary to insert a text node to receive characters.
+ Selection selection = endingSelection();
+ ASSERT(selection.isCaret());
+
+ Position pos = selection.start();
+ if (adjustDownstream)
+ pos = pos.downstream();
+ else
+ pos = pos.upstream();
+
+ Selection typingStyleRange;
+
+ pos = positionOutsideContainingSpecialElement(pos);
+
+ if (!pos.node()->isTextNode()) {
+ NodeImpl *textNode = document()->createEditingTextNode("");
+ NodeImpl *nodeToInsert = textNode;
+
+ // Now insert the node in the right place
+ if (pos.node()->rootEditableElement() != NULL) {
+ LOG(Editing, "prepareForTextInsertion case 1");
+ insertNodeAt(nodeToInsert, pos.node(), pos.offset());
+ }
+ else if (pos.node()->caretMinOffset() == pos.offset()) {
+ LOG(Editing, "prepareForTextInsertion case 2");
+ insertNodeBefore(nodeToInsert, pos.node());
+ }
+ else if (pos.node()->caretMaxOffset() == pos.offset()) {
+ LOG(Editing, "prepareForTextInsertion case 3");
+ insertNodeAfter(nodeToInsert, pos.node());
+ }
+ else
+ ASSERT_NOT_REACHED();
+
+ pos = Position(textNode, 0);
+ }
+
+ return pos;
+}
+
+static const int spacesPerTab = 4;
+
+static inline bool isNBSP(const QChar &c)
+{
+ return c.unicode() == 0xa0;
+}
+
+void InsertTextCommand::input(const DOMString &text, bool selectInsertedText)
+{
+ assert(text.find('\n') == -1);
+
+ Selection selection = endingSelection();
+ bool adjustDownstream = isStartOfLine(VisiblePosition(selection.start().downstream(), DOWNSTREAM));
+
+ // Delete the current selection, or collapse whitespace, as needed
+ if (selection.isRange())
+ deleteSelection();
+
+ // Delete any insignificant text that could get in the way of whitespace turning
+ // out correctly after the insertion.
+ selection = endingSelection();
+ deleteInsignificantTextDownstream(selection.end().trailingWhitespacePosition(selection.endAffinity()));
+
+ // Make sure the document is set up to receive text
+ Position startPosition = prepareForTextInsertion(adjustDownstream);
+
+ Position endPosition;
+
+ TextImpl *textNode = static_cast<TextImpl *>(startPosition.node());
+ long offset = startPosition.offset();
+
+ // Now that we are about to add content, check to see if a placeholder element
+ // can be removed.
+ removeBlockPlaceholder(textNode->enclosingBlockFlowElement());
+
+ // These are temporary implementations for inserting adjoining spaces
+ // into a document. We are working on a CSS-related whitespace solution
+ // that will replace this some day. We hope.
+ if (text == "\t") {
+ // Treat a tab like a number of spaces. This seems to be the HTML editing convention,
+ // although the number of spaces varies (we choose four spaces).
+ // Note that there is no attempt to make this work like a real tab stop, it is merely
+ // a set number of spaces. This also seems to be the HTML editing convention.
+ for (int i = 0; i < spacesPerTab; i++) {
+ insertSpace(textNode, offset);
+ rebalanceWhitespace();
+ document()->updateLayout();
+ }
+
+ endPosition = Position(textNode, offset + spacesPerTab);
+
+ m_charactersAdded += spacesPerTab;
+ }
+ else if (text == " ") {
+ insertSpace(textNode, offset);
+ endPosition = Position(textNode, offset + 1);
+
+ m_charactersAdded++;
+ rebalanceWhitespace();
+ }
+ else {
+ const DOMString &existingText = textNode->data();
+ if (textNode->length() >= 2 && offset >= 2 && isNBSP(existingText[offset - 1]) && !isCollapsibleWhitespace(existingText[offset - 2])) {
+ // DOM looks like this:
+ // character nbsp caret
+ // As we are about to insert a non-whitespace character at the caret
+ // convert the nbsp to a regular space.
+ // EDIT FIXME: This needs to be improved some day to convert back only
+ // those nbsp's added by the editor to make rendering come out right.
+ replaceTextInNode(textNode, offset - 1, 1, " ");
+ }
+ insertTextIntoNode(textNode, offset, text);
+ endPosition = Position(textNode, offset + text.length());
+
+ m_charactersAdded += text.length();
+ }
+
+ setEndingSelection(Selection(startPosition, DOWNSTREAM, endPosition, SEL_DEFAULT_AFFINITY));
+
+ // Handle the case where there is a typing style.
+ // FIXME: Improve typing style.
+ // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
+ CSSMutableStyleDeclarationImpl *typingStyle = document()->part()->typingStyle();
+ if (typingStyle && typingStyle->length() > 0)
+ applyStyle(typingStyle);
+
+ if (!selectInsertedText)
+ setEndingSelection(endingSelection().end(), endingSelection().endAffinity());
+}
+
+void InsertTextCommand::insertSpace(TextImpl *textNode, unsigned long offset)
+{
+ ASSERT(textNode);
+
+ DOMString text(textNode->data());
+
+ // count up all spaces and newlines in front of the caret
+ // delete all collapsed ones
+ // this will work out OK since the offset we have been passed has been upstream-ized
+ int count = 0;
+ for (unsigned int i = offset; i < text.length(); i++) {
+ if (isCollapsibleWhitespace(text[i]))
+ count++;
+ else
+ break;
+ }
+ if (count > 0) {
+ // By checking the character at the downstream position, we can
+ // check if there is a rendered WS at the caret
+ Position pos(textNode, offset);
+ Position downstream = pos.downstream();
+ if (downstream.offset() < (long)text.length() && isCollapsibleWhitespace(text[downstream.offset()]))
+ count--; // leave this WS in
+ if (count > 0)
+ deleteTextFromNode(textNode, offset, count);
+ }
+
+ if (offset > 0 && offset <= text.length() - 1 && !isCollapsibleWhitespace(text[offset]) && !isCollapsibleWhitespace(text[offset - 1])) {
+ // insert a "regular" space
+ insertTextIntoNode(textNode, offset, " ");
+ return;
+ }
+
+ if (text.length() >= 2 && offset >= 2 && isNBSP(text[offset - 2]) && isNBSP(text[offset - 1])) {
+ // DOM looks like this:
+ // nbsp nbsp caret
+ // insert a space between the two nbsps
+ insertTextIntoNode(textNode, offset - 1, " ");
+ return;
+ }
+
+ // insert an nbsp
+ insertTextIntoNode(textNode, offset, nonBreakingSpaceString());
+}
+
+bool InsertTextCommand::isInsertTextCommand() const
+{
+ return true;
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __insert_text_command_h__
+#define __insert_text_command_h__
+
+#include "composite_edit_command.h"
+
+namespace khtml {
+
+class InsertTextCommand : public CompositeEditCommand
+{
+public:
+ InsertTextCommand(DOM::DocumentImpl *document);
+
+ virtual void doApply();
+
+ void deleteCharacter();
+ void input(const DOM::DOMString &text, bool selectInsertedText = false);
+
+ unsigned long charactersAdded() const { return m_charactersAdded; }
+
+private:
+ virtual bool isInsertTextCommand() const;
+
+ DOM::Position prepareForTextInsertion(bool adjustDownstream);
+ void insertSpace(DOM::TextImpl *textNode, unsigned long offset);
+
+ unsigned long m_charactersAdded;
+};
+
+} // namespace khtml
+
+#endif // __insert_text_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "join_text_nodes_command.h"
+
+#include "xml/dom_docimpl.h"
+#include "xml/dom_textimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::TextImpl;
+
+namespace khtml {
+
+JoinTextNodesCommand::JoinTextNodesCommand(DocumentImpl *document, TextImpl *text1, TextImpl *text2)
+ : EditCommand(document), m_text1(text1), m_text2(text2)
+{
+ ASSERT(m_text1);
+ ASSERT(m_text2);
+ ASSERT(m_text1->nextSibling() == m_text2);
+ ASSERT(m_text1->length() > 0);
+ ASSERT(m_text2->length() > 0);
+
+ m_text1->ref();
+ m_text2->ref();
+}
+
+JoinTextNodesCommand::~JoinTextNodesCommand()
+{
+ ASSERT(m_text1);
+ m_text1->deref();
+ ASSERT(m_text2);
+ m_text2->deref();
+}
+
+void JoinTextNodesCommand::doApply()
+{
+ ASSERT(m_text1);
+ ASSERT(m_text2);
+ ASSERT(m_text1->nextSibling() == m_text2);
+
+ int exceptionCode = 0;
+ m_text2->insertData(0, m_text1->data(), exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ m_text2->parentNode()->removeChild(m_text1, exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ m_offset = m_text1->length();
+}
+
+void JoinTextNodesCommand::doUnapply()
+{
+ ASSERT(m_text2);
+ ASSERT(m_offset > 0);
+
+ int exceptionCode = 0;
+
+ m_text2->deleteData(0, m_offset, exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ ASSERT(m_text2->previousSibling()->isTextNode());
+ ASSERT(m_text2->previousSibling() == m_text1);
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __join_text_nodes_command_h__
+#define __join_text_nodes_command_h__
+
+#include "edit_command.h"
+
+namespace DOM {
+ class TextImpl;
+}
+
+namespace khtml {
+
+class JoinTextNodesCommand : public EditCommand
+{
+public:
+ JoinTextNodesCommand(DOM::DocumentImpl *, DOM::TextImpl *, DOM::TextImpl *);
+ virtual ~JoinTextNodesCommand();
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+ DOM::TextImpl *firstNode() const { return m_text1; }
+ DOM::TextImpl *secondNode() const { return m_text2; }
+
+private:
+ DOM::TextImpl *m_text1;
+ DOM::TextImpl *m_text2;
+ unsigned long m_offset;
+};
+
+} // namespace khtml
+
+#endif // __join_text_nodes_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "merge_identical_elements_command.h"
+
+#include "xml/dom_docimpl.h"
+#include "xml/dom_elementimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::ElementImpl;
+
+namespace khtml {
+
+MergeIdenticalElementsCommand::MergeIdenticalElementsCommand(DOM::DocumentImpl *document, DOM::ElementImpl *first, DOM::ElementImpl *second)
+ : EditCommand(document), m_element1(first), m_element2(second), m_atChild(0)
+{
+ ASSERT(m_element1);
+ ASSERT(m_element2);
+
+ m_element1->ref();
+ m_element2->ref();
+}
+
+MergeIdenticalElementsCommand::~MergeIdenticalElementsCommand()
+{
+ if (m_atChild)
+ m_atChild->deref();
+
+ ASSERT(m_element1);
+ m_element1->deref();
+ ASSERT(m_element2);
+ m_element2->deref();
+}
+
+void MergeIdenticalElementsCommand::doApply()
+{
+ ASSERT(m_element1);
+ ASSERT(m_element2);
+ ASSERT(m_element1->nextSibling() == m_element2);
+
+ int exceptionCode = 0;
+
+ if (!m_atChild) {
+ m_atChild = m_element2->firstChild();
+ m_atChild->ref();
+ }
+
+ while (m_element1->lastChild()) {
+ m_element2->insertBefore(m_element1->lastChild(), m_element2->firstChild(), exceptionCode);
+ ASSERT(exceptionCode == 0);
+ }
+
+ m_element2->parentNode()->removeChild(m_element1, exceptionCode);
+ ASSERT(exceptionCode == 0);
+}
+
+void MergeIdenticalElementsCommand::doUnapply()
+{
+ ASSERT(m_element1);
+ ASSERT(m_element2);
+
+ int exceptionCode = 0;
+
+ m_element2->parent()->insertBefore(m_element1, m_element2, exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ while (m_element2->firstChild() != m_atChild) {
+ ASSERT(m_element2->firstChild());
+ m_element1->appendChild(m_element2->firstChild(), exceptionCode);
+ ASSERT(exceptionCode == 0);
+ }
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __merge_identical_elements_command_h__
+#define __merge_identical_elements_command_h__
+
+#include "edit_command.h"
+
+namespace khtml {
+
+class MergeIdenticalElementsCommand : public EditCommand
+{
+public:
+ MergeIdenticalElementsCommand(DOM::DocumentImpl *, DOM::ElementImpl *first, DOM::ElementImpl *second);
+ virtual ~MergeIdenticalElementsCommand();
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+private:
+ DOM::ElementImpl *m_element1;
+ DOM::ElementImpl *m_element2;
+ DOM::NodeImpl *m_atChild;
+};
+
+} // namespace khtml
+
+#endif // __merge_identical_elements_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "move_selection_command.h"
+#include "replace_selection_command.h"
+
+#include "xml/dom_docimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::DocumentFragmentImpl;
+using DOM::NodeImpl;
+using DOM::Position;
+
+namespace khtml {
+
+MoveSelectionCommand::MoveSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, Position &position, bool smartMove)
+ : CompositeEditCommand(document), m_fragment(fragment), m_position(position), m_smartMove(smartMove)
+{
+ ASSERT(m_fragment);
+ m_fragment->ref();
+}
+
+MoveSelectionCommand::~MoveSelectionCommand()
+{
+ ASSERT(m_fragment);
+ m_fragment->deref();
+}
+
+void MoveSelectionCommand::doApply()
+{
+ Selection selection = endingSelection();
+ ASSERT(selection.isRange());
+
+ Position pos = m_position;
+ if (pos.isNull())
+ return;
+
+ // Update the position otherwise it may become invalid after the selection is deleted.
+ NodeImpl *positionNode = m_position.node();
+ long positionOffset = m_position.offset();
+ Position selectionEnd = selection.end();
+ long selectionEndOffset = selectionEnd.offset();
+ if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) {
+ positionOffset -= selectionEndOffset;
+ Position selectionStart = selection.start();
+ if (selectionStart.node() == positionNode) {
+ positionOffset += selectionStart.offset();
+ }
+ pos = Position(positionNode, positionOffset);
+ }
+
+ deleteSelection(m_smartMove);
+
+ // If the node for the destination has been removed as a result of the deletion,
+ // set the destination to the ending point after the deletion.
+ // Fixes: <rdar://problem/3910425> REGRESSION (Mail): Crash in ReplaceSelectionCommand;
+ // selection is empty, leading to null deref
+ if (!pos.node()->inDocument())
+ pos = endingSelection().start();
+
+ setEndingSelection(pos, endingSelection().startAffinity());
+ EditCommandPtr cmd(new ReplaceSelectionCommand(document(), m_fragment, true, m_smartMove));
+ applyCommandToComposite(cmd);
+}
+
+EditAction MoveSelectionCommand::editingAction() const
+{
+ return EditActionDrag;
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __move_selection_command_h__
+#define __move_selection_command_h__
+
+#include "composite_edit_command.h"
+
+namespace DOM {
+ class DocumentFragmentImpl;
+}
+
+namespace khtml {
+
+class MoveSelectionCommand : public CompositeEditCommand
+{
+public:
+ MoveSelectionCommand(DOM::DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, DOM::Position &position, bool smartMove=false);
+ virtual ~MoveSelectionCommand();
+
+ virtual void doApply();
+ virtual EditAction editingAction() const;
+
+private:
+ DOM::DocumentFragmentImpl *m_fragment;
+ DOM::Position m_position;
+ bool m_smartMove;
+};
+
+} // namespace khtml
+
+#endif // __move_selection_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "rebalance_whitespace_command.h"
+
+#include "htmlediting.h"
+#include "visible_text.h"
+#include "xml/dom_docimpl.h"
+#include "xml/dom_textimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DOMString;
+using DOM::DocumentImpl;
+using DOM::Position;
+using DOM::TextImpl;
+
+namespace khtml {
+
+RebalanceWhitespaceCommand::RebalanceWhitespaceCommand(DocumentImpl *document, const Position &pos)
+ : EditCommand(document), m_position(pos), m_upstreamOffset(InvalidOffset), m_downstreamOffset(InvalidOffset)
+{
+}
+
+RebalanceWhitespaceCommand::~RebalanceWhitespaceCommand()
+{
+}
+
+static inline bool isNBSP(const QChar &c)
+{
+ return c.unicode() == 0xa0;
+}
+
+void RebalanceWhitespaceCommand::doApply()
+{
+ static DOMString space(" ");
+
+ if (m_position.isNull() || !m_position.node()->isTextNode())
+ return;
+
+ TextImpl *textNode = static_cast<TextImpl *>(m_position.node());
+ DOMString text = textNode->data();
+ if (text.length() == 0)
+ return;
+
+ // find upstream offset
+ long upstream = m_position.offset();
+ while (upstream > 0 && isCollapsibleWhitespace(text[upstream - 1]) || isNBSP(text[upstream - 1])) {
+ upstream--;
+ m_upstreamOffset = upstream;
+ }
+
+ // find downstream offset
+ long downstream = m_position.offset();
+ while ((unsigned)downstream < text.length() && isCollapsibleWhitespace(text[downstream]) || isNBSP(text[downstream])) {
+ downstream++;
+ m_downstreamOffset = downstream;
+ }
+
+ if (m_upstreamOffset == InvalidOffset && m_downstreamOffset == InvalidOffset)
+ return;
+
+ m_upstreamOffset = upstream;
+ m_downstreamOffset = downstream;
+ long length = m_downstreamOffset - m_upstreamOffset;
+
+ m_beforeString = text.substring(m_upstreamOffset, length);
+
+ // The following loop figures out a "rebalanced" whitespace string for any length
+ // string, and takes into account the special cases that need to handled for the
+ // start and end of strings (i.e. first and last character must be an nbsp.
+ long i = m_upstreamOffset;
+ while (i < m_downstreamOffset) {
+ long add = (m_downstreamOffset - i) % 3;
+ switch (add) {
+ case 0:
+ m_afterString += nonBreakingSpaceString();
+ m_afterString += space;
+ m_afterString += nonBreakingSpaceString();
+ add = 3;
+ break;
+ case 1:
+ if (i == 0 || (unsigned)i + 1 == text.length()) // at start or end of string
+ m_afterString += nonBreakingSpaceString();
+ else
+ m_afterString += space;
+ break;
+ case 2:
+ if ((unsigned)i + 2 == text.length()) {
+ // at end of string
+ m_afterString += nonBreakingSpaceString();
+ m_afterString += nonBreakingSpaceString();
+ }
+ else {
+ m_afterString += nonBreakingSpaceString();
+ m_afterString += space;
+ }
+ break;
+ }
+ i += add;
+ }
+
+ text.remove(m_upstreamOffset, length);
+ text.insert(m_afterString, m_upstreamOffset);
+}
+
+void RebalanceWhitespaceCommand::doUnapply()
+{
+ if (m_upstreamOffset == InvalidOffset && m_downstreamOffset == InvalidOffset)
+ return;
+
+ ASSERT(m_position.node()->isTextNode());
+ TextImpl *textNode = static_cast<TextImpl *>(m_position.node());
+ DOMString text = textNode->data();
+ text.remove(m_upstreamOffset, m_afterString.length());
+ text.insert(m_beforeString, m_upstreamOffset);
+}
+
+bool RebalanceWhitespaceCommand::preservesTypingStyle() const
+{
+ return true;
+}
+
+} // namespace khtml
+
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __rebalance_whitespace_command_h__
+#define __rebalance_whitespace_command_h__
+
+#include "edit_command.h"
+#include "dom/dom_string.h"
+
+namespace khtml {
+
+class RebalanceWhitespaceCommand : public EditCommand
+{
+public:
+ RebalanceWhitespaceCommand(DOM::DocumentImpl *, const DOM::Position &);
+ virtual ~RebalanceWhitespaceCommand();
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+private:
+ enum { InvalidOffset = -1 };
+
+ virtual bool preservesTypingStyle() const;
+
+ DOM::DOMString m_beforeString;
+ DOM::DOMString m_afterString;
+ DOM::Position m_position;
+ long m_upstreamOffset;
+ long m_downstreamOffset;
+};
+
+} // namespace khtml
+
+#endif // __rebalance_whitespace_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "remove_css_property_command.h"
+
+#include "css/css_valueimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::CSSStyleDeclarationImpl;
+using DOM::DOMString;
+using DOM::DocumentImpl;
+
+namespace khtml {
+
+RemoveCSSPropertyCommand::RemoveCSSPropertyCommand(DocumentImpl *document, CSSStyleDeclarationImpl *decl, int property)
+ : EditCommand(document), m_decl(decl->makeMutable()), m_property(property), m_important(false)
+{
+ ASSERT(m_decl);
+ m_decl->ref();
+}
+
+RemoveCSSPropertyCommand::~RemoveCSSPropertyCommand()
+{
+ ASSERT(m_decl);
+ m_decl->deref();
+}
+
+void RemoveCSSPropertyCommand::doApply()
+{
+ ASSERT(m_decl);
+
+ m_oldValue = m_decl->getPropertyValue(m_property);
+ ASSERT(!m_oldValue.isNull());
+
+ m_important = m_decl->getPropertyPriority(m_property);
+ m_decl->removeProperty(m_property);
+}
+
+void RemoveCSSPropertyCommand::doUnapply()
+{
+ ASSERT(m_decl);
+ ASSERT(!m_oldValue.isNull());
+
+ m_decl->setProperty(m_property, m_oldValue, m_important);
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __remove_css_property_command_h__
+#define __remove_css_property_command_h__
+
+#include "edit_command.h"
+
+#include "dom/dom_string.h"
+
+namespace DOM {
+ class CSSStyleDeclarationImpl;
+}
+
+namespace khtml {
+
+class RemoveCSSPropertyCommand : public EditCommand
+{
+public:
+ RemoveCSSPropertyCommand(DOM::DocumentImpl *, DOM::CSSStyleDeclarationImpl *, int property);
+ virtual ~RemoveCSSPropertyCommand();
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+ DOM::CSSMutableStyleDeclarationImpl *styleDeclaration() const { return m_decl; }
+ int property() const { return m_property; }
+
+private:
+ DOM::CSSMutableStyleDeclarationImpl *m_decl;
+ int m_property;
+ DOM::DOMString m_oldValue;
+ bool m_important;
+};
+
+} // namespace khtml
+
+#endif // __remove_css_property_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "remove_node_attribute_command.h"
+
+#include "xml/dom_elementimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::ElementImpl;
+using DOM::NodeImpl;
+
+namespace khtml {
+
+RemoveNodeAttributeCommand::RemoveNodeAttributeCommand(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute)
+ : EditCommand(document), m_element(element), m_attribute(attribute)
+{
+ ASSERT(m_element);
+ m_element->ref();
+}
+
+RemoveNodeAttributeCommand::~RemoveNodeAttributeCommand()
+{
+ ASSERT(m_element);
+ m_element->deref();
+}
+
+void RemoveNodeAttributeCommand::doApply()
+{
+ ASSERT(m_element);
+
+ m_oldValue = m_element->getAttribute(m_attribute);
+ ASSERT(!m_oldValue.isNull());
+
+ int exceptionCode = 0;
+ m_element->removeAttribute(m_attribute, exceptionCode);
+ ASSERT(exceptionCode == 0);
+}
+
+void RemoveNodeAttributeCommand::doUnapply()
+{
+ ASSERT(m_element);
+ ASSERT(!m_oldValue.isNull());
+
+ int exceptionCode = 0;
+ m_element->setAttribute(m_attribute, m_oldValue.implementation(), exceptionCode);
+ ASSERT(exceptionCode == 0);
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __remove_node_attribute_command_h__
+#define __remove_node_attribute_command_h__
+
+#include "edit_command.h"
+
+#include "xml/dom_nodeimpl.h"
+
+namespace khtml {
+
+class RemoveNodeAttributeCommand : public EditCommand
+{
+public:
+ RemoveNodeAttributeCommand(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute);
+ virtual ~RemoveNodeAttributeCommand();
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+ DOM::ElementImpl *element() const { return m_element; }
+ DOM::NodeImpl::Id attribute() const { return m_attribute; }
+
+private:
+ DOM::ElementImpl *m_element;
+ DOM::NodeImpl::Id m_attribute;
+ DOM::DOMString m_oldValue;
+};
+
+} // namespace khtml
+
+#endif // __remove_node_attribute_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "remove_node_command.h"
+
+#include "xml/dom_nodeimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::NodeImpl;
+
+namespace khtml {
+
+RemoveNodeCommand::RemoveNodeCommand(DocumentImpl *document, NodeImpl *removeChild)
+ : EditCommand(document), m_parent(0), m_removeChild(removeChild), m_refChild(0)
+{
+ ASSERT(m_removeChild);
+ m_removeChild->ref();
+
+ m_parent = m_removeChild->parentNode();
+ ASSERT(m_parent);
+ m_parent->ref();
+
+ m_refChild = m_removeChild->nextSibling();
+ if (m_refChild)
+ m_refChild->ref();
+}
+
+RemoveNodeCommand::~RemoveNodeCommand()
+{
+ ASSERT(m_parent);
+ m_parent->deref();
+
+ ASSERT(m_removeChild);
+ m_removeChild->deref();
+
+ if (m_refChild)
+ m_refChild->deref();
+}
+
+void RemoveNodeCommand::doApply()
+{
+ ASSERT(m_parent);
+ ASSERT(m_removeChild);
+
+ int exceptionCode = 0;
+ m_parent->removeChild(m_removeChild, exceptionCode);
+ ASSERT(exceptionCode == 0);
+}
+
+void RemoveNodeCommand::doUnapply()
+{
+ ASSERT(m_parent);
+ ASSERT(m_removeChild);
+
+ int exceptionCode = 0;
+ m_parent->insertBefore(m_removeChild, m_refChild, exceptionCode);
+ ASSERT(exceptionCode == 0);
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __remove_node_command_h__
+#define __remove_node_command_h__
+
+#include "edit_command.h"
+
+namespace khtml {
+
+class RemoveNodeCommand : public EditCommand
+{
+public:
+ RemoveNodeCommand(DOM::DocumentImpl *, DOM::NodeImpl *);
+ virtual ~RemoveNodeCommand();
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+ DOM::NodeImpl *node() const { return m_removeChild; }
+
+private:
+ DOM::NodeImpl *m_parent;
+ DOM::NodeImpl *m_removeChild;
+ DOM::NodeImpl *m_refChild;
+};
+
+} // namespace khtml
+
+#endif // __remove_node_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "remove_node_preserving_children_command.h"
+
+#include "xml/dom_nodeimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::NodeImpl;
+
+namespace khtml {
+
+RemoveNodePreservingChildrenCommand::RemoveNodePreservingChildrenCommand(DocumentImpl *document, NodeImpl *node)
+ : CompositeEditCommand(document), m_node(node)
+{
+ ASSERT(m_node);
+ m_node->ref();
+}
+
+RemoveNodePreservingChildrenCommand::~RemoveNodePreservingChildrenCommand()
+{
+ ASSERT(m_node);
+ m_node->deref();
+}
+
+void RemoveNodePreservingChildrenCommand::doApply()
+{
+ while (NodeImpl* curr = node()->firstChild()) {
+ removeNode(curr);
+ insertNodeBefore(curr, node());
+ }
+ removeNode(node());
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __remove_node_preserving_children_command_h__
+#define __remove_node_preserving_children_command_h__
+
+#include "composite_edit_command.h"
+
+namespace khtml {
+
+class RemoveNodePreservingChildrenCommand : public CompositeEditCommand
+{
+public:
+ RemoveNodePreservingChildrenCommand(DOM::DocumentImpl *, DOM::NodeImpl *);
+ virtual ~RemoveNodePreservingChildrenCommand();
+
+ virtual void doApply();
+
+ DOM::NodeImpl *node() const { return m_node; }
+
+private:
+ DOM::NodeImpl *m_node;
+};
+
+} // namespace khtml
+
+#endif // __remove_node_preserving_children_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "replace_selection_command.h"
+
+#include "visible_position.h"
+#include "misc/htmlattrs.h"
+#include "misc/htmltags.h"
+#include "xml/dom_docimpl.h"
+#include "html_interchange.h"
+#include "xml/dom_textimpl.h"
+#include "html/html_elementimpl.h"
+#include "htmlediting.h"
+#include "css/css_valueimpl.h"
+#include "visible_units.h"
+#include "xml/dom_position.h"
+#include "khtml_part.h"
+#include "xml/dom2_rangeimpl.h"
+#include "rendering/render_object.h"
+#include "css/css_computedstyle.h"
+#include "css/cssproperties.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::CSSComputedStyleDeclarationImpl;
+using DOM::DocumentImpl;
+using DOM::NodeImpl;
+using DOM::DocumentFragmentImpl;
+using DOM::ElementImpl;
+using DOM::TextImpl;
+using DOM::DOMString;
+using DOM::HTMLElementImpl;
+using DOM::CSSMutableStyleDeclarationImpl;
+using DOM::Position;
+
+namespace khtml {
+
+ReplacementFragment::ReplacementFragment(DocumentImpl *document, DocumentFragmentImpl *fragment, bool matchStyle)
+ : m_document(document),
+ m_fragment(fragment),
+ m_matchStyle(matchStyle),
+ m_hasInterchangeNewlineAtStart(false),
+ m_hasInterchangeNewlineAtEnd(false),
+ m_hasMoreThanOneBlock(false)
+{
+ if (!m_document)
+ return;
+
+ if (!m_fragment) {
+ m_type = EmptyFragment;
+ return;
+ }
+
+ m_document->ref();
+ m_fragment->ref();
+
+ NodeImpl *firstChild = m_fragment->firstChild();
+ NodeImpl *lastChild = m_fragment->lastChild();
+
+ if (!firstChild) {
+ m_type = EmptyFragment;
+ return;
+ }
+
+ if (firstChild == lastChild && firstChild->isTextNode()) {
+ m_type = SingleTextNodeFragment;
+ return;
+ }
+
+ m_type = TreeFragment;
+
+ NodeImpl *node = m_fragment->firstChild();
+ NodeImpl *newlineAtStartNode = 0;
+ NodeImpl *newlineAtEndNode = 0;
+ while (node) {
+ NodeImpl *next = node->traverseNextNode();
+ if (isInterchangeNewlineNode(node)) {
+ if (next || node == m_fragment->firstChild()) {
+ m_hasInterchangeNewlineAtStart = true;
+ newlineAtStartNode = node;
+ }
+ else {
+ m_hasInterchangeNewlineAtEnd = true;
+ newlineAtEndNode = node;
+ }
+ }
+ else if (isInterchangeConvertedSpaceSpan(node)) {
+ NodeImpl *n = 0;
+ while ((n = node->firstChild())) {
+ n->ref();
+ removeNode(n);
+ insertNodeBefore(n, node);
+ n->deref();
+ }
+ removeNode(node);
+ if (n)
+ next = n->traverseNextNode();
+ }
+ node = next;
+ }
+
+ if (newlineAtStartNode)
+ removeNode(newlineAtStartNode);
+ if (newlineAtEndNode)
+ removeNode(newlineAtEndNode);
+
+ NodeImpl *holder = insertFragmentForTestRendering();
+ if (holder)
+ holder->ref();
+ if (!m_matchStyle) {
+ computeStylesUsingTestRendering(holder);
+ }
+ removeUnrenderedNodesUsingTestRendering(holder);
+ m_hasMoreThanOneBlock = countRenderedBlocks(holder) > 1;
+ restoreTestRenderingNodesToFragment(holder);
+ removeNode(holder);
+ holder->deref();
+ removeStyleNodes();
+}
+
+ReplacementFragment::~ReplacementFragment()
+{
+ if (m_document)
+ m_document->deref();
+ if (m_fragment)
+ m_fragment->deref();
+}
+
+NodeImpl *ReplacementFragment::firstChild() const
+{
+ return m_fragment->firstChild();
+}
+
+NodeImpl *ReplacementFragment::lastChild() const
+{
+ return m_fragment->lastChild();
+}
+
+static bool isProbablyBlock(const NodeImpl *node)
+{
+ if (!node)
+ return false;
+
+ switch (node->id()) {
+ case ID_BLOCKQUOTE:
+ case ID_DD:
+ case ID_DIV:
+ case ID_DL:
+ case ID_DT:
+ case ID_H1:
+ case ID_H2:
+ case ID_H3:
+ case ID_H4:
+ case ID_H5:
+ case ID_H6:
+ case ID_HR:
+ case ID_LI:
+ case ID_OL:
+ case ID_P:
+ case ID_PRE:
+ case ID_TD:
+ case ID_TH:
+ case ID_UL:
+ return true;
+ }
+
+ return false;
+}
+
+static bool isMailPasteAsQuotationNode(const NodeImpl *node)
+{
+ if (!node)
+ return false;
+
+ return static_cast<const ElementImpl *>(node)->getAttribute("class") == ApplePasteAsQuotation;
+}
+
+NodeImpl *ReplacementFragment::mergeStartNode() const
+{
+ NodeImpl *node = m_fragment->firstChild();
+ while (node && isProbablyBlock(node) && !isMailPasteAsQuotationNode(node))
+ node = node->traverseNextNode();
+ return node;
+}
+
+static bool isProbablyTableStructureNode(const NodeImpl *node)
+{
+ if (!node)
+ return false;
+
+ switch (node->id()) {
+ case ID_TABLE:
+ case ID_TBODY:
+ case ID_TD:
+ case ID_TFOOT:
+ case ID_THEAD:
+ case ID_TR:
+ return true;
+ }
+ return false;
+}
+
+void ReplacementFragment::pruneEmptyNodes()
+{
+ bool run = true;
+ while (run) {
+ run = false;
+ NodeImpl *node = m_fragment->firstChild();
+ while (node) {
+ if ((node->isTextNode() && static_cast<TextImpl *>(node)->length() == 0) ||
+ (isProbablyBlock(node) && !isProbablyTableStructureNode(node) && node->childNodeCount() == 0)) {
+ NodeImpl *next = node->traverseNextSibling();
+ removeNode(node);
+ node = next;
+ run = true;
+ }
+ else {
+ node = node->traverseNextNode();
+ }
+ }
+ }
+}
+
+bool ReplacementFragment::isInterchangeNewlineNode(const NodeImpl *node)
+{
+ static DOMString interchangeNewlineClassString(AppleInterchangeNewline);
+ return node && node->id() == ID_BR && static_cast<const ElementImpl *>(node)->getAttribute(ATTR_CLASS) == interchangeNewlineClassString;
+}
+
+bool ReplacementFragment::isInterchangeConvertedSpaceSpan(const NodeImpl *node)
+{
+ static DOMString convertedSpaceSpanClassString(AppleConvertedSpace);
+ return node->isHTMLElement() && static_cast<const HTMLElementImpl *>(node)->getAttribute(ATTR_CLASS) == convertedSpaceSpanClassString;
+}
+
+NodeImpl *ReplacementFragment::enclosingBlock(NodeImpl *node) const
+{
+ while (node && !isProbablyBlock(node))
+ node = node->parentNode();
+ return node ? node : m_fragment;
+}
+
+void ReplacementFragment::removeNodePreservingChildren(NodeImpl *node)
+{
+ if (!node)
+ return;
+
+ while (NodeImpl *n = node->firstChild()) {
+ n->ref();
+ removeNode(n);
+ insertNodeBefore(n, node);
+ n->deref();
+ }
+ removeNode(node);
+}
+
+void ReplacementFragment::removeNode(NodeImpl *node)
+{
+ if (!node)
+ return;
+
+ NodeImpl *parent = node->parentNode();
+ if (!parent)
+ return;
+
+ int exceptionCode = 0;
+ parent->removeChild(node, exceptionCode);
+ ASSERT(exceptionCode == 0);
+}
+
+void ReplacementFragment::insertNodeBefore(NodeImpl *node, NodeImpl *refNode)
+{
+ if (!node || !refNode)
+ return;
+
+ NodeImpl *parent = refNode->parentNode();
+ if (!parent)
+ return;
+
+ int exceptionCode = 0;
+ parent->insertBefore(node, refNode, exceptionCode);
+ ASSERT(exceptionCode == 0);
+}
+
+NodeImpl *ReplacementFragment::insertFragmentForTestRendering()
+{
+ NodeImpl *body = m_document->body();
+ if (!body)
+ return 0;
+
+ ElementImpl *holder = createDefaultParagraphElement(m_document);
+ holder->ref();
+
+ int exceptionCode = 0;
+ holder->appendChild(m_fragment, exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ body->appendChild(holder, exceptionCode);
+ ASSERT(exceptionCode == 0);
+ holder->deref();
+
+ m_document->updateLayout();
+
+ return holder;
+}
+
+void ReplacementFragment::restoreTestRenderingNodesToFragment(NodeImpl *holder)
+{
+ if (!holder)
+ return;
+
+ int exceptionCode = 0;
+ while (NodeImpl *node = holder->firstChild()) {
+ node->ref();
+ holder->removeChild(node, exceptionCode);
+ ASSERT(exceptionCode == 0);
+ m_fragment->appendChild(node, exceptionCode);
+ ASSERT(exceptionCode == 0);
+ node->deref();
+ }
+}
+
+static DOMString &matchNearestBlockquoteColorString()
+{
+ static DOMString matchNearestBlockquoteColorString = "match";
+ return matchNearestBlockquoteColorString;
+}
+
+void ReplaceSelectionCommand::fixupNodeStyles(const QValueList<NodeDesiredStyle> &list)
+{
+ // This function uses the mapped "desired style" to apply the additional style needed, if any,
+ // to make the node have the desired style.
+
+ document()->updateLayout();
+
+ QValueListConstIterator<NodeDesiredStyle> it;
+ for (it = list.begin(); it != list.end(); ++it) {
+ NodeImpl *node = (*it).node();
+ CSSMutableStyleDeclarationImpl *desiredStyle = (*it).style();
+ ASSERT(desiredStyle);
+
+ if (!node->inDocument())
+ continue;
+
+ // The desiredStyle declaration tells what style this node wants to be.
+ // Compare that to the style that it is right now in the document.
+ Position pos(node, 0);
+ CSSComputedStyleDeclarationImpl *currentStyle = pos.computedStyle();
+ currentStyle->ref();
+
+ // Check for the special "match nearest blockquote color" property and resolve to the correct
+ // color if necessary.
+ DOMString matchColorCheck = desiredStyle->getPropertyValue(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
+ if (matchColorCheck == matchNearestBlockquoteColorString()) {
+ NodeImpl *blockquote = nearestMailBlockquote(node);
+ Position pos(blockquote ? blockquote : node->getDocument()->documentElement(), 0);
+ CSSComputedStyleDeclarationImpl *style = pos.computedStyle();
+ DOMString desiredColor = desiredStyle->getPropertyValue(CSS_PROP_COLOR);
+ DOMString nearestColor = style->getPropertyValue(CSS_PROP_COLOR);
+ if (desiredColor != nearestColor)
+ desiredStyle->setProperty(CSS_PROP_COLOR, nearestColor);
+ }
+ desiredStyle->removeProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR);
+
+ currentStyle->diff(desiredStyle);
+
+ // Only add in block properties if the node is at the start of a
+ // paragraph. This matches AppKit.
+ if (!isStartOfParagraph(VisiblePosition(pos, DOWNSTREAM)))
+ desiredStyle->removeBlockProperties();
+
+ // If the desiredStyle is non-zero length, that means the current style differs
+ // from the desired by the styles remaining in the desiredStyle declaration.
+ if (desiredStyle->length() > 0) {
+ DOM::RangeImpl *rangeAroundNode = document()->createRange();
+ rangeAroundNode->ref();
+ int exceptionCode = 0;
+ rangeAroundNode->selectNode(node, exceptionCode);
+ ASSERT(exceptionCode == 0);
+ // affinity is not really important since this is a temp selection
+ // just for calling applyStyle
+ setEndingSelection(Selection(rangeAroundNode, SEL_DEFAULT_AFFINITY, SEL_DEFAULT_AFFINITY));
+ applyStyle(desiredStyle);
+ rangeAroundNode->deref();
+ }
+
+ currentStyle->deref();
+ }
+}
+
+static void computeAndStoreNodeDesiredStyle(DOM::NodeImpl *node, QValueList<NodeDesiredStyle> &list)
+{
+ if (!node || !node->inDocument())
+ return;
+
+ CSSComputedStyleDeclarationImpl *computedStyle = Position(node, 0).computedStyle();
+ computedStyle->ref();
+ CSSMutableStyleDeclarationImpl *style = computedStyle->copyInheritableProperties();
+ list.append(NodeDesiredStyle(node, style));
+ computedStyle->deref();
+
+ // In either of the color-matching tests below, set the color to a pseudo-color that will
+ // make the content take on the color of the nearest-enclosing blockquote (if any) after
+ // being pasted in.
+ if (NodeImpl *blockquote = nearestMailBlockquote(node)) {
+ CSSComputedStyleDeclarationImpl *blockquoteStyle = Position(blockquote, 0).computedStyle();
+ if (blockquoteStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR)) {
+ style->setProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
+ return;
+ }
+ }
+ NodeImpl *documentElement = node->getDocument() ? node->getDocument()->documentElement() : 0;
+ if (documentElement) {
+ CSSComputedStyleDeclarationImpl *documentStyle = Position(documentElement, 0).computedStyle();
+ if (documentStyle->getPropertyValue(CSS_PROP_COLOR) == style->getPropertyValue(CSS_PROP_COLOR)) {
+ style->setProperty(CSS_PROP__KHTML_MATCH_NEAREST_MAIL_BLOCKQUOTE_COLOR, matchNearestBlockquoteColorString());
+ }
+ }
+}
+
+void ReplacementFragment::computeStylesUsingTestRendering(NodeImpl *holder)
+{
+ if (!holder)
+ return;
+
+ m_document->updateLayout();
+
+ for (NodeImpl *node = holder->firstChild(); node; node = node->traverseNextNode(holder))
+ computeAndStoreNodeDesiredStyle(node, m_styles);
+}
+
+void ReplacementFragment::removeUnrenderedNodesUsingTestRendering(NodeImpl *holder)
+{
+ if (!holder)
+ return;
+
+ QPtrList<NodeImpl> unrendered;
+
+ for (NodeImpl *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
+ if (!isNodeRendered(node) && !isTableStructureNode(node))
+ unrendered.append(node);
+ }
+
+ for (QPtrListIterator<NodeImpl> it(unrendered); it.current(); ++it)
+ removeNode(it.current());
+}
+
+int ReplacementFragment::countRenderedBlocks(NodeImpl *holder)
+{
+ if (!holder)
+ return 0;
+
+ int count = 0;
+ NodeImpl *prev = 0;
+ for (NodeImpl *node = holder->firstChild(); node; node = node->traverseNextNode(holder)) {
+ if (node->isBlockFlow()) {
+ if (!prev) {
+ count++;
+ prev = node;
+ }
+ }
+ else {
+ NodeImpl *block = node->enclosingBlockFlowElement();
+ if (block != prev) {
+ count++;
+ prev = block;
+ }
+ }
+ }
+
+ return count;
+}
+
+void ReplacementFragment::removeStyleNodes()
+{
+ // Since style information has been computed and cached away in
+ // computeStylesForNodes(), these style nodes can be removed, since
+ // the correct styles will be added back in fixupNodeStyles().
+ NodeImpl *node = m_fragment->firstChild();
+ while (node) {
+ NodeImpl *next = node->traverseNextNode();
+ // This list of tags change the appearance of content
+ // in ways we can add back on later with CSS, if necessary.
+ if (node->id() == ID_B ||
+ node->id() == ID_BIG ||
+ node->id() == ID_CENTER ||
+ node->id() == ID_FONT ||
+ node->id() == ID_I ||
+ node->id() == ID_S ||
+ node->id() == ID_SMALL ||
+ node->id() == ID_STRIKE ||
+ node->id() == ID_SUB ||
+ node->id() == ID_SUP ||
+ node->id() == ID_TT ||
+ node->id() == ID_U ||
+ isStyleSpan(node)) {
+ removeNodePreservingChildren(node);
+ }
+ else if (node->isHTMLElement()) {
+ HTMLElementImpl *elem = static_cast<HTMLElementImpl *>(node);
+ CSSMutableStyleDeclarationImpl *inlineStyleDecl = elem->inlineStyleDecl();
+ if (inlineStyleDecl) {
+ inlineStyleDecl->removeBlockProperties();
+ inlineStyleDecl->removeInheritableProperties();
+ }
+ }
+ node = next;
+ }
+}
+
+NodeDesiredStyle::NodeDesiredStyle(NodeImpl *node, CSSMutableStyleDeclarationImpl *style)
+ : m_node(node), m_style(style)
+{
+ if (m_node)
+ m_node->ref();
+ if (m_style)
+ m_style->ref();
+}
+
+NodeDesiredStyle::NodeDesiredStyle(const NodeDesiredStyle &other)
+ : m_node(other.node()), m_style(other.style())
+{
+ if (m_node)
+ m_node->ref();
+ if (m_style)
+ m_style->ref();
+}
+
+NodeDesiredStyle::~NodeDesiredStyle()
+{
+ if (m_node)
+ m_node->deref();
+ if (m_style)
+ m_style->deref();
+}
+
+NodeDesiredStyle &NodeDesiredStyle::operator=(const NodeDesiredStyle &other)
+{
+ NodeImpl *oldNode = m_node;
+ CSSMutableStyleDeclarationImpl *oldStyle = m_style;
+
+ m_node = other.node();
+ m_style = other.style();
+
+ if (m_node)
+ m_node->ref();
+ if (m_style)
+ m_style->ref();
+
+ if (oldNode)
+ oldNode->deref();
+ if (oldStyle)
+ oldStyle->deref();
+
+ return *this;
+}
+
+ReplaceSelectionCommand::ReplaceSelectionCommand(DocumentImpl *document, DocumentFragmentImpl *fragment, bool selectReplacement, bool smartReplace, bool matchStyle)
+ : CompositeEditCommand(document),
+ m_fragment(document, fragment, matchStyle),
+ m_firstNodeInserted(0),
+ m_lastNodeInserted(0),
+ m_lastTopNodeInserted(0),
+ m_insertionStyle(0),
+ m_selectReplacement(selectReplacement),
+ m_smartReplace(smartReplace),
+ m_matchStyle(matchStyle)
+{
+}
+
+ReplaceSelectionCommand::~ReplaceSelectionCommand()
+{
+ if (m_firstNodeInserted)
+ m_firstNodeInserted->deref();
+ if (m_lastNodeInserted)
+ m_lastNodeInserted->deref();
+ if (m_lastTopNodeInserted)
+ m_lastTopNodeInserted->deref();
+ if (m_insertionStyle)
+ m_insertionStyle->deref();
+}
+
+static int maxRangeOffset(NodeImpl *n)
+{
+ if (DOM::offsetInCharacters(n->nodeType()))
+ return n->maxOffset();
+
+ if (n->isElementNode())
+ return n->childNodeCount();
+
+ return 1;
+}
+
+// This version of the function is meant to be called on positions in a document fragment,
+// so it does not check for a root editable element, it is assumed these nodes will be put
+// somewhere editable in the future
+bool isFirstVisiblePositionInSpecialElementInFragment(const Position& pos)
+{
+ VisiblePosition vPos = VisiblePosition(pos, DOWNSTREAM);
+
+ for (NodeImpl *n = pos.node(); n; n = n->parentNode()) {
+ if (VisiblePosition(n, 0, DOWNSTREAM) != vPos)
+ return false;
+ if (isSpecialElement(n))
+ return true;
+ }
+
+ return false;
+}
+
+void ReplaceSelectionCommand::doApply()
+{
+ // collect information about the current selection, prior to deleting the selection
+ Selection selection = endingSelection();
+ ASSERT(selection.isCaretOrRange());
+
+ VisiblePosition visibleStart(selection.start(), selection.startAffinity());
+ VisiblePosition visibleEnd(selection.end(), selection.endAffinity());
+ bool startAtStartOfBlock = isStartOfBlock(visibleStart);
+ bool startAtEndOfBlock = isEndOfBlock(visibleStart);
+ bool startAtBlockBoundary = startAtStartOfBlock || startAtEndOfBlock;
+ NodeImpl *startBlock = selection.start().node()->enclosingBlockFlowElement();
+ NodeImpl *endBlock = selection.end().node()->enclosingBlockFlowElement();
+
+ // decide whether to later merge content into the startBlock
+ bool mergeStart = false;
+ if (startBlock == startBlock->rootEditableElement() && startAtStartOfBlock && startAtEndOfBlock) {
+ // empty editable subtree, need to mergeStart so that fragment ends up
+ // inside the editable subtree rather than just before it
+ mergeStart = false;
+ } else {
+ // merge if current selection starts inside a paragraph, or there is only one block and no interchange newline to add
+ mergeStart = !m_fragment.hasInterchangeNewlineAtStart() &&
+ (!isStartOfParagraph(visibleStart) || (!m_fragment.hasInterchangeNewlineAtEnd() && !m_fragment.hasMoreThanOneBlock())) &&
+ !isLastVisiblePositionInSpecialElement(selection.start());
+
+ // This is a workaround for this bug:
+ // <rdar://problem/4013642> REGRESSION (Mail): Copied quoted word does not paste as a quote if pasted at the start of a line
+ // We need more powerful logic in this whole mergeStart code for this case to come out right without
+ // breaking other cases.
+ if (isStartOfParagraph(visibleStart) && isMailBlockquote(m_fragment.firstChild()))
+ mergeStart = false;
+ }
+
+ // decide whether to later append nodes to the end
+ NodeImpl *beyondEndNode = 0;
+ if (!isEndOfParagraph(visibleEnd) && !m_fragment.hasInterchangeNewlineAtEnd()) {
+ Position beyondEndPos = selection.end().downstream();
+ if (!isFirstVisiblePositionInSpecialElement(beyondEndPos))
+ beyondEndNode = beyondEndPos.node();
+ }
+ bool moveNodesAfterEnd = beyondEndNode && (startBlock != endBlock || m_fragment.hasMoreThanOneBlock());
+
+ Position startPos = selection.start();
+
+ // delete the current range selection, or insert paragraph for caret selection, as needed
+ if (selection.isRange()) {
+ deleteSelection(false, !(m_fragment.hasInterchangeNewlineAtStart() || m_fragment.hasInterchangeNewlineAtEnd() || m_fragment.hasMoreThanOneBlock()));
+ document()->updateLayout();
+ visibleStart = VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY);
+ if (m_fragment.hasInterchangeNewlineAtStart()) {
+ if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
+ if (!isEndOfDocument(visibleStart))
+ setEndingSelection(visibleStart.next());
+ }
+ else {
+ insertParagraphSeparator();
+ setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY));
+ }
+ }
+ startPos = endingSelection().start();
+ }
+ else {
+ ASSERT(selection.isCaret());
+ if (m_fragment.hasInterchangeNewlineAtStart()) {
+ if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
+ if (!isEndOfDocument(visibleStart))
+ setEndingSelection(visibleStart.next());
+ }
+ else {
+ insertParagraphSeparator();
+ setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY));
+ }
+ }
+ if (!m_fragment.hasInterchangeNewlineAtEnd() && m_fragment.hasMoreThanOneBlock() &&
+ !startAtBlockBoundary && !isEndOfParagraph(visibleEnd)) {
+ // The start and the end need to wind up in separate blocks.
+ // Insert a paragraph separator to make that happen.
+ insertParagraphSeparator();
+ setEndingSelection(VisiblePosition(endingSelection().start(), VP_DEFAULT_AFFINITY).previous());
+ }
+ startPos = endingSelection().start();
+ }
+
+ if (startAtStartOfBlock && startBlock->inDocument())
+ startPos = Position(startBlock, 0);
+
+ startPos = positionOutsideContainingSpecialElement(startPos);
+
+ KHTMLPart *part = document()->part();
+ if (m_matchStyle) {
+ m_insertionStyle = styleAtPosition(startPos);
+ m_insertionStyle->ref();
+ }
+
+ // FIXME: Improve typing style.
+ // See this bug: <rdar://problem/3769899> Implementation of typing style needs improvement
+ part->clearTypingStyle();
+ setTypingStyle(0);
+
+ // done if there is nothing to add
+ if (!m_fragment.firstChild())
+ return;
+
+ // check for a line placeholder, and store it away for possible removal later.
+ NodeImpl *block = startPos.node()->enclosingBlockFlowElement();
+ NodeImpl *linePlaceholder = findBlockPlaceholder(block);
+ if (!linePlaceholder) {
+ Position downstream = startPos.downstream();
+ downstream = positionOutsideContainingSpecialElement(downstream);
+ if (downstream.node()->id() == ID_BR && downstream.offset() == 0 &&
+ m_fragment.hasInterchangeNewlineAtEnd() &&
+ isStartOfLine(VisiblePosition(downstream, VP_DEFAULT_AFFINITY)))
+ linePlaceholder = downstream.node();
+ }
+
+ // check whether to "smart replace" needs to add leading and/or trailing space
+ bool addLeadingSpace = false;
+ bool addTrailingSpace = false;
+ // FIXME: We need the affinity for startPos and endPos, but Position::downstream
+ // and Position::upstream do not give it
+ if (m_smartReplace) {
+ VisiblePosition visiblePos = VisiblePosition(startPos, VP_DEFAULT_AFFINITY);
+ assert(visiblePos.isNotNull());
+ addLeadingSpace = startPos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isStartOfLine(visiblePos);
+ if (addLeadingSpace) {
+ QChar previousChar = visiblePos.previous().character();
+ if (!previousChar.isNull()) {
+ addLeadingSpace = !part->isCharacterSmartReplaceExempt(previousChar, true);
+ }
+ }
+ addTrailingSpace = startPos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull() && !isEndOfLine(visiblePos);
+ if (addTrailingSpace) {
+ QChar thisChar = visiblePos.character();
+ if (!thisChar.isNull()) {
+ addTrailingSpace = !part->isCharacterSmartReplaceExempt(thisChar, false);
+ }
+ }
+ }
+
+ // There are five steps to adding the content: merge blocks at start, add remaining blocks,
+ // add "smart replace" space, handle trailing newline, clean up.
+
+ // initially, we say the insertion point is the start of selection
+ document()->updateLayout();
+ Position insertionPos = startPos;
+
+ // step 1: merge content into the start block, if that is needed
+ if (mergeStart && !isFirstVisiblePositionInSpecialElementInFragment(Position(m_fragment.mergeStartNode(), 0))) {
+ NodeImpl *refNode = m_fragment.mergeStartNode();
+ if (refNode) {
+ NodeImpl *node = refNode ? refNode->nextSibling() : 0;
+ insertNodeAtAndUpdateNodesInserted(refNode, startPos.node(), startPos.offset());
+ while (node && !isProbablyBlock(node)) {
+ NodeImpl *next = node->nextSibling();
+ insertNodeAfterAndUpdateNodesInserted(node, refNode);
+ refNode = node;
+ node = next;
+ }
+ }
+
+ // update insertion point to be at the end of the last block inserted
+ if (m_lastNodeInserted) {
+ document()->updateLayout();
+ insertionPos = Position(m_lastNodeInserted, m_lastNodeInserted->caretMaxOffset());
+ }
+ }
+
+ // prune empty nodes from fragment
+ // NOTE: why was this not done earlier, before the mergeStart?
+ m_fragment.pruneEmptyNodes();
+
+ // step 2 : merge everything remaining in the fragment
+ if (m_fragment.firstChild()) {
+ NodeImpl *refNode = m_fragment.firstChild();
+ NodeImpl *node = refNode ? refNode->nextSibling() : 0;
+ NodeImpl *insertionBlock = insertionPos.node()->enclosingBlockFlowElement();
+ bool insertionBlockIsRoot = insertionBlock == insertionBlock->rootEditableElement();
+ VisiblePosition visiblePos(insertionPos, DOWNSTREAM);
+ if (!insertionBlockIsRoot && isProbablyBlock(refNode) && isStartOfBlock(visiblePos))
+ insertNodeBeforeAndUpdateNodesInserted(refNode, insertionBlock);
+ else if (!insertionBlockIsRoot && isProbablyBlock(refNode) && isEndOfBlock(visiblePos)) {
+ insertNodeAfterAndUpdateNodesInserted(refNode, insertionBlock);
+ } else if (mergeStart && !isProbablyBlock(refNode)) {
+ Position pos = visiblePos.next().deepEquivalent().downstream();
+ insertNodeAtAndUpdateNodesInserted(refNode, pos.node(), pos.offset());
+ } else {
+ insertNodeAtAndUpdateNodesInserted(refNode, insertionPos.node(), insertionPos.offset());
+ }
+
+ while (node) {
+ NodeImpl *next = node->nextSibling();
+ insertNodeAfterAndUpdateNodesInserted(node, refNode);
+ refNode = node;
+ node = next;
+ }
+ document()->updateLayout();
+ insertionPos = Position(m_lastNodeInserted, m_lastNodeInserted->caretMaxOffset());
+ }
+
+ // step 3 : handle "smart replace" whitespace
+ if (addTrailingSpace && m_lastNodeInserted) {
+ document()->updateLayout();
+ Position pos(m_lastNodeInserted, m_lastNodeInserted->caretMaxOffset());
+ bool needsTrailingSpace = pos.trailingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull();
+ if (needsTrailingSpace) {
+ if (m_lastNodeInserted->isTextNode()) {
+ TextImpl *text = static_cast<TextImpl *>(m_lastNodeInserted);
+ insertTextIntoNode(text, text->length(), nonBreakingSpaceString());
+ insertionPos = Position(text, text->length());
+ }
+ else {
+ NodeImpl *node = document()->createEditingTextNode(nonBreakingSpaceString());
+ insertNodeAfterAndUpdateNodesInserted(node, m_lastNodeInserted);
+ insertionPos = Position(node, 1);
+ }
+ }
+ }
+
+ if (addLeadingSpace && m_firstNodeInserted) {
+ document()->updateLayout();
+ Position pos(m_firstNodeInserted, 0);
+ bool needsLeadingSpace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY, true).isNull();
+ if (needsLeadingSpace) {
+ if (m_firstNodeInserted->isTextNode()) {
+ TextImpl *text = static_cast<TextImpl *>(m_firstNodeInserted);
+ insertTextIntoNode(text, 0, nonBreakingSpaceString());
+ } else {
+ NodeImpl *node = document()->createEditingTextNode(nonBreakingSpaceString());
+ insertNodeBeforeAndUpdateNodesInserted(node, m_firstNodeInserted);
+ }
+ }
+ }
+
+ Position lastPositionToSelect;
+
+ // step 4 : handle trailing newline
+ if (m_fragment.hasInterchangeNewlineAtEnd()) {
+ removeLinePlaceholderIfNeeded(linePlaceholder);
+
+ if (!m_lastNodeInserted) {
+ lastPositionToSelect = endingSelection().end().downstream();
+ }
+ else {
+ bool insertParagraph = false;
+ VisiblePosition pos(insertionPos, VP_DEFAULT_AFFINITY);
+
+ if (startBlock == endBlock && !isProbablyBlock(m_lastTopNodeInserted)) {
+ insertParagraph = true;
+ } else {
+ // Handle end-of-document case.
+ document()->updateLayout();
+ if (isEndOfDocument(pos))
+ insertParagraph = true;
+ }
+ if (insertParagraph) {
+ setEndingSelection(insertionPos, DOWNSTREAM);
+ insertParagraphSeparator();
+ VisiblePosition next = pos.next();
+
+ // Select up to the paragraph separator that was added.
+ lastPositionToSelect = next.deepEquivalent().downstream();
+ updateNodesInserted(lastPositionToSelect.node());
+ } else {
+ // Select up to the preexising paragraph separator.
+ VisiblePosition next = pos.next();
+ lastPositionToSelect = next.deepEquivalent().downstream();
+ }
+ }
+ }
+ else {
+ if (m_lastNodeInserted && m_lastNodeInserted->id() == ID_BR && !document()->inStrictMode()) {
+ document()->updateLayout();
+ VisiblePosition pos(Position(m_lastNodeInserted, 1), DOWNSTREAM);
+ if (isEndOfBlock(pos)) {
+ NodeImpl *next = m_lastNodeInserted->traverseNextNode();
+ bool hasTrailingBR = next && next->id() == ID_BR && m_lastNodeInserted->enclosingBlockFlowElement() == next->enclosingBlockFlowElement();
+ if (!hasTrailingBR) {
+ // Insert an "extra" BR at the end of the block.
+ insertNodeBefore(createBreakElement(document()), m_lastNodeInserted);
+ }
+ }
+ }
+
+ if (moveNodesAfterEnd && !isLastVisiblePositionInSpecialElement(Position(m_lastNodeInserted, maxRangeOffset(m_lastNodeInserted)))) {
+ document()->updateLayout();
+ QValueList<NodeDesiredStyle> styles;
+ QPtrList<NodeImpl> blocks;
+ NodeImpl *node = beyondEndNode;
+ NodeImpl *refNode = m_lastNodeInserted;
+ while (node) {
+ RenderObject *renderer = node->renderer();
+ // Stop at the first table or block.
+ if (renderer && (renderer->isBlockFlow() || renderer->isTable()))
+ break;
+ NodeImpl *next = node->nextSibling();
+ blocks.append(node->enclosingBlockFlowElement());
+ computeAndStoreNodeDesiredStyle(node, styles);
+ removeNode(node);
+ // No need to update inserted node variables.
+ insertNodeAfter(node, refNode);
+ refNode = node;
+ // We want to move the first BR we see, so check for that here.
+ if (node->id() == ID_BR)
+ break;
+ node = next;
+ }
+ document()->updateLayout();
+ for (QPtrListIterator<NodeImpl> it(blocks); it.current(); ++it) {
+ NodeImpl *blockToRemove = it.current();
+ if (!blockToRemove->inDocument())
+ continue;
+ if (!blockToRemove->renderer() || !blockToRemove->renderer()->firstChild()) {
+ if (blockToRemove->parentNode())
+ blocks.append(blockToRemove->parentNode()->enclosingBlockFlowElement());
+ removeNode(blockToRemove);
+ document()->updateLayout();
+ }
+ }
+
+ fixupNodeStyles(styles);
+ }
+ }
+
+ if (!m_matchStyle)
+ fixupNodeStyles(m_fragment.desiredStyles());
+ completeHTMLReplacement(lastPositionToSelect);
+
+ // step 5 : mop up
+ removeLinePlaceholderIfNeeded(linePlaceholder);
+}
+
+void ReplaceSelectionCommand::removeLinePlaceholderIfNeeded(NodeImpl *linePlaceholder)
+{
+ if (!linePlaceholder)
+ return;
+
+ document()->updateLayout();
+ if (linePlaceholder->inDocument()) {
+ VisiblePosition placeholderPos(linePlaceholder, linePlaceholder->renderer()->caretMinOffset(), DOWNSTREAM);
+ if (placeholderPos.next().isNull() ||
+ !(isStartOfLine(placeholderPos) && isEndOfLine(placeholderPos))) {
+ NodeImpl *block = linePlaceholder->enclosingBlockFlowElement();
+ removeNode(linePlaceholder);
+ document()->updateLayout();
+ if (!block->renderer() || block->renderer()->height() == 0)
+ removeNode(block);
+ }
+ }
+}
+
+void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
+{
+ Position start;
+ Position end;
+
+ if (m_firstNodeInserted && m_firstNodeInserted->inDocument() &&
+ m_lastNodeInserted && m_lastNodeInserted->inDocument()) {
+
+ // Find the last leaf.
+ NodeImpl *lastLeaf = m_lastNodeInserted;
+ while (1) {
+ NodeImpl *nextChild = lastLeaf->lastChild();
+ if (!nextChild)
+ break;
+ lastLeaf = nextChild;
+ }
+
+ // Find the first leaf.
+ NodeImpl *firstLeaf = m_firstNodeInserted;
+ while (1) {
+ NodeImpl *nextChild = firstLeaf->firstChild();
+ if (!nextChild)
+ break;
+ firstLeaf = nextChild;
+ }
+
+ // Call updateLayout so caretMinOffset and caretMaxOffset return correct values.
+ document()->updateLayout();
+ start = Position(firstLeaf, firstLeaf->caretMinOffset());
+ end = Position(lastLeaf, lastLeaf->caretMaxOffset());
+
+ if (m_matchStyle) {
+ assert(m_insertionStyle);
+ setEndingSelection(Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY));
+ applyStyle(m_insertionStyle);
+ }
+
+ if (lastPositionToSelect.isNotNull())
+ end = lastPositionToSelect;
+ }
+ else if (lastPositionToSelect.isNotNull()) {
+ start = end = lastPositionToSelect;
+ }
+ else {
+ return;
+ }
+
+ if (m_selectReplacement)
+ setEndingSelection(Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY));
+ else
+ setEndingSelection(end, SEL_DEFAULT_AFFINITY);
+
+ rebalanceWhitespace();
+}
+
+EditAction ReplaceSelectionCommand::editingAction() const
+{
+ return EditActionPaste;
+}
+
+void ReplaceSelectionCommand::insertNodeAfterAndUpdateNodesInserted(NodeImpl *insertChild, NodeImpl *refChild)
+{
+ insertNodeAfter(insertChild, refChild);
+ updateNodesInserted(insertChild);
+}
+
+void ReplaceSelectionCommand::insertNodeAtAndUpdateNodesInserted(NodeImpl *insertChild, NodeImpl *refChild, long offset)
+{
+ insertNodeAt(insertChild, refChild, offset);
+ updateNodesInserted(insertChild);
+}
+
+void ReplaceSelectionCommand::insertNodeBeforeAndUpdateNodesInserted(NodeImpl *insertChild, NodeImpl *refChild)
+{
+ insertNodeBefore(insertChild, refChild);
+ updateNodesInserted(insertChild);
+}
+
+void ReplaceSelectionCommand::updateNodesInserted(NodeImpl *node)
+{
+ if (!node)
+ return;
+
+ // update m_lastTopNodeInserted
+ node->ref();
+ if (m_lastTopNodeInserted)
+ m_lastTopNodeInserted->deref();
+ m_lastTopNodeInserted = node;
+
+ // update m_firstNodeInserted
+ if (!m_firstNodeInserted) {
+ m_firstNodeInserted = node;
+ m_firstNodeInserted->ref();
+ }
+
+ if (node == m_lastNodeInserted)
+ return;
+
+ // update m_lastNodeInserted
+ NodeImpl *old = m_lastNodeInserted;
+ m_lastNodeInserted = node->lastDescendent();
+ m_lastNodeInserted->ref();
+ if (old)
+ old->deref();
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __replace_selection_command_h__
+#define __replace_selection_command_h__
+
+#include "composite_edit_command.h"
+
+namespace DOM {
+ class DocumentFragmentImpl;
+}
+
+namespace khtml {
+
+class NodeDesiredStyle
+{
+public:
+ NodeDesiredStyle(DOM::NodeImpl *, DOM::CSSMutableStyleDeclarationImpl *);
+ NodeDesiredStyle(const NodeDesiredStyle &);
+ ~NodeDesiredStyle();
+
+ DOM::NodeImpl *node() const { return m_node; }
+ DOM::CSSMutableStyleDeclarationImpl *style() const { return m_style; }
+
+ NodeDesiredStyle &operator=(const NodeDesiredStyle &);
+
+private:
+ DOM::NodeImpl *m_node;
+ DOM::CSSMutableStyleDeclarationImpl *m_style;
+};
+
+// --- ReplacementFragment helper class
+
+class ReplacementFragment
+{
+public:
+ ReplacementFragment(DOM::DocumentImpl *, DOM::DocumentFragmentImpl *, bool matchStyle);
+ ~ReplacementFragment();
+
+ enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
+
+ DOM::DocumentFragmentImpl *root() const { return m_fragment; }
+ DOM::NodeImpl *firstChild() const;
+ DOM::NodeImpl *lastChild() const;
+
+ DOM::NodeImpl *mergeStartNode() const;
+
+ const QValueList<NodeDesiredStyle> &desiredStyles() { return m_styles; }
+
+ void pruneEmptyNodes();
+
+ EFragmentType type() const { return m_type; }
+ bool isEmpty() const { return m_type == EmptyFragment; }
+ bool isSingleTextNode() const { return m_type == SingleTextNodeFragment; }
+ bool isTreeFragment() const { return m_type == TreeFragment; }
+
+ bool hasMoreThanOneBlock() const { return m_hasMoreThanOneBlock; }
+ bool hasInterchangeNewlineAtStart() const { return m_hasInterchangeNewlineAtStart; }
+ bool hasInterchangeNewlineAtEnd() const { return m_hasInterchangeNewlineAtEnd; }
+
+private:
+ // no copy construction or assignment
+ ReplacementFragment(const ReplacementFragment &);
+ ReplacementFragment &operator=(const ReplacementFragment &);
+
+ static bool isInterchangeNewlineNode(const DOM::NodeImpl *);
+ static bool isInterchangeConvertedSpaceSpan(const DOM::NodeImpl *);
+
+ DOM::NodeImpl *insertFragmentForTestRendering();
+ void restoreTestRenderingNodesToFragment(DOM::NodeImpl *);
+ void computeStylesUsingTestRendering(DOM::NodeImpl *);
+ void removeUnrenderedNodesUsingTestRendering(DOM::NodeImpl *);
+ int countRenderedBlocks(DOM::NodeImpl *holder);
+ void removeStyleNodes();
+
+ // A couple simple DOM helpers
+ DOM::NodeImpl *enclosingBlock(DOM::NodeImpl *) const;
+ void removeNode(DOM::NodeImpl *);
+ void removeNodePreservingChildren(DOM::NodeImpl *);
+ void insertNodeBefore(DOM::NodeImpl *node, DOM::NodeImpl *refNode);
+
+ EFragmentType m_type;
+ DOM::DocumentImpl *m_document;
+ DOM::DocumentFragmentImpl *m_fragment;
+ QValueList<NodeDesiredStyle> m_styles;
+ bool m_matchStyle;
+ bool m_hasInterchangeNewlineAtStart;
+ bool m_hasInterchangeNewlineAtEnd;
+ bool m_hasMoreThanOneBlock;
+};
+
+class ReplaceSelectionCommand : public CompositeEditCommand
+{
+public:
+ ReplaceSelectionCommand(DOM::DocumentImpl *document, DOM::DocumentFragmentImpl *fragment, bool selectReplacement=true, bool smartReplace=false, bool matchStyle=false);
+ virtual ~ReplaceSelectionCommand();
+
+ virtual void doApply();
+ virtual EditAction editingAction() const;
+
+private:
+ void completeHTMLReplacement(const DOM::Position &lastPositionToSelect);
+
+ void insertNodeAfterAndUpdateNodesInserted(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
+ void insertNodeAtAndUpdateNodesInserted(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild, long offset);
+ void insertNodeBeforeAndUpdateNodesInserted(DOM::NodeImpl *insertChild, DOM::NodeImpl *refChild);
+
+ void updateNodesInserted(DOM::NodeImpl *);
+ void fixupNodeStyles(const QValueList<NodeDesiredStyle> &);
+ void removeLinePlaceholderIfNeeded(DOM::NodeImpl *);
+
+ ReplacementFragment m_fragment;
+ DOM::NodeImpl *m_firstNodeInserted;
+ DOM::NodeImpl *m_lastNodeInserted;
+ DOM::NodeImpl *m_lastTopNodeInserted;
+ DOM::CSSMutableStyleDeclarationImpl *m_insertionStyle;
+ bool m_selectReplacement;
+ bool m_smartReplace;
+ bool m_matchStyle;
+};
+
+} // namespace khtml
+
+#endif // __replace_selection_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "set_node_attribute_command.h"
+
+#include "xml/dom_elementimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::ElementImpl;
+using DOM::NodeImpl;
+using DOM::DOMString;
+
+namespace khtml {
+
+SetNodeAttributeCommand::SetNodeAttributeCommand(DocumentImpl *document, ElementImpl *element, NodeImpl::Id attribute, const DOMString &value)
+ : EditCommand(document), m_element(element), m_attribute(attribute), m_value(value)
+{
+ ASSERT(m_element);
+ m_element->ref();
+ ASSERT(!m_value.isNull());
+}
+
+SetNodeAttributeCommand::~SetNodeAttributeCommand()
+{
+ ASSERT(m_element);
+ m_element->deref();
+}
+
+void SetNodeAttributeCommand::doApply()
+{
+ ASSERT(m_element);
+ ASSERT(!m_value.isNull());
+
+ int exceptionCode = 0;
+ m_oldValue = m_element->getAttribute(m_attribute);
+ m_element->setAttribute(m_attribute, m_value.implementation(), exceptionCode);
+ ASSERT(exceptionCode == 0);
+}
+
+void SetNodeAttributeCommand::doUnapply()
+{
+ ASSERT(m_element);
+
+ int exceptionCode = 0;
+ if (m_oldValue.isNull())
+ m_element->removeAttribute(m_attribute, exceptionCode);
+ else
+ m_element->setAttribute(m_attribute, m_oldValue.implementation(), exceptionCode);
+ ASSERT(exceptionCode == 0);
+}
+
+} // namespace khtml
+
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __set_node_attribute_command_h__
+#define __set_node_attribute_command_h__
+
+#include "edit_command.h"
+
+#include "xml/dom_nodeimpl.h"
+
+namespace khtml {
+
+class SetNodeAttributeCommand : public EditCommand
+{
+public:
+ SetNodeAttributeCommand(DOM::DocumentImpl *, DOM::ElementImpl *, DOM::NodeImpl::Id attribute, const DOM::DOMString &value);
+ virtual ~SetNodeAttributeCommand();
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+ DOM::ElementImpl *element() const { return m_element; }
+ DOM::NodeImpl::Id attribute() const { return m_attribute; }
+ DOM::DOMString value() const { return m_value; }
+
+private:
+ DOM::ElementImpl *m_element;
+ DOM::NodeImpl::Id m_attribute;
+ DOM::DOMString m_value;
+ DOM::DOMString m_oldValue;
+};
+
+} // namespace khtml
+
+#endif // __set_node_attribute_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "split_element_command.h"
+
+#include "xml/dom_elementimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::ElementImpl;
+using DOM::NodeImpl;
+
+namespace khtml {
+
+SplitElementCommand::SplitElementCommand(DOM::DocumentImpl *document, DOM::ElementImpl *element, DOM::NodeImpl *atChild)
+ : EditCommand(document), m_element1(0), m_element2(element), m_atChild(atChild)
+{
+ ASSERT(m_element2);
+ ASSERT(m_atChild);
+
+ m_element2->ref();
+ m_atChild->ref();
+}
+
+SplitElementCommand::~SplitElementCommand()
+{
+ if (m_element1)
+ m_element1->deref();
+
+ ASSERT(m_element2);
+ m_element2->deref();
+ ASSERT(m_atChild);
+ m_atChild->deref();
+}
+
+void SplitElementCommand::doApply()
+{
+ ASSERT(m_element2);
+ ASSERT(m_atChild);
+
+ int exceptionCode = 0;
+
+ if (!m_element1) {
+ // create only if needed.
+ // if reapplying, this object will already exist.
+ m_element1 = static_cast<ElementImpl *>(m_element2->cloneNode(false));
+ ASSERT(m_element1);
+ m_element1->ref();
+ }
+
+ m_element2->parent()->insertBefore(m_element1, m_element2, exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ while (m_element2->firstChild() != m_atChild) {
+ ASSERT(m_element2->firstChild());
+ m_element1->appendChild(m_element2->firstChild(), exceptionCode);
+ ASSERT(exceptionCode == 0);
+ }
+}
+
+void SplitElementCommand::doUnapply()
+{
+ ASSERT(m_element1);
+ ASSERT(m_element2);
+ ASSERT(m_atChild);
+
+ ASSERT(m_element1->nextSibling() == m_element2);
+ ASSERT(m_element2->firstChild() && m_element2->firstChild() == m_atChild);
+
+ int exceptionCode = 0;
+
+ while (m_element1->lastChild()) {
+ m_element2->insertBefore(m_element1->lastChild(), m_element2->firstChild(), exceptionCode);
+ ASSERT(exceptionCode == 0);
+ }
+
+ m_element2->parentNode()->removeChild(m_element1, exceptionCode);
+ ASSERT(exceptionCode == 0);
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __split_element_command_h__
+#define __split_element_command_h__
+
+#include "edit_command.h"
+
+namespace khtml {
+
+class SplitElementCommand : public EditCommand
+{
+public:
+ SplitElementCommand(DOM::DocumentImpl *, DOM::ElementImpl *element, DOM::NodeImpl *atChild);
+ virtual ~SplitElementCommand();
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+private:
+ DOM::ElementImpl *m_element1;
+ DOM::ElementImpl *m_element2;
+ DOM::NodeImpl *m_atChild;
+};
+
+} // namespace khtml
+
+#endif // __split_element_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "split_text_node_command.h"
+
+#include "xml/dom_docimpl.h"
+#include "xml/dom_textimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::TextImpl;
+using DOM::NodeImpl;
+
+namespace khtml {
+
+SplitTextNodeCommand::SplitTextNodeCommand(DocumentImpl *document, TextImpl *text, long offset)
+ : EditCommand(document), m_text1(0), m_text2(text), m_offset(offset)
+{
+ ASSERT(m_text2);
+ ASSERT(m_text2->length() > 0);
+
+ m_text2->ref();
+}
+
+SplitTextNodeCommand::~SplitTextNodeCommand()
+{
+ if (m_text1)
+ m_text1->deref();
+
+ ASSERT(m_text2);
+ m_text2->deref();
+}
+
+void SplitTextNodeCommand::doApply()
+{
+ ASSERT(m_text2);
+ ASSERT(m_offset > 0);
+
+ int exceptionCode = 0;
+
+ // EDIT FIXME: This should use better smarts for figuring out which portion
+ // of the split to copy (based on their comparitive sizes). We should also
+ // just use the DOM's splitText function.
+
+ if (!m_text1) {
+ // create only if needed.
+ // if reapplying, this object will already exist.
+ m_text1 = document()->createTextNode(m_text2->substringData(0, m_offset, exceptionCode));
+ ASSERT(exceptionCode == 0);
+ ASSERT(m_text1);
+ m_text1->ref();
+ }
+
+ m_text2->deleteData(0, m_offset, exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ m_text2->parentNode()->insertBefore(m_text1, m_text2, exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ ASSERT(m_text2->previousSibling()->isTextNode());
+ ASSERT(m_text2->previousSibling() == m_text1);
+}
+
+void SplitTextNodeCommand::doUnapply()
+{
+ ASSERT(m_text1);
+ ASSERT(m_text2);
+
+ ASSERT(m_text1->nextSibling() == m_text2);
+
+ int exceptionCode = 0;
+ m_text2->insertData(0, m_text1->data(), exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ m_text2->parentNode()->removeChild(m_text1, exceptionCode);
+ ASSERT(exceptionCode == 0);
+
+ m_offset = m_text1->length();
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __split_text_node_command_h__
+#define __split_text_node_command_h__
+
+#include "edit_command.h"
+
+namespace DOM {
+ class TextImpl;
+}
+
+namespace khtml {
+
+class SplitTextNodeCommand : public EditCommand
+{
+public:
+ SplitTextNodeCommand(DOM::DocumentImpl *, DOM::TextImpl *, long);
+ virtual ~SplitTextNodeCommand();
+
+ virtual void doApply();
+ virtual void doUnapply();
+
+ DOM::TextImpl *node() const { return m_text2; }
+ long offset() const { return m_offset; }
+
+private:
+ DOM::TextImpl *m_text1;
+ DOM::TextImpl *m_text2;
+ unsigned long m_offset;
+};
+
+} // namespace khtml
+
+#endif // __split_text_node_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __split_text_node_containing_element_command_h__
+#define __split_text_node_containing_element_command_h__
+
+#include "composite_edit_command.h"
+
+namespace khtml {
+
+class SplitTextNodeContainingElementCommand : public CompositeEditCommand
+{
+public:
+ SplitTextNodeContainingElementCommand(DOM::DocumentImpl *, DOM::TextImpl *, long);
+ virtual ~SplitTextNodeContainingElementCommand();
+
+ virtual void doApply();
+
+private:
+ DOM::TextImpl *m_text;
+ long m_offset;
+};
+
+} // namespace khtml
+
+#endif // __split_text_node_containing_element_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "typing_command.h"
+
+#include "insert_text_command.h"
+#include "insert_line_break_command.h"
+#include "insert_paragraph_separator_command.h"
+#include "break_blockquote_command.h"
+
+#include "khtml_part.h"
+#include "xml/dom_docimpl.h"
+#include "visible_position.h"
+#include "visible_units.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::DOMString;
+using DOM::Position;
+
+namespace khtml {
+
+TypingCommand::TypingCommand(DocumentImpl *document, ETypingCommand commandType, const DOMString &textToInsert, bool selectInsertedText)
+ : CompositeEditCommand(document),
+ m_commandType(commandType),
+ m_textToInsert(textToInsert),
+ m_openForMoreTyping(true),
+ m_applyEditing(false),
+ m_selectInsertedText(selectInsertedText),
+ m_smartDelete(false)
+{
+}
+
+void TypingCommand::deleteKeyPressed(DocumentImpl *document, bool smartDelete)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->deleteKeyPressed();
+ }
+ else {
+ Selection selection = part->selection();
+ if (selection.isCaret() && VisiblePosition(selection.start(), selection.startAffinity()).previous().isNull()) {
+ // do nothing for a delete key at the start of an editable element.
+ }
+ else {
+ TypingCommand *typingCommand = new TypingCommand(document, DeleteKey);
+ typingCommand->setSmartDelete(smartDelete);
+ EditCommandPtr cmd(typingCommand);
+ cmd.apply();
+ }
+ }
+}
+
+void TypingCommand::forwardDeleteKeyPressed(DocumentImpl *document, bool smartDelete)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->forwardDeleteKeyPressed();
+ }
+ else {
+ Selection selection = part->selection();
+ if (selection.isCaret() && isEndOfDocument(VisiblePosition(selection.start(), selection.startAffinity()))) {
+ // do nothing for a delete key at the start of an editable element.
+ }
+ else {
+ TypingCommand *typingCommand = new TypingCommand(document, ForwardDeleteKey);
+ typingCommand->setSmartDelete(smartDelete);
+ EditCommandPtr cmd(typingCommand);
+ cmd.apply();
+ }
+ }
+}
+
+void TypingCommand::insertText(DocumentImpl *document, const DOMString &text, bool selectInsertedText)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->insertText(text, selectInsertedText);
+ }
+ else {
+ EditCommandPtr cmd(new TypingCommand(document, InsertText, text, selectInsertedText));
+ cmd.apply();
+ }
+}
+
+void TypingCommand::insertLineBreak(DocumentImpl *document)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->insertLineBreak();
+ }
+ else {
+ EditCommandPtr cmd(new TypingCommand(document, InsertLineBreak));
+ cmd.apply();
+ }
+}
+
+void TypingCommand::insertParagraphSeparatorInQuotedContent(DocumentImpl *document)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->insertParagraphSeparatorInQuotedContent();
+ }
+ else {
+ EditCommandPtr cmd(new TypingCommand(document, InsertParagraphSeparatorInQuotedContent));
+ cmd.apply();
+ }
+}
+
+void TypingCommand::insertParagraphSeparator(DocumentImpl *document)
+{
+ ASSERT(document);
+
+ KHTMLPart *part = document->part();
+ ASSERT(part);
+
+ EditCommandPtr lastEditCommand = part->lastEditCommand();
+ if (isOpenForMoreTypingCommand(lastEditCommand)) {
+ static_cast<TypingCommand *>(lastEditCommand.get())->insertParagraphSeparator();
+ }
+ else {
+ EditCommandPtr cmd(new TypingCommand(document, InsertParagraphSeparator));
+ cmd.apply();
+ }
+}
+
+bool TypingCommand::isOpenForMoreTypingCommand(const EditCommandPtr &cmd)
+{
+ return cmd.isTypingCommand() &&
+ static_cast<const TypingCommand *>(cmd.get())->openForMoreTyping();
+}
+
+void TypingCommand::closeTyping(const EditCommandPtr &cmd)
+{
+ if (isOpenForMoreTypingCommand(cmd))
+ static_cast<TypingCommand *>(cmd.get())->closeTyping();
+}
+
+void TypingCommand::doApply()
+{
+ if (endingSelection().isNone())
+ return;
+
+ switch (m_commandType) {
+ case DeleteKey:
+ deleteKeyPressed();
+ return;
+ case ForwardDeleteKey:
+ forwardDeleteKeyPressed();
+ return;
+ case InsertLineBreak:
+ insertLineBreak();
+ return;
+ case InsertParagraphSeparator:
+ insertParagraphSeparator();
+ return;
+ case InsertParagraphSeparatorInQuotedContent:
+ insertParagraphSeparatorInQuotedContent();
+ return;
+ case InsertText:
+ insertText(m_textToInsert, m_selectInsertedText);
+ return;
+ }
+
+ ASSERT_NOT_REACHED();
+}
+
+EditAction TypingCommand::editingAction() const
+{
+ return EditActionTyping;
+}
+
+void TypingCommand::markMisspellingsAfterTyping()
+{
+ // Take a look at the selection that results after typing and determine whether we need to spellcheck.
+ // Since the word containing the current selection is never marked, this does a check to
+ // see if typing made a new word that is not in the current selection. Basically, you
+ // get this by being at the end of a word and typing a space.
+ VisiblePosition start(endingSelection().start(), endingSelection().startAffinity());
+ VisiblePosition previous = start.previous();
+ if (previous.isNotNull()) {
+ VisiblePosition p1 = startOfWord(previous, LeftWordIfOnBoundary);
+ VisiblePosition p2 = startOfWord(start, LeftWordIfOnBoundary);
+ if (p1 != p2)
+ KWQ(document()->part())->markMisspellingsInAdjacentWords(p1);
+ }
+}
+
+void TypingCommand::typingAddedToOpenCommand()
+{
+ markMisspellingsAfterTyping();
+ // Do not apply editing to the part on the first time through.
+ // The part will get told in the same way as all other commands.
+ // But since this command stays open and is used for additional typing,
+ // we need to tell the part here as other commands are added.
+ if (m_applyEditing) {
+ EditCommandPtr cmd(this);
+ document()->part()->appliedEditing(cmd);
+ }
+ m_applyEditing = true;
+}
+
+void TypingCommand::insertText(const DOMString &text, bool selectInsertedText)
+{
+ // FIXME: Need to implement selectInsertedText for cases where more than one insert is involved.
+ // This requires support from insertTextRunWithoutNewlines and insertParagraphSeparator for extending
+ // an existing selection; at the moment they can either put the caret after what's inserted or
+ // select what's inserted, but there's no way to "extend selection" to include both an old selection
+ // that ends just before where we want to insert text and the newly inserted text.
+ int offset = 0;
+ int newline;
+ while ((newline = text.find('\n', offset)) != -1) {
+ if (newline != offset) {
+ insertTextRunWithoutNewlines(text.substring(offset, newline - offset), false);
+ }
+ insertParagraphSeparator();
+ offset = newline + 1;
+ }
+ if (offset == 0) {
+ insertTextRunWithoutNewlines(text, selectInsertedText);
+ } else {
+ int length = text.length();
+ if (length != offset) {
+ insertTextRunWithoutNewlines(text.substring(offset, length - offset), selectInsertedText);
+ }
+ }
+}
+
+void TypingCommand::insertTextRunWithoutNewlines(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) {
+ InsertTextCommand *impl = new InsertTextCommand(document());
+ EditCommandPtr cmd(impl);
+ applyCommandToComposite(cmd);
+ impl->input(text, selectInsertedText);
+ }
+ else {
+ EditCommandPtr lastCommand = m_cmds.last();
+ if (lastCommand.isInsertTextCommand()) {
+ InsertTextCommand *impl = static_cast<InsertTextCommand *>(lastCommand.get());
+ impl->input(text, selectInsertedText);
+ }
+ else {
+ InsertTextCommand *impl = new InsertTextCommand(document());
+ EditCommandPtr cmd(impl);
+ applyCommandToComposite(cmd);
+ impl->input(text, selectInsertedText);
+ }
+ }
+ typingAddedToOpenCommand();
+}
+
+void TypingCommand::insertLineBreak()
+{
+ EditCommandPtr cmd(new InsertLineBreakCommand(document()));
+ applyCommandToComposite(cmd);
+ typingAddedToOpenCommand();
+}
+
+void TypingCommand::insertParagraphSeparator()
+{
+ EditCommandPtr cmd(new InsertParagraphSeparatorCommand(document()));
+ applyCommandToComposite(cmd);
+ typingAddedToOpenCommand();
+}
+
+void TypingCommand::insertParagraphSeparatorInQuotedContent()
+{
+ EditCommandPtr cmd(new BreakBlockquoteCommand(document()));
+ applyCommandToComposite(cmd);
+ typingAddedToOpenCommand();
+}
+
+void TypingCommand::deleteKeyPressed()
+{
+ Selection selectionToDelete;
+
+ switch (endingSelection().state()) {
+ case Selection::RANGE:
+ selectionToDelete = endingSelection();
+ break;
+ case Selection::CARET: {
+ // Handle delete at beginning-of-block case.
+ // Do nothing in the case that the caret is at the start of a
+ // root editable element or at the start of a document.
+ Position pos(endingSelection().start());
+ Position start = VisiblePosition(pos, endingSelection().startAffinity()).previous().deepEquivalent();
+ Position end = VisiblePosition(pos, endingSelection().startAffinity()).deepEquivalent();
+ if (start.isNotNull() && end.isNotNull() && start.node()->rootEditableElement() == end.node()->rootEditableElement())
+ selectionToDelete = Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY);
+ break;
+ }
+ case Selection::NONE:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ if (selectionToDelete.isCaretOrRange()) {
+ deleteSelection(selectionToDelete, m_smartDelete);
+ setSmartDelete(false);
+ typingAddedToOpenCommand();
+ }
+}
+
+void TypingCommand::forwardDeleteKeyPressed()
+{
+ Selection selectionToDelete;
+
+ switch (endingSelection().state()) {
+ case Selection::RANGE:
+ selectionToDelete = endingSelection();
+ break;
+ case Selection::CARET: {
+ // Handle delete at beginning-of-block case.
+ // Do nothing in the case that the caret is at the start of a
+ // root editable element or at the start of a document.
+ Position pos(endingSelection().start());
+ Position start = VisiblePosition(pos, endingSelection().startAffinity()).next().deepEquivalent();
+ Position end = VisiblePosition(pos, endingSelection().startAffinity()).deepEquivalent();
+ if (start.isNotNull() && end.isNotNull() && start.node()->rootEditableElement() == end.node()->rootEditableElement())
+ selectionToDelete = Selection(start, SEL_DEFAULT_AFFINITY, end, SEL_DEFAULT_AFFINITY);
+ break;
+ }
+ case Selection::NONE:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ if (selectionToDelete.isCaretOrRange()) {
+ deleteSelection(selectionToDelete, m_smartDelete);
+ setSmartDelete(false);
+ typingAddedToOpenCommand();
+ }
+}
+
+bool TypingCommand::preservesTypingStyle() const
+{
+ switch (m_commandType) {
+ case DeleteKey:
+ case ForwardDeleteKey:
+ case InsertParagraphSeparator:
+ case InsertLineBreak:
+ return true;
+ case InsertParagraphSeparatorInQuotedContent:
+ case InsertText:
+ return false;
+ }
+ ASSERT_NOT_REACHED();
+ return false;
+}
+
+bool TypingCommand::isTypingCommand() const
+{
+ return true;
+}
+
+} // namespace khtml
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __typing_command_h__
+#define __typing_command_h__
+
+#include "composite_edit_command.h"
+#include "dom/dom_string.h"
+
+namespace khtml {
+
+class TypingCommand : public CompositeEditCommand
+{
+public:
+ enum ETypingCommand {
+ DeleteKey,
+ ForwardDeleteKey,
+ InsertText,
+ InsertLineBreak,
+ InsertParagraphSeparator,
+ InsertParagraphSeparatorInQuotedContent,
+ };
+
+ TypingCommand(DOM::DocumentImpl *document, ETypingCommand, const DOM::DOMString &text = "", bool selectInsertedText = false);
+
+ static void deleteKeyPressed(DOM::DocumentImpl *, bool smartDelete = false);
+ static void forwardDeleteKeyPressed(DOM::DocumentImpl *, bool smartDelete = false);
+ static void insertText(DOM::DocumentImpl *, const DOM::DOMString &, bool selectInsertedText = false);
+ static void insertLineBreak(DOM::DocumentImpl *);
+ static void insertParagraphSeparator(DOM::DocumentImpl *);
+ static void insertParagraphSeparatorInQuotedContent(DOM::DocumentImpl *);
+ static bool isOpenForMoreTypingCommand(const EditCommandPtr &);
+ static void closeTyping(const EditCommandPtr &);
+
+ virtual void doApply();
+ virtual EditAction editingAction() const;
+
+ bool openForMoreTyping() const { return m_openForMoreTyping; }
+ void closeTyping() { m_openForMoreTyping = false; }
+
+ void insertText(const DOM::DOMString &text, bool selectInsertedText);
+ void insertTextRunWithoutNewlines(const DOM::DOMString &text, bool selectInsertedText);
+ void insertLineBreak();
+ void insertParagraphSeparatorInQuotedContent();
+ void insertParagraphSeparator();
+ void deleteKeyPressed();
+ void forwardDeleteKeyPressed();
+
+ bool smartDelete() { return m_smartDelete; }
+ void setSmartDelete(bool smartDelete) { m_smartDelete = smartDelete; }
+
+private:
+ virtual bool isTypingCommand() const;
+ virtual bool preservesTypingStyle() const;
+
+ void markMisspellingsAfterTyping();
+ void typingAddedToOpenCommand();
+
+ ETypingCommand m_commandType;
+ DOM::DOMString m_textToInsert;
+ bool m_openForMoreTyping;
+ bool m_applyEditing;
+ bool m_selectInsertedText;
+ bool m_smartDelete;
+};
+
+} // namespace khtml
+
+#endif // __typing_command_h__
--- /dev/null
+/*
+ * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "wrap_contents_in_dummy_span_command.h"
+
+#include "apply_style_command.h"
+#include "xml/dom_elementimpl.h"
+
+#if APPLE_CHANGES
+#include "KWQAssertions.h"
+#else
+#define ASSERT(assertion) assert(assertion)
+#endif
+
+using DOM::DocumentImpl;
+using DOM::ElementImpl;
+
+namespace khtml {
+
+WrapContentsInDummySpanCommand::WrapContentsInDummySpanCommand(DOM::DocumentImpl *document, DOM::ElementImpl *element)
+ : EditCommand(document), m_element(element), m_dummySpan(0)
+{
+ ASSERT(m_element);
+
+