Repaint rect too small on elements with shadows
[WebKit-https.git] / Source / WebCore / editing / mac / EditorMac.mm
1 /*
2  * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27 #import "Editor.h"
28
29 #import "ColorMac.h"
30 #import "ClipboardMac.h"
31 #import "CachedResourceLoader.h"
32 #import "DocumentFragment.h"
33 #import "DOMRangeInternal.h"
34 #import "Editor.h"
35 #import "EditorClient.h"
36 #import "Font.h"
37 #import "Frame.h"
38 #import "FrameView.h"
39 #import "HTMLConverter.h"
40 #import "HTMLNames.h"
41 #import "LegacyWebArchive.h"
42 #import "NodeTraversal.h"
43 #import "Pasteboard.h"
44 #import "PasteboardStrategy.h"
45 #import "PlatformStrategies.h"
46 #import "Range.h"
47 #import "RenderBlock.h"
48 #import "RuntimeApplicationChecks.h"
49 #import "Sound.h"
50 #import "StylePropertySet.h"
51 #import "Text.h"
52 #import "TypingCommand.h"
53 #import "htmlediting.h"
54 #import "WebNSAttributedStringExtras.h"
55
56 namespace WebCore {
57
58 using namespace HTMLNames;
59
60 PassRefPtr<Clipboard> Editor::newGeneralClipboard(ClipboardAccessPolicy policy, Frame* frame)
61 {
62     return ClipboardMac::create(Clipboard::CopyAndPaste,
63         policy == ClipboardWritable ? platformStrategies()->pasteboardStrategy()->uniqueName() : String(NSGeneralPboard), policy, ClipboardMac::CopyAndPasteGeneric, frame);
64 }
65
66 void Editor::showFontPanel()
67 {
68     [[NSFontManager sharedFontManager] orderFrontFontPanel:nil];
69 }
70
71 void Editor::showStylesPanel()
72 {
73     [[NSFontManager sharedFontManager] orderFrontStylesPanel:nil];
74 }
75
76 void Editor::showColorPanel()
77 {
78     [[NSApplication sharedApplication] orderFrontColorPanel:nil];
79 }
80
81 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText)
82 {
83     RefPtr<Range> range = selectedRange();
84     bool choosePlainText;
85     
86     m_frame->editor()->client()->setInsertionPasteboard(NSGeneralPboard);
87 #if PLATFORM(IOS) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
88     RefPtr<DocumentFragment> fragment = pasteboard->documentFragment(m_frame, range, allowPlainText, choosePlainText);
89     if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
90         pasteAsFragment(fragment, canSmartReplaceWithPasteboard(pasteboard), false);
91 #else
92     // Mail is ignoring the frament passed to the delegate and creates a new one.
93     // We want to avoid creating the fragment twice.
94     if (applicationIsAppleMail()) {
95         if (shouldInsertFragment(NULL, range, EditorInsertActionPasted)) {
96             RefPtr<DocumentFragment> fragment = pasteboard->documentFragment(m_frame, range, allowPlainText, choosePlainText);
97             if (fragment)
98                 pasteAsFragment(fragment, canSmartReplaceWithPasteboard(pasteboard), false);
99         }        
100     } else {
101         RefPtr<DocumentFragment>fragment = pasteboard->documentFragment(m_frame, range, allowPlainText, choosePlainText);
102         if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
103             pasteAsFragment(fragment, canSmartReplaceWithPasteboard(pasteboard), false);
104     }
105 #endif
106     m_frame->editor()->client()->setInsertionPasteboard(String());
107 }
108
109 bool Editor::insertParagraphSeparatorInQuotedContent()
110 {
111     // FIXME: Why is this missing calls to canEdit, canEditRichly, etc...
112     TypingCommand::insertParagraphSeparatorInQuotedContent(m_frame->document());
113     revealSelectionAfterEditingOperation();
114     return true;
115 }
116
117 static RenderStyle* styleForSelectionStart(Frame* frame, Node *&nodeToRemove)
118 {
119     nodeToRemove = 0;
120
121     if (frame->selection()->isNone())
122         return 0;
123
124     Position position = frame->selection()->selection().visibleStart().deepEquivalent();
125     if (!position.isCandidate() || position.isNull())
126         return 0;
127
128     RefPtr<EditingStyle> typingStyle = frame->selection()->typingStyle();
129     if (!typingStyle || !typingStyle->style())
130         return position.deprecatedNode()->renderer()->style();
131
132     RefPtr<Element> styleElement = frame->document()->createElement(spanTag, false);
133
134     String styleText = typingStyle->style()->asText() + " display: inline";
135     styleElement->setAttribute(styleAttr, styleText.impl());
136
137     styleElement->appendChild(frame->document()->createEditingTextNode(""), ASSERT_NO_EXCEPTION);
138
139     position.deprecatedNode()->parentNode()->appendChild(styleElement, ASSERT_NO_EXCEPTION);
140
141     nodeToRemove = styleElement.get();
142     return styleElement->renderer() ? styleElement->renderer()->style() : 0;
143 }
144
145 const SimpleFontData* Editor::fontForSelection(bool& hasMultipleFonts) const
146 {
147     hasMultipleFonts = false;
148
149     if (!m_frame->selection()->isRange()) {
150         Node* nodeToRemove;
151         RenderStyle* style = styleForSelectionStart(m_frame, nodeToRemove); // sets nodeToRemove
152
153         const SimpleFontData* result = 0;
154         if (style)
155             result = style->font().primaryFont();
156
157         if (nodeToRemove)
158             nodeToRemove->remove(ASSERT_NO_EXCEPTION);
159
160         return result;
161     }
162
163     const SimpleFontData* font = 0;
164     RefPtr<Range> range = m_frame->selection()->toNormalizedRange();
165     Node* startNode = adjustedSelectionStartForStyleComputation(m_frame->selection()->selection()).deprecatedNode();
166     if (range && startNode) {
167         Node* pastEnd = range->pastLastNode();
168         // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one
169         // unreproducible case where this didn't happen, so check for null also.
170         for (Node* node = startNode; node && node != pastEnd; node = NodeTraversal::next(node)) {
171             RenderObject* renderer = node->renderer();
172             if (!renderer)
173                 continue;
174             // FIXME: Are there any node types that have renderers, but that we should be skipping?
175             const SimpleFontData* primaryFont = renderer->style()->font().primaryFont();
176             if (!font)
177                 font = primaryFont;
178             else if (font != primaryFont) {
179                 hasMultipleFonts = true;
180                 break;
181             }
182         }
183     }
184
185     return font;
186 }
187
188 NSDictionary* Editor::fontAttributesForSelectionStart() const
189 {
190     Node* nodeToRemove;
191     RenderStyle* style = styleForSelectionStart(m_frame, nodeToRemove);
192     if (!style)
193         return nil;
194
195     NSMutableDictionary* result = [NSMutableDictionary dictionary];
196
197     if (style->visitedDependentColor(CSSPropertyBackgroundColor).isValid() && style->visitedDependentColor(CSSPropertyBackgroundColor).alpha() != 0)
198         [result setObject:nsColor(style->visitedDependentColor(CSSPropertyBackgroundColor)) forKey:NSBackgroundColorAttributeName];
199
200     if (style->font().primaryFont()->getNSFont())
201         [result setObject:style->font().primaryFont()->getNSFont() forKey:NSFontAttributeName];
202
203     if (style->visitedDependentColor(CSSPropertyColor).isValid() && style->visitedDependentColor(CSSPropertyColor) != Color::black)
204         [result setObject:nsColor(style->visitedDependentColor(CSSPropertyColor)) forKey:NSForegroundColorAttributeName];
205
206     const ShadowData* shadow = style->textShadow();
207     if (shadow) {
208         RetainPtr<NSShadow> s(AdoptNS, [[NSShadow alloc] init]);
209         [s.get() setShadowOffset:NSMakeSize(shadow->x(), shadow->y())];
210         [s.get() setShadowBlurRadius:shadow->radius()];
211         [s.get() setShadowColor:nsColor(shadow->color())];
212         [result setObject:s.get() forKey:NSShadowAttributeName];
213     }
214
215     int decoration = style->textDecorationsInEffect();
216     if (decoration & LINE_THROUGH)
217         [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName];
218
219     int superscriptInt = 0;
220     switch (style->verticalAlign()) {
221         case BASELINE:
222         case BOTTOM:
223         case BASELINE_MIDDLE:
224         case LENGTH:
225         case MIDDLE:
226         case TEXT_BOTTOM:
227         case TEXT_TOP:
228         case TOP:
229             break;
230         case SUB:
231             superscriptInt = -1;
232             break;
233         case SUPER:
234             superscriptInt = 1;
235             break;
236     }
237     if (superscriptInt)
238         [result setObject:[NSNumber numberWithInt:superscriptInt] forKey:NSSuperscriptAttributeName];
239
240     if (decoration & UNDERLINE)
241         [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
242
243     if (nodeToRemove)
244         nodeToRemove->remove(ASSERT_NO_EXCEPTION);
245
246     return result;
247 }
248
249 bool Editor::canCopyExcludingStandaloneImages()
250 {
251     FrameSelection* selection = m_frame->selection();
252     return selection->isRange() && !selection->isInPasswordField();
253 }
254
255 void Editor::takeFindStringFromSelection()
256 {
257     if (!canCopyExcludingStandaloneImages()) {
258         systemBeep();
259         return;
260     }
261
262     Vector<String> types;
263     types.append(String(NSStringPboardType));
264     platformStrategies()->pasteboardStrategy()->setTypes(types, NSFindPboard);
265     platformStrategies()->pasteboardStrategy()->setStringForType(m_frame->displayStringModifiedByEncoding(selectedTextForClipboard()), NSStringPboardType, NSFindPboard);
266 }
267
268 void Editor::writeSelectionToPasteboard(const String& pasteboardName, const Vector<String>& pasteboardTypes)
269 {
270     Pasteboard pasteboard(pasteboardName);
271     pasteboard.writeSelectionForTypes(pasteboardTypes, true, m_frame, DefaultSelectedTextType);
272 }
273     
274 void Editor::readSelectionFromPasteboard(const String& pasteboardName)
275 {
276     Pasteboard pasteboard(pasteboardName);
277     if (m_frame->selection()->isContentRichlyEditable())
278         pasteWithPasteboard(&pasteboard, true);
279     else
280         pasteAsPlainTextWithPasteboard(&pasteboard);   
281 }
282
283 String Editor::stringSelectionForPasteboard()
284 {
285     return Pasteboard::getStringSelection(m_frame, DefaultSelectedTextType);
286 }
287
288 PassRefPtr<SharedBuffer> Editor::dataSelectionForPasteboard(const String& pasteboardType)
289 {
290     return Pasteboard::getDataSelection(m_frame, pasteboardType);
291 }
292
293 } // namespace WebCore