|
1 /* |
|
2 * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved. |
|
3 * (C) 2006, 2007 Graham Dennis (graham.dennis@gmail.com) |
|
4 * |
|
5 * Redistribution and use in source and binary forms, with or without |
|
6 * modification, are permitted provided that the following conditions |
|
7 * are met: |
|
8 * |
|
9 * 1. Redistributions of source code must retain the above copyright |
|
10 * notice, this list of conditions and the following disclaimer. |
|
11 * 2. Redistributions in binary form must reproduce the above copyright |
|
12 * notice, this list of conditions and the following disclaimer in the |
|
13 * documentation and/or other materials provided with the distribution. |
|
14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
|
15 * its contributors may be used to endorse or promote products derived |
|
16 * from this software without specific prior written permission. |
|
17 * |
|
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
|
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
|
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
28 */ |
|
29 |
|
30 #import "WebHTMLView.h" |
|
31 |
|
32 #import "DOMNodeInternal.h" |
|
33 #import "DOMRangeInternal.h" |
|
34 #import "WebArchive.h" |
|
35 #import "WebArchiver.h" |
|
36 #import "WebBaseNetscapePluginViewInternal.h" |
|
37 #import "WebClipView.h" |
|
38 #import "WebDOMOperationsPrivate.h" |
|
39 #import "WebDataSourceInternal.h" |
|
40 #import "WebDefaultUIDelegate.h" |
|
41 #import "WebDocumentInternal.h" |
|
42 #import "WebDynamicScrollBarsView.h" |
|
43 #import "WebEditingDelegate.h" |
|
44 #import "WebElementDictionary.h" |
|
45 #import "WebFrameBridge.h" |
|
46 #import "WebFrameInternal.h" |
|
47 #import "WebFramePrivate.h" |
|
48 #import "WebFrameViewInternal.h" |
|
49 #import "WebHTMLRepresentationPrivate.h" |
|
50 #import "WebHTMLViewInternal.h" |
|
51 #import "WebKitLogging.h" |
|
52 #import "WebKitNSStringExtras.h" |
|
53 #import "WebKitPluginContainerView.h" |
|
54 #import "WebKitVersionChecks.h" |
|
55 #import "WebLocalizableStrings.h" |
|
56 #import "WebNSAttributedStringExtras.h" |
|
57 #import "WebNSEventExtras.h" |
|
58 #import "WebNSFileManagerExtras.h" |
|
59 #import "WebNSImageExtras.h" |
|
60 #import "WebNSObjectExtras.h" |
|
61 #import "WebNSPasteboardExtras.h" |
|
62 #import "WebNSPrintOperationExtras.h" |
|
63 #import "WebNSURLExtras.h" |
|
64 #import "WebNSViewExtras.h" |
|
65 #import "WebNetscapePluginEmbeddedView.h" |
|
66 #import "WebPluginController.h" |
|
67 #import "WebPreferences.h" |
|
68 #import "WebPreferencesPrivate.h" |
|
69 #import "WebResourcePrivate.h" |
|
70 #import "WebStringTruncator.h" |
|
71 #import "WebUIDelegatePrivate.h" |
|
72 #import "WebViewInternal.h" |
|
73 #import <AppKit/NSAccessibility.h> |
|
74 #import <ApplicationServices/ApplicationServices.h> |
|
75 #import <dlfcn.h> |
|
76 #import <WebCore/CachedImage.h> |
|
77 #import <WebCore/CachedResourceClient.h> |
|
78 #import <WebCore/ColorMac.h> |
|
79 #import <WebCore/ContextMenu.h> |
|
80 #import <WebCore/ContextMenuController.h> |
|
81 #import <WebCore/Document.h> |
|
82 #import <WebCore/Editor.h> |
|
83 #import <WebCore/EditorDeleteAction.h> |
|
84 #import <WebCore/Element.h> |
|
85 #import <WebCore/EventHandler.h> |
|
86 #import <WebCore/EventNames.h> |
|
87 #import <WebCore/ExceptionHandlers.h> |
|
88 #import <WebCore/DragController.h> |
|
89 #import <WebCore/FloatRect.h> |
|
90 #import <WebCore/FocusController.h> |
|
91 #import <WebCore/Frame.h> |
|
92 #import <WebCore/FrameLoader.h> |
|
93 #import <WebCore/FrameView.h> |
|
94 #import <WebCore/HitTestResult.h> |
|
95 #import <WebCore/HTMLNames.h> |
|
96 #import <WebCore/Image.h> |
|
97 #import <WebCore/KeyboardEvent.h> |
|
98 #import <WebCore/MIMETypeRegistry.h> |
|
99 #import <WebCore/Page.h> |
|
100 #import <WebCore/PlatformKeyboardEvent.h> |
|
101 #import <WebCore/PlatformMouseEvent.h> |
|
102 #import <WebCore/Range.h> |
|
103 #import <WebCore/SelectionController.h> |
|
104 #import <WebCore/SharedBuffer.h> |
|
105 #import <WebCore/Text.h> |
|
106 #import <WebCore/WebCoreObjCExtras.h> |
|
107 #import <WebCore/WebCoreTextRenderer.h> |
|
108 #import <WebKit/DOM.h> |
|
109 #import <WebKit/DOMExtensions.h> |
|
110 #import <WebKit/DOMPrivate.h> |
|
111 #import <WebKitSystemInterface.h> |
|
112 |
|
113 using namespace WebCore; |
|
114 using namespace HTMLNames; |
|
115 |
|
116 @interface NSWindow (BorderViewAccess) |
|
117 - (NSView*)_web_borderView; |
|
118 @end |
|
119 |
|
120 @implementation NSWindow (BorderViewAccess) |
|
121 - (NSView*)_web_borderView |
|
122 { |
|
123 return _borderView; |
|
124 } |
|
125 @end |
|
126 |
|
127 static IMP oldSetCursorIMP = NULL; |
|
128 |
|
129 #ifdef BUILDING_ON_TIGER |
|
130 static IMP oldResetCursorRectsIMP = NULL; |
|
131 static BOOL canSetCursor = YES; |
|
132 |
|
133 static void resetCursorRects(NSWindow* self, SEL cmd) |
|
134 { |
|
135 NSPoint point = [self mouseLocationOutsideOfEventStream]; |
|
136 NSView* view = [[self _web_borderView] hitTest:point]; |
|
137 if ([view isKindOfClass:[WebHTMLView class]]) { |
|
138 WebHTMLView *htmlView = (WebHTMLView*)view; |
|
139 NSPoint localPoint = [htmlView convertPoint:point fromView:nil]; |
|
140 NSDictionary *dict = [htmlView elementAtPoint:point allowShadowContent:NO]; |
|
141 DOMElement *element = [dict objectForKey:WebElementDOMNodeKey]; |
|
142 if (![element isKindOfClass:[DOMHTMLAppletElement class]] && ![element isKindOfClass:[DOMHTMLObjectElement class]] && |
|
143 ![element isKindOfClass:[DOMHTMLEmbedElement class]]) |
|
144 canSetCursor = NO; |
|
145 } |
|
146 oldResetCursorRectsIMP(self, cmd); |
|
147 canSetCursor = YES; |
|
148 } |
|
149 |
|
150 static void setCursor(NSCursor* self, SEL cmd) |
|
151 { |
|
152 if (canSetCursor) |
|
153 oldSetCursorIMP(self, cmd); |
|
154 } |
|
155 #else |
|
156 static void setCursor(NSWindow* self, SEL cmd, NSPoint point) |
|
157 { |
|
158 NSView* view = [[self _web_borderView] hitTest:point]; |
|
159 if ([view isKindOfClass:[WebHTMLView class]]) { |
|
160 WebHTMLView *htmlView = (WebHTMLView*)view; |
|
161 NSPoint localPoint = [htmlView convertPoint:point fromView:nil]; |
|
162 NSDictionary *dict = [htmlView elementAtPoint:point allowShadowContent:NO]; |
|
163 DOMElement *element = [dict objectForKey:WebElementDOMNodeKey]; |
|
164 if (![element isKindOfClass:[DOMHTMLAppletElement class]] && ![element isKindOfClass:[DOMHTMLObjectElement class]] && |
|
165 ![element isKindOfClass:[DOMHTMLEmbedElement class]]) |
|
166 return; |
|
167 } |
|
168 oldSetCursorIMP(self, cmd, point); |
|
169 } |
|
170 #endif |
|
171 |
|
172 extern "C" { |
|
173 |
|
174 // Need to declare these attribute names because AppKit exports them but does not make them available in API or SPI headers. |
|
175 |
|
176 extern NSString *NSMarkedClauseSegmentAttributeName; |
|
177 extern NSString *NSTextInputReplacementRangeAttributeName; |
|
178 |
|
179 // Kill ring calls. Would be better to use NSKillRing.h, but that's not available as API or SPI. |
|
180 |
|
181 void _NSInitializeKillRing(void); |
|
182 void _NSAppendToKillRing(NSString *); |
|
183 void _NSPrependToKillRing(NSString *); |
|
184 NSString *_NSYankFromKillRing(void); |
|
185 NSString *_NSYankPreviousFromKillRing(void); |
|
186 void _NSNewKillRingSequence(void); |
|
187 void _NSSetKillRingToYankedState(void); |
|
188 void _NSResetKillRingOperationFlag(void); |
|
189 |
|
190 } |
|
191 |
|
192 @interface NSView (AppKitSecretsIKnowAbout) |
|
193 - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView; |
|
194 - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect; |
|
195 - (NSRect)_dirtyRect; |
|
196 - (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants; |
|
197 - (void)_propagateDirtyRectsToOpaqueAncestors; |
|
198 - (void)_windowChangedKeyState; |
|
199 @end |
|
200 |
|
201 @interface NSApplication (AppKitSecretsIKnowAbout) |
|
202 - (void)speakString:(NSString *)string; |
|
203 @end |
|
204 |
|
205 @interface NSWindow (AppKitSecretsIKnowAbout) |
|
206 - (id)_newFirstResponderAfterResigning; |
|
207 - (void)_setForceActiveControls:(BOOL)flag; |
|
208 @end |
|
209 |
|
210 @interface NSAttributedString (AppKitSecretsIKnowAbout) |
|
211 - (id)_initWithDOMRange:(DOMRange *)domRange; |
|
212 - (DOMDocumentFragment *)_documentFromRange:(NSRange)range document:(DOMDocument *)document documentAttributes:(NSDictionary *)dict subresources:(NSArray **)subresources; |
|
213 @end |
|
214 |
|
215 @interface NSSpellChecker (CurrentlyPrivateForTextView) |
|
216 - (void)learnWord:(NSString *)word; |
|
217 @end |
|
218 |
|
219 // By imaging to a width a little wider than the available pixels, |
|
220 // thin pages will be scaled down a little, matching the way they |
|
221 // print in IE and Camino. This lets them use fewer sheets than they |
|
222 // would otherwise, which is presumably why other browsers do this. |
|
223 // Wide pages will be scaled down more than this. |
|
224 #define PrintingMinimumShrinkFactor 1.25f |
|
225 |
|
226 // This number determines how small we are willing to reduce the page content |
|
227 // in order to accommodate the widest line. If the page would have to be |
|
228 // reduced smaller to make the widest line fit, we just clip instead (this |
|
229 // behavior matches MacIE and Mozilla, at least) |
|
230 #define PrintingMaximumShrinkFactor 2.0f |
|
231 |
|
232 // This number determines how short the last printed page of a multi-page print session |
|
233 // can be before we try to shrink the scale in order to reduce the number of pages, and |
|
234 // thus eliminate the orphan. |
|
235 #define LastPrintedPageOrphanRatio 0.1f |
|
236 |
|
237 // This number determines the amount the scale factor is adjusted to try to eliminate orphans. |
|
238 // It has no direct mathematical relationship to LastPrintedPageOrphanRatio, due to variable |
|
239 // numbers of pages, logic to avoid breaking elements, and CSS-supplied hard page breaks. |
|
240 #define PrintingOrphanShrinkAdjustment 1.1f |
|
241 |
|
242 #define AUTOSCROLL_INTERVAL 0.1f |
|
243 |
|
244 #define DRAG_LABEL_BORDER_X 4.0f |
|
245 //Keep border_y in synch with DragController::LinkDragBorderInset |
|
246 #define DRAG_LABEL_BORDER_Y 2.0f |
|
247 #define DRAG_LABEL_RADIUS 5.0f |
|
248 #define DRAG_LABEL_BORDER_Y_OFFSET 2.0f |
|
249 |
|
250 #define MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP 120.0f |
|
251 #define MAX_DRAG_LABEL_WIDTH 320.0f |
|
252 |
|
253 #define DRAG_LINK_LABEL_FONT_SIZE 11.0f |
|
254 #define DRAG_LINK_URL_FONT_SIZE 10.0f |
|
255 |
|
256 // Any non-zero value will do, but using something recognizable might help us debug some day. |
|
257 #define TRACKING_RECT_TAG 0xBADFACE |
|
258 |
|
259 // FIXME: This constant is copied from AppKit's _NXSmartPaste constant. |
|
260 #define WebSmartPastePboardType @"NeXT smart paste pasteboard type" |
|
261 |
|
262 #define STANDARD_WEIGHT 5 |
|
263 #define MIN_BOLD_WEIGHT 9 |
|
264 #define STANDARD_BOLD_WEIGHT 10 |
|
265 |
|
266 // Fake URL scheme. |
|
267 #define WebDataProtocolScheme @"webkit-fake-url" |
|
268 |
|
269 // <rdar://problem/4985524> References to WebCoreScrollView as a subview of a WebHTMLView may be present |
|
270 // in some NIB files, so NSUnarchiver must be still able to look up this now-unused class. |
|
271 @interface WebCoreScrollView : NSScrollView |
|
272 @end |
|
273 |
|
274 @implementation WebCoreScrollView |
|
275 @end |
|
276 |
|
277 // if YES, do the standard NSView hit test (which can't give the right result when HTML overlaps a view) |
|
278 static BOOL forceNSViewHitTest; |
|
279 |
|
280 // if YES, do the "top WebHTMLView" hit test (which we'd like to do all the time but can't because of Java requirements [see bug 4349721]) |
|
281 static BOOL forceWebHTMLViewHitTest; |
|
282 |
|
283 static WebHTMLView *lastHitView; |
|
284 |
|
285 // We need this to be able to safely reference the CachedImage for the promised drag data |
|
286 static CachedResourceClient* promisedDataClient() |
|
287 { |
|
288 static CachedResourceClient* staticCachedResourceClient = new CachedResourceClient; |
|
289 return staticCachedResourceClient; |
|
290 } |
|
291 |
|
292 @interface WebHTMLView (WebTextSizing) <_WebDocumentTextSizing> |
|
293 @end |
|
294 |
|
295 @interface WebHTMLView (WebHTMLViewFileInternal) |
|
296 - (BOOL)_imageExistsAtPaths:(NSArray *)paths; |
|
297 - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard inContext:(DOMRange *)context allowPlainText:(BOOL)allowPlainText chosePlainText:(BOOL *)chosePlainText; |
|
298 - (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard; |
|
299 - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText; |
|
300 - (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard; |
|
301 - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action; |
|
302 - (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action; |
|
303 - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action; |
|
304 - (float)_calculatePrintHeight; |
|
305 - (void)_updateTextSizeMultiplier; |
|
306 - (DOMRange *)_selectedRange; |
|
307 - (BOOL)_shouldDeleteRange:(DOMRange *)range; |
|
308 - (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard; |
|
309 - (NSView *)_hitViewForEvent:(NSEvent *)event; |
|
310 - (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString; |
|
311 - (DOMRange *)_documentRange; |
|
312 - (WebFrameBridge *)_bridge; |
|
313 - (void)_setMouseDownEvent:(NSEvent *)event; |
|
314 - (WebHTMLView *)_topHTMLView; |
|
315 - (BOOL)_isTopHTMLView; |
|
316 - (void)_web_setPrintingModeRecursive; |
|
317 - (void)_web_setPrintingModeRecursiveAndAdjustViewSize; |
|
318 - (void)_web_clearPrintingModeRecursive; |
|
319 @end |
|
320 |
|
321 @interface WebHTMLView (WebForwardDeclaration) // FIXME: Put this in a normal category and stop doing the forward declaration trick. |
|
322 - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize; |
|
323 @end |
|
324 |
|
325 @interface WebHTMLView (WebNSTextInputSupport) <NSTextInput> |
|
326 - (void)_updateSelectionForInputManager; |
|
327 @end |
|
328 |
|
329 @interface WebHTMLView (WebEditingStyleSupport) |
|
330 - (DOMCSSStyleDeclaration *)_emptyStyle; |
|
331 - (NSString *)_colorAsString:(NSColor *)color; |
|
332 @end |
|
333 |
|
334 @interface NSView (WebHTMLViewFileInternal) |
|
335 - (void)_web_addDescendantWebHTMLViewsToArray:(NSMutableArray *) array; |
|
336 @end |
|
337 |
|
338 @interface NSMutableDictionary (WebHTMLViewFileInternal) |
|
339 - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key; |
|
340 @end |
|
341 |
|
342 // Handles the complete: text command |
|
343 @interface WebTextCompleteController : NSObject { |
|
344 @private |
|
345 WebHTMLView *_view; |
|
346 NSWindow *_popupWindow; |
|
347 NSTableView *_tableView; |
|
348 NSArray *_completions; |
|
349 NSString *_originalString; |
|
350 int prefixLength; |
|
351 } |
|
352 - (id)initWithHTMLView:(WebHTMLView *)view; |
|
353 - (void)doCompletion; |
|
354 - (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft; |
|
355 - (BOOL)popupWindowIsOpen; |
|
356 - (BOOL)filterKeyDown:(NSEvent *)event; |
|
357 - (void)_reflectSelection; |
|
358 @end |
|
359 |
|
360 struct WebHTMLViewInterpretKeyEventsParameters { |
|
361 KeyboardEvent* event; |
|
362 BOOL eventWasHandled; |
|
363 BOOL shouldSaveCommand; |
|
364 // The Input Method may consume an event and not tell us, in |
|
365 // which case we should not bubble the event up the DOM |
|
366 BOOL consumedByIM; |
|
367 }; |
|
368 |
|
369 @implementation WebHTMLViewPrivate |
|
370 |
|
371 |
|
372 + (void)initialize |
|
373 { |
|
374 #ifndef BUILDING_ON_TIGER |
|
375 WebCoreObjCFinalizeOnMainThread(self); |
|
376 #endif |
|
377 |
|
378 if (!oldSetCursorIMP) { |
|
379 #ifdef BUILDING_ON_TIGER |
|
380 Method setCursorMethod = class_getInstanceMethod([NSCursor class], @selector(set)); |
|
381 #else |
|
382 Method setCursorMethod = class_getInstanceMethod([NSWindow class], @selector(_setCursorForMouseLocation:)); |
|
383 #endif |
|
384 ASSERT(setCursorMethod); |
|
385 |
|
386 oldSetCursorIMP = method_setImplementation(setCursorMethod, (IMP)setCursor); |
|
387 ASSERT(oldSetCursorIMP); |
|
388 } |
|
389 |
|
390 #ifdef BUILDING_ON_TIGER |
|
391 if (!oldResetCursorRectsIMP) { |
|
392 Method resetCursorRectsMethod = class_getInstanceMethod([NSWindow class], @selector(resetCursorRects)); |
|
393 ASSERT(resetCursorRectsMethod); |
|
394 oldResetCursorRectsIMP = method_setImplementation(resetCursorRectsMethod, (IMP)resetCursorRects); |
|
395 ASSERT(oldResetCursorRectsIMP); |
|
396 } |
|
397 #endif |
|
398 |
|
399 } |
|
400 |
|
401 - (void)dealloc |
|
402 { |
|
403 ASSERT(!autoscrollTimer); |
|
404 ASSERT(!autoscrollTriggerEvent); |
|
405 ASSERT(!updateActiveStateTimer); |
|
406 ASSERT(!updateMouseoverTimer); |
|
407 |
|
408 [mouseDownEvent release]; |
|
409 [keyDownEvent release]; |
|
410 [pluginController release]; |
|
411 [toolTip release]; |
|
412 [compController release]; |
|
413 [firstResponderTextViewAtMouseDownTime release]; |
|
414 [dataSource release]; |
|
415 [highlighters release]; |
|
416 if (promisedDragTIFFDataSource) |
|
417 promisedDragTIFFDataSource->deref(promisedDataClient()); |
|
418 |
|
419 [super dealloc]; |
|
420 } |
|
421 |
|
422 - (void)finalize |
|
423 { |
|
424 ASSERT_MAIN_THREAD(); |
|
425 |
|
426 if (promisedDragTIFFDataSource) |
|
427 promisedDragTIFFDataSource->deref(promisedDataClient()); |
|
428 |
|
429 [super finalize]; |
|
430 } |
|
431 |
|
432 - (void)clear |
|
433 { |
|
434 [mouseDownEvent release]; |
|
435 [keyDownEvent release]; |
|
436 [pluginController release]; |
|
437 [toolTip release]; |
|
438 [compController release]; |
|
439 [firstResponderTextViewAtMouseDownTime release]; |
|
440 [dataSource release]; |
|
441 [highlighters release]; |
|
442 if (promisedDragTIFFDataSource) |
|
443 promisedDragTIFFDataSource->deref(promisedDataClient()); |
|
444 |
|
445 mouseDownEvent = nil; |
|
446 keyDownEvent = nil; |
|
447 pluginController = nil; |
|
448 toolTip = nil; |
|
449 compController = nil; |
|
450 firstResponderTextViewAtMouseDownTime = nil; |
|
451 dataSource = nil; |
|
452 highlighters = nil; |
|
453 promisedDragTIFFDataSource = 0; |
|
454 } |
|
455 |
|
456 @end |
|
457 |
|
458 @implementation WebHTMLView (WebHTMLViewFileInternal) |
|
459 |
|
460 - (DOMRange *)_documentRange |
|
461 { |
|
462 return [[[self _frame] DOMDocument] _documentRange]; |
|
463 } |
|
464 |
|
465 - (BOOL)_imageExistsAtPaths:(NSArray *)paths |
|
466 { |
|
467 NSEnumerator *enumerator = [paths objectEnumerator]; |
|
468 NSString *path; |
|
469 |
|
470 while ((path = [enumerator nextObject]) != nil) { |
|
471 NSString *MIMEType = WKGetMIMETypeForExtension([path pathExtension]); |
|
472 if (MIMETypeRegistry::isSupportedImageResourceMIMEType(MIMEType)) |
|
473 return YES; |
|
474 } |
|
475 |
|
476 return NO; |
|
477 } |
|
478 |
|
479 - (WebDataSource *)_dataSource |
|
480 { |
|
481 return _private->dataSource; |
|
482 } |
|
483 |
|
484 - (WebFrameBridge *)_bridge |
|
485 { |
|
486 return [_private->dataSource _bridge]; |
|
487 } |
|
488 |
|
489 - (WebView *)_webView |
|
490 { |
|
491 return [_private->dataSource _webView]; |
|
492 } |
|
493 |
|
494 - (WebFrameView *)_frameView |
|
495 { |
|
496 return [[_private->dataSource webFrame] frameView]; |
|
497 } |
|
498 |
|
499 - (DOMDocumentFragment *)_documentFragmentWithPaths:(NSArray *)paths |
|
500 { |
|
501 DOMDocumentFragment *fragment; |
|
502 NSEnumerator *enumerator = [paths objectEnumerator]; |
|
503 NSMutableArray *domNodes = [[NSMutableArray alloc] init]; |
|
504 NSString *path; |
|
505 |
|
506 while ((path = [enumerator nextObject]) != nil) { |
|
507 // Non-image file types; _web_userVisibleString is appropriate here because this will |
|
508 // be pasted as visible text. |
|
509 NSString *url = [[[NSURL fileURLWithPath:path] _webkit_canonicalize] _web_userVisibleString]; |
|
510 [domNodes addObject:[[[self _frame] DOMDocument] createTextNode: url]]; |
|
511 } |
|
512 |
|
513 fragment = [[self _bridge] documentFragmentWithNodesAsParagraphs:domNodes]; |
|
514 |
|
515 [domNodes release]; |
|
516 |
|
517 return [fragment firstChild] != nil ? fragment : nil; |
|
518 } |
|
519 |
|
520 + (NSArray *)_excludedElementsForAttributedStringConversion |
|
521 { |
|
522 static NSArray *elements = nil; |
|
523 if (elements == nil) { |
|
524 elements = [[NSArray alloc] initWithObjects: |
|
525 // Omit style since we want style to be inline so the fragment can be easily inserted. |
|
526 @"style", |
|
527 // Omit xml so the result is not XHTML. |
|
528 @"xml", |
|
529 // Omit tags that will get stripped when converted to a fragment anyway. |
|
530 @"doctype", @"html", @"head", @"body", |
|
531 // Omit deprecated tags. |
|
532 @"applet", @"basefont", @"center", @"dir", @"font", @"isindex", @"menu", @"s", @"strike", @"u", |
|
533 // Omit object so no file attachments are part of the fragment. |
|
534 @"object", nil]; |
|
535 CFRetain(elements); |
|
536 } |
|
537 return elements; |
|
538 } |
|
539 |
|
540 static NSURL* uniqueURLWithRelativePart(NSString *relativePart) |
|
541 { |
|
542 CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault); |
|
543 NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef); |
|
544 CFRelease(UUIDRef); |
|
545 NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@/%@", WebDataProtocolScheme, UUIDString, relativePart]]; |
|
546 CFRelease(UUIDString); |
|
547 |
|
548 return URL; |
|
549 } |
|
550 |
|
551 - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard |
|
552 inContext:(DOMRange *)context |
|
553 allowPlainText:(BOOL)allowPlainText |
|
554 chosePlainText:(BOOL *)chosePlainText |
|
555 { |
|
556 NSArray *types = [pasteboard types]; |
|
557 *chosePlainText = NO; |
|
558 DOMDocumentFragment *fragment = nil; |
|
559 |
|
560 if ([types containsObject:WebArchivePboardType] && |
|
561 (fragment = [self _documentFragmentFromPasteboard:pasteboard |
|
562 forType:WebArchivePboardType |
|
563 inContext:context |
|
564 subresources:0])) |
|
565 return fragment; |
|
566 |
|
567 if ([types containsObject:NSFilenamesPboardType] && |
|
568 (fragment = [self _documentFragmentFromPasteboard:pasteboard |
|
569 forType:NSFilenamesPboardType |
|
570 inContext:context |
|
571 subresources:0])) |
|
572 return fragment; |
|
573 |
|
574 if ([types containsObject:NSHTMLPboardType] && |
|
575 (fragment = [self _documentFragmentFromPasteboard:pasteboard |
|
576 forType:NSHTMLPboardType |
|
577 inContext:context |
|
578 subresources:0])) |
|
579 return fragment; |
|
580 |
|
581 if ([types containsObject:NSRTFPboardType] && |
|
582 (fragment = [self _documentFragmentFromPasteboard:pasteboard |
|
583 forType:NSRTFPboardType |
|
584 inContext:context |
|
585 subresources:0])) |
|
586 return fragment; |
|
587 |
|
588 if ([types containsObject:NSRTFDPboardType] && |
|
589 (fragment = [self _documentFragmentFromPasteboard:pasteboard |
|
590 forType:NSRTFDPboardType |
|
591 inContext:context |
|
592 subresources:0])) |
|
593 return fragment; |
|
594 |
|
595 if ([types containsObject:NSTIFFPboardType] && |
|
596 (fragment = [self _documentFragmentFromPasteboard:pasteboard |
|
597 forType:NSTIFFPboardType |
|
598 inContext:context |
|
599 subresources:0])) |
|
600 return fragment; |
|
601 |
|
602 if ([types containsObject:NSPICTPboardType] && |
|
603 (fragment = [self _documentFragmentFromPasteboard:pasteboard |
|
604 forType:NSPICTPboardType |
|
605 inContext:context |
|
606 subresources:0])) |
|
607 return fragment; |
|
608 |
|
609 if ([types containsObject:NSURLPboardType] && |
|
610 (fragment = [self _documentFragmentFromPasteboard:pasteboard |
|
611 forType:NSURLPboardType |
|
612 inContext:context |
|
613 subresources:0])) |
|
614 return fragment; |
|
615 |
|
616 if (allowPlainText && [types containsObject:NSStringPboardType] && |
|
617 (fragment = [self _documentFragmentFromPasteboard:pasteboard |
|
618 forType:NSStringPboardType |
|
619 inContext:context |
|
620 subresources:0])) { |
|
621 *chosePlainText = YES; |
|
622 return fragment; |
|
623 } |
|
624 |
|
625 return nil; |
|
626 } |
|
627 |
|
628 - (NSString *)_plainTextFromPasteboard:(NSPasteboard *)pasteboard |
|
629 { |
|
630 NSArray *types = [pasteboard types]; |
|
631 |
|
632 if ([types containsObject:NSStringPboardType]) |
|
633 return [pasteboard stringForType:NSStringPboardType]; |
|
634 |
|
635 NSAttributedString *attributedString = nil; |
|
636 NSString *string; |
|
637 |
|
638 if ([types containsObject:NSRTFDPboardType]) |
|
639 attributedString = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL]; |
|
640 if (attributedString == nil && [types containsObject:NSRTFPboardType]) |
|
641 attributedString = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL]; |
|
642 if (attributedString != nil) { |
|
643 string = [[attributedString string] copy]; |
|
644 [attributedString release]; |
|
645 return [string autorelease]; |
|
646 } |
|
647 |
|
648 if ([types containsObject:NSFilenamesPboardType]) { |
|
649 string = [[pasteboard propertyListForType:NSFilenamesPboardType] componentsJoinedByString:@"\n"]; |
|
650 if (string != nil) |
|
651 return string; |
|
652 } |
|
653 |
|
654 NSURL *URL; |
|
655 |
|
656 if ((URL = [NSURL URLFromPasteboard:pasteboard])) { |
|
657 string = [URL _web_userVisibleString]; |
|
658 if ([string length] > 0) |
|
659 return string; |
|
660 } |
|
661 |
|
662 return nil; |
|
663 } |
|
664 |
|
665 - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText |
|
666 { |
|
667 DOMRange *range = [self _selectedRange]; |
|
668 BOOL chosePlainText; |
|
669 DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard |
|
670 inContext:range allowPlainText:allowPlainText chosePlainText:&chosePlainText]; |
|
671 WebFrameBridge *bridge = [self _bridge]; |
|
672 if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[self _selectedRange] givenAction:WebViewInsertActionPasted]) { |
|
673 [bridge replaceSelectionWithFragment:fragment selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard] matchStyle:chosePlainText]; |
|
674 } |
|
675 } |
|
676 |
|
677 - (void)_pasteAsPlainTextWithPasteboard:(NSPasteboard *)pasteboard |
|
678 { |
|
679 NSString *text = [self _plainTextFromPasteboard:pasteboard]; |
|
680 if ([self _shouldReplaceSelectionWithText:text givenAction:WebViewInsertActionPasted]) |
|
681 [[self _bridge] replaceSelectionWithText:text selectReplacement:NO smartReplace:[self _canSmartReplaceWithPasteboard:pasteboard]]; |
|
682 } |
|
683 |
|
684 - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action |
|
685 { |
|
686 WebView *webView = [self _webView]; |
|
687 DOMNode *child = [fragment firstChild]; |
|
688 if ([fragment lastChild] == child && [child isKindOfClass:[DOMCharacterData class]]) |
|
689 return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:[(DOMCharacterData *)child data] replacingDOMRange:range givenAction:action]; |
|
690 return [[webView _editingDelegateForwarder] webView:webView shouldInsertNode:fragment replacingDOMRange:range givenAction:action]; |
|
691 } |
|
692 |
|
693 - (BOOL)_shouldInsertText:(NSString *)text replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action |
|
694 { |
|
695 WebView *webView = [self _webView]; |
|
696 return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:range givenAction:action]; |
|
697 } |
|
698 |
|
699 - (BOOL)_shouldReplaceSelectionWithText:(NSString *)text givenAction:(WebViewInsertAction)action |
|
700 { |
|
701 return [self _shouldInsertText:text replacingDOMRange:[self _selectedRange] givenAction:action]; |
|
702 } |
|
703 |
|
704 // Calculate the vertical size of the view that fits on a single page |
|
705 - (float)_calculatePrintHeight |
|
706 { |
|
707 // Obtain the print info object for the current operation |
|
708 NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo]; |
|
709 |
|
710 // Calculate the page height in points |
|
711 NSSize paperSize = [pi paperSize]; |
|
712 return paperSize.height - [pi topMargin] - [pi bottomMargin]; |
|
713 } |
|
714 |
|
715 - (void)_updateTextSizeMultiplier |
|
716 { |
|
717 [[self _bridge] setTextSizeMultiplier:[[self _webView] textSizeMultiplier]]; |
|
718 } |
|
719 |
|
720 - (DOMRange *)_selectedRange |
|
721 { |
|
722 Frame* coreFrame = core([self _frame]); |
|
723 return coreFrame ? kit(coreFrame->selectionController()->toRange().get()) : nil; |
|
724 } |
|
725 |
|
726 - (BOOL)_shouldDeleteRange:(DOMRange *)range |
|
727 { |
|
728 Frame* coreFrame = core([self _frame]); |
|
729 return coreFrame && coreFrame->editor()->shouldDeleteRange(core(range)); |
|
730 } |
|
731 |
|
732 - (BOOL)_canSmartReplaceWithPasteboard:(NSPasteboard *)pasteboard |
|
733 { |
|
734 return [[self _webView] smartInsertDeleteEnabled] && [[pasteboard types] containsObject:WebSmartPastePboardType]; |
|
735 } |
|
736 |
|
737 - (NSView *)_hitViewForEvent:(NSEvent *)event |
|
738 { |
|
739 // Usually, we hack AK's hitTest method to catch all events at the topmost WebHTMLView. |
|
740 // Callers of this method, however, want to query the deepest view instead. |
|
741 forceNSViewHitTest = YES; |
|
742 NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]]; |
|
743 forceNSViewHitTest = NO; |
|
744 return hitView; |
|
745 } |
|
746 |
|
747 - (void)_writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard cachedAttributedString:(NSAttributedString *)attributedString |
|
748 { |
|
749 // Put HTML on the pasteboard. |
|
750 if ([types containsObject:WebArchivePboardType]) { |
|
751 WebArchive *archive = [WebArchiver archiveSelectionInFrame:[self _frame]]; |
|
752 [pasteboard setData:[archive data] forType:WebArchivePboardType]; |
|
753 } |
|
754 |
|
755 // Put the attributed string on the pasteboard (RTF/RTFD format). |
|
756 if ([types containsObject:NSRTFDPboardType]) { |
|
757 if (attributedString == nil) { |
|
758 attributedString = [self selectedAttributedString]; |
|
759 } |
|
760 NSData *RTFDData = [attributedString RTFDFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil]; |
|
761 [pasteboard setData:RTFDData forType:NSRTFDPboardType]; |
|
762 } |
|
763 if ([types containsObject:NSRTFPboardType]) { |
|
764 if (attributedString == nil) { |
|
765 attributedString = [self selectedAttributedString]; |
|
766 } |
|
767 if ([attributedString containsAttachments]) { |
|
768 attributedString = [attributedString _web_attributedStringByStrippingAttachmentCharacters]; |
|
769 } |
|
770 NSData *RTFData = [attributedString RTFFromRange:NSMakeRange(0, [attributedString length]) documentAttributes:nil]; |
|
771 [pasteboard setData:RTFData forType:NSRTFPboardType]; |
|
772 } |
|
773 |
|
774 // Put plain string on the pasteboard. |
|
775 if ([types containsObject:NSStringPboardType]) { |
|
776 // Map to a plain old space because this is better for source code, other browsers do it, |
|
777 // and because HTML forces you to do this any time you want two spaces in a row. |
|
778 NSMutableString *s = [[self selectedString] mutableCopy]; |
|
779 const unichar NonBreakingSpaceCharacter = 0xA0; |
|
780 NSString *NonBreakingSpaceString = [NSString stringWithCharacters:&NonBreakingSpaceCharacter length:1]; |
|
781 [s replaceOccurrencesOfString:NonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])]; |
|
782 [pasteboard setString:s forType:NSStringPboardType]; |
|
783 [s release]; |
|
784 } |
|
785 |
|
786 if ([self _canSmartCopyOrDelete] && [types containsObject:WebSmartPastePboardType]) { |
|
787 [pasteboard setData:nil forType:WebSmartPastePboardType]; |
|
788 } |
|
789 } |
|
790 |
|
791 - (void)_setMouseDownEvent:(NSEvent *)event |
|
792 { |
|
793 ASSERT(!event || [event type] == NSLeftMouseDown || [event type] == NSRightMouseDown || [event type] == NSOtherMouseDown); |
|
794 |
|
795 if (event == _private->mouseDownEvent) |
|
796 return; |
|
797 |
|
798 [event retain]; |
|
799 [_private->mouseDownEvent release]; |
|
800 _private->mouseDownEvent = event; |
|
801 |
|
802 [_private->firstResponderTextViewAtMouseDownTime release]; |
|
803 |
|
804 // The only code that checks this ivar only cares about NSTextViews. The code used to be more general, |
|
805 // but it caused reference cycles leading to world leaks (see 4557386). We should be able to eliminate |
|
806 // firstResponderTextViewAtMouseDownTime entirely when all the form controls are native widgets, because |
|
807 // the only caller (in WebCore) will be unnecessary. |
|
808 if (event) { |
|
809 NSResponder *firstResponder = [[self window] firstResponder]; |
|
810 if ([firstResponder isKindOfClass:[NSTextView class]]) |
|
811 _private->firstResponderTextViewAtMouseDownTime = [firstResponder retain]; |
|
812 else |
|
813 _private->firstResponderTextViewAtMouseDownTime = nil; |
|
814 } else |
|
815 _private->firstResponderTextViewAtMouseDownTime = nil; |
|
816 } |
|
817 |
|
818 - (void)_cancelUpdateActiveStateTimer |
|
819 { |
|
820 if (_private->updateActiveStateTimer) { |
|
821 CFRunLoopTimerInvalidate(_private->updateActiveStateTimer); |
|
822 CFRelease(_private->updateActiveStateTimer); |
|
823 _private->updateActiveStateTimer = NULL; |
|
824 } |
|
825 } |
|
826 |
|
827 - (void)_cancelUpdateMouseoverTimer |
|
828 { |
|
829 if (_private->updateMouseoverTimer) { |
|
830 CFRunLoopTimerInvalidate(_private->updateMouseoverTimer); |
|
831 CFRelease(_private->updateMouseoverTimer); |
|
832 _private->updateMouseoverTimer = NULL; |
|
833 } |
|
834 } |
|
835 |
|
836 - (WebHTMLView *)_topHTMLView |
|
837 { |
|
838 // FIXME: this can fail if the dataSource is nil, which happens when the WebView is tearing down from the window closing. |
|
839 WebHTMLView *view = (WebHTMLView *)[[[[_private->dataSource _webView] mainFrame] frameView] documentView]; |
|
840 ASSERT(view); |
|
841 ASSERT([view isKindOfClass:[WebHTMLView class]]); |
|
842 return view; |
|
843 } |
|
844 |
|
845 - (BOOL)_isTopHTMLView |
|
846 { |
|
847 // FIXME: this should be a cached boolean that doesn't rely on _topHTMLView since that can fail (see _topHTMLView). |
|
848 return self == [self _topHTMLView]; |
|
849 } |
|
850 |
|
851 - (void)_web_setPrintingModeRecursive |
|
852 { |
|
853 [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; |
|
854 |
|
855 #ifndef NDEBUG |
|
856 _private->enumeratingSubviews = YES; |
|
857 #endif |
|
858 |
|
859 NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init]; |
|
860 |
|
861 [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews]; |
|
862 |
|
863 unsigned count = [descendantWebHTMLViews count]; |
|
864 for (unsigned i = 0; i < count; ++i) |
|
865 [[descendantWebHTMLViews objectAtIndex:i] _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; |
|
866 |
|
867 [descendantWebHTMLViews release]; |
|
868 |
|
869 #ifndef NDEBUG |
|
870 _private->enumeratingSubviews = NO; |
|
871 #endif |
|
872 } |
|
873 |
|
874 - (void)_web_clearPrintingModeRecursive |
|
875 { |
|
876 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; |
|
877 |
|
878 #ifndef NDEBUG |
|
879 _private->enumeratingSubviews = YES; |
|
880 #endif |
|
881 |
|
882 NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init]; |
|
883 |
|
884 [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews]; |
|
885 |
|
886 unsigned count = [descendantWebHTMLViews count]; |
|
887 for (unsigned i = 0; i < count; ++i) |
|
888 [[descendantWebHTMLViews objectAtIndex:i] _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; |
|
889 |
|
890 [descendantWebHTMLViews release]; |
|
891 |
|
892 #ifndef NDEBUG |
|
893 _private->enumeratingSubviews = NO; |
|
894 #endif |
|
895 } |
|
896 |
|
897 - (void)_web_setPrintingModeRecursiveAndAdjustViewSize |
|
898 { |
|
899 [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES]; |
|
900 |
|
901 #ifndef NDEBUG |
|
902 _private->enumeratingSubviews = YES; |
|
903 #endif |
|
904 |
|
905 NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init]; |
|
906 |
|
907 [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews]; |
|
908 |
|
909 unsigned count = [descendantWebHTMLViews count]; |
|
910 for (unsigned i = 0; i < count; ++i) |
|
911 [[descendantWebHTMLViews objectAtIndex:i] _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES]; |
|
912 |
|
913 [descendantWebHTMLViews release]; |
|
914 |
|
915 #ifndef NDEBUG |
|
916 _private->enumeratingSubviews = NO; |
|
917 #endif |
|
918 } |
|
919 |
|
920 @end |
|
921 |
|
922 @implementation WebHTMLView (WebPrivate) |
|
923 |
|
924 + (NSArray *)supportedMIMETypes |
|
925 { |
|
926 return [WebHTMLRepresentation supportedMIMETypes]; |
|
927 } |
|
928 |
|
929 + (NSArray *)supportedImageMIMETypes |
|
930 { |
|
931 return [WebHTMLRepresentation supportedImageMIMETypes]; |
|
932 } |
|
933 |
|
934 + (NSArray *)supportedNonImageMIMETypes |
|
935 { |
|
936 return [WebHTMLRepresentation supportedNonImageMIMETypes]; |
|
937 } |
|
938 |
|
939 + (NSArray *)unsupportedTextMIMETypes |
|
940 { |
|
941 return [NSArray arrayWithObjects: |
|
942 @"text/calendar", // iCal |
|
943 @"text/x-calendar", |
|
944 @"text/x-vcalendar", |
|
945 @"text/vcalendar", |
|
946 @"text/vcard", // vCard |
|
947 @"text/x-vcard", |
|
948 @"text/directory", |
|
949 @"text/ldif", // Netscape Address Book |
|
950 @"text/qif", // Quicken |
|
951 @"text/x-qif", |
|
952 @"text/x-csv", // CSV (for Address Book and Microsoft Outlook) |
|
953 @"text/x-vcf", // vCard type used in Sun affinity app |
|
954 @"text/rtf", // Rich Text Format |
|
955 nil]; |
|
956 } |
|
957 |
|
958 + (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent |
|
959 { |
|
960 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved |
|
961 location:[[flagsChangedEvent window] convertScreenToBase:[NSEvent mouseLocation]] |
|
962 modifierFlags:[flagsChangedEvent modifierFlags] |
|
963 timestamp:[flagsChangedEvent timestamp] |
|
964 windowNumber:[flagsChangedEvent windowNumber] |
|
965 context:[flagsChangedEvent context] |
|
966 eventNumber:0 clickCount:0 pressure:0]; |
|
967 |
|
968 // Pretend it's a mouse move. |
|
969 [[NSNotificationCenter defaultCenter] |
|
970 postNotificationName:WKMouseMovedNotification() object:self |
|
971 userInfo:[NSDictionary dictionaryWithObject:fakeEvent forKey:@"NSEvent"]]; |
|
972 } |
|
973 |
|
974 - (void)_updateMouseoverWithFakeEvent |
|
975 { |
|
976 [self _cancelUpdateMouseoverTimer]; |
|
977 |
|
978 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved |
|
979 location:[[self window] convertScreenToBase:[NSEvent mouseLocation]] |
|
980 modifierFlags:[[NSApp currentEvent] modifierFlags] |
|
981 timestamp:[NSDate timeIntervalSinceReferenceDate] |
|
982 windowNumber:[[self window] windowNumber] |
|
983 context:[[NSApp currentEvent] context] |
|
984 eventNumber:0 clickCount:0 pressure:0]; |
|
985 |
|
986 [self _updateMouseoverWithEvent:fakeEvent]; |
|
987 } |
|
988 |
|
989 static void _updateMouseoverTimerCallback(CFRunLoopTimerRef timer, void *info) |
|
990 { |
|
991 WebHTMLView *view = (WebHTMLView *)info; |
|
992 |
|
993 [view _updateMouseoverWithFakeEvent]; |
|
994 } |
|
995 |
|
996 - (void)_frameOrBoundsChanged |
|
997 { |
|
998 if (!NSEqualSizes(_private->lastLayoutSize, [(NSClipView *)[self superview] documentVisibleRect].size)) { |
|
999 [self setNeedsLayout:YES]; |
|
1000 [self setNeedsDisplay:YES]; |
|
1001 [_private->compController endRevertingChange:NO moveLeft:NO]; |
|
1002 } |
|
1003 |
|
1004 NSPoint origin = [[self superview] bounds].origin; |
|
1005 if (!NSEqualPoints(_private->lastScrollPosition, origin)) { |
|
1006 [[self _bridge] sendScrollEvent]; |
|
1007 [_private->compController endRevertingChange:NO moveLeft:NO]; |
|
1008 |
|
1009 WebView *webView = [self _webView]; |
|
1010 [[webView _UIDelegateForwarder] webView:webView didScrollDocumentInFrameView:[self _frameView]]; |
|
1011 } |
|
1012 _private->lastScrollPosition = origin; |
|
1013 |
|
1014 if (!_private->updateMouseoverTimer) { |
|
1015 CFRunLoopTimerContext context = { 0, self, NULL, NULL, NULL }; |
|
1016 |
|
1017 // Use a 100ms delay so that the synthetic mouse over update doesn't cause cursor thrashing when pages are loading |
|
1018 // and scrolling rapidly back to back. |
|
1019 _private->updateMouseoverTimer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent() + 0.1, 0, 0, 0, |
|
1020 _updateMouseoverTimerCallback, &context); |
|
1021 CFRunLoopAddTimer(CFRunLoopGetCurrent(), _private->updateMouseoverTimer, kCFRunLoopDefaultMode); |
|
1022 } |
|
1023 } |
|
1024 |
|
1025 - (void)_setAsideSubviews |
|
1026 { |
|
1027 ASSERT(!_private->subviewsSetAside); |
|
1028 ASSERT(_private->savedSubviews == nil); |
|
1029 _private->savedSubviews = _subviews; |
|
1030 _subviews = nil; |
|
1031 _private->subviewsSetAside = YES; |
|
1032 } |
|
1033 |
|
1034 - (void)_restoreSubviews |
|
1035 { |
|
1036 ASSERT(_private->subviewsSetAside); |
|
1037 ASSERT(_subviews == nil); |
|
1038 _subviews = _private->savedSubviews; |
|
1039 _private->savedSubviews = nil; |
|
1040 _private->subviewsSetAside = NO; |
|
1041 } |
|
1042 |
|
1043 #ifndef NDEBUG |
|
1044 |
|
1045 - (void)didAddSubview:(NSView *)subview |
|
1046 { |
|
1047 if (_private->enumeratingSubviews) |
|
1048 LOG(View, "A view of class %s was added during subview enumeration for layout or printing mode change. This view might paint without first receiving layout.", object_getClassName([subview class])); |
|
1049 } |
|
1050 |
|
1051 - (void)willRemoveSubview:(NSView *)subview |
|
1052 { |
|
1053 if (_private->enumeratingSubviews) |
|
1054 LOG(View, "A view of class %s was removed during subview enumeration for layout or printing mode change. We will still do layout or the printing mode change even though this view is no longer in the view hierarchy.", object_getClassName([subview class])); |
|
1055 } |
|
1056 |
|
1057 #endif |
|
1058 |
|
1059 #ifdef BUILDING_ON_TIGER |
|
1060 |
|
1061 // This is called when we are about to draw, but before our dirty rect is propagated to our ancestors. |
|
1062 // That's the perfect time to do a layout, except that ideally we'd want to be sure that we're dirty |
|
1063 // before doing it. As a compromise, when we're opaque we do the layout only when actually asked to |
|
1064 // draw, but when we're transparent we do the layout at this stage so views behind us know that they |
|
1065 // need to be redrawn (in case the layout causes some things to get dirtied). |
|
1066 - (void)_propagateDirtyRectsToOpaqueAncestors |
|
1067 { |
|
1068 if (![[self _webView] drawsBackground]) |
|
1069 [self _web_layoutIfNeededRecursive]; |
|
1070 [super _propagateDirtyRectsToOpaqueAncestors]; |
|
1071 } |
|
1072 |
|
1073 #else |
|
1074 |
|
1075 - (void)viewWillDraw |
|
1076 { |
|
1077 // On window close we will be called when the datasource is nil, then hit an assert in _topHTMLView |
|
1078 // So check if the dataSource is nil before calling [self _isTopHTMLView], this can be removed |
|
1079 // once the FIXME in _isTopHTMLView is fixed. |
|
1080 if (_private->dataSource && [self _isTopHTMLView]) |
|
1081 [self _web_layoutIfNeededRecursive]; |
|
1082 [super viewWillDraw]; |
|
1083 } |
|
1084 |
|
1085 #endif |
|
1086 |
|
1087 // Don't let AppKit even draw subviews. We take care of that. |
|
1088 - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView |
|
1089 { |
|
1090 // This helps when we print as part of a larger print process. |
|
1091 // If the WebHTMLView itself is what we're printing, then we will never have to do this. |
|
1092 BOOL wasInPrintingMode = _private->printing; |
|
1093 BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen]; |
|
1094 if (wasInPrintingMode != isPrinting) { |
|
1095 if (isPrinting) |
|
1096 [self _web_setPrintingModeRecursive]; |
|
1097 else |
|
1098 [self _web_clearPrintingModeRecursive]; |
|
1099 } |
|
1100 |
|
1101 #ifdef BUILDING_ON_TIGER |
|
1102 |
|
1103 // Because Tiger does not have viewWillDraw we need to do layout here. |
|
1104 [self _web_layoutIfNeededRecursive]; |
|
1105 [_subviews makeObjectsPerformSelector:@selector(_propagateDirtyRectsToOpaqueAncestors)]; |
|
1106 |
|
1107 #endif |
|
1108 |
|
1109 [self _setAsideSubviews]; |
|
1110 [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect rectIsVisibleRectForView:visibleView topView:topView]; |
|
1111 [self _restoreSubviews]; |
|
1112 |
|
1113 if (wasInPrintingMode != isPrinting) { |
|
1114 if (wasInPrintingMode) |
|
1115 [self _web_setPrintingModeRecursive]; |
|
1116 else |
|
1117 [self _web_clearPrintingModeRecursive]; |
|
1118 } |
|
1119 } |
|
1120 |
|
1121 // Don't let AppKit even draw subviews. We take care of that. |
|
1122 - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect |
|
1123 { |
|
1124 BOOL needToSetAsideSubviews = !_private->subviewsSetAside; |
|
1125 |
|
1126 BOOL wasInPrintingMode = _private->printing; |
|
1127 BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen]; |
|
1128 |
|
1129 if (needToSetAsideSubviews) { |
|
1130 // This helps when we print as part of a larger print process. |
|
1131 // If the WebHTMLView itself is what we're printing, then we will never have to do this. |
|
1132 if (wasInPrintingMode != isPrinting) { |
|
1133 if (isPrinting) |
|
1134 [self _web_setPrintingModeRecursive]; |
|
1135 else |
|
1136 [self _web_clearPrintingModeRecursive]; |
|
1137 } |
|
1138 |
|
1139 #ifdef BUILDING_ON_TIGER |
|
1140 |
|
1141 // Because Tiger does not have viewWillDraw we need to do layout here. |
|
1142 NSRect boundsBeforeLayout = [self bounds]; |
|
1143 if (!NSIsEmptyRect(visRect)) |
|
1144 [self _web_layoutIfNeededRecursive]; |
|
1145 |
|
1146 // If layout changes the view's bounds, then we need to recompute the visRect. |
|
1147 // That's because the visRect passed to us was based on the bounds at the time |
|
1148 // we were called. This method is only displayed to draw "all", so it's safe |
|
1149 // to just call visibleRect to compute the entire rectangle. |
|
1150 if (!NSEqualRects(boundsBeforeLayout, [self bounds])) |
|
1151 visRect = [self visibleRect]; |
|
1152 |
|
1153 #endif |
|
1154 |
|
1155 [self _setAsideSubviews]; |
|
1156 } |
|
1157 |
|
1158 [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect]; |
|
1159 |
|
1160 if (needToSetAsideSubviews) { |
|
1161 if (wasInPrintingMode != isPrinting) { |
|
1162 if (wasInPrintingMode) |
|
1163 [self _web_setPrintingModeRecursive]; |
|
1164 else |
|
1165 [self _web_clearPrintingModeRecursive]; |
|
1166 } |
|
1167 |
|
1168 [self _restoreSubviews]; |
|
1169 } |
|
1170 } |
|
1171 |
|
1172 - (BOOL)_insideAnotherHTMLView |
|
1173 { |
|
1174 return self != [self _topHTMLView]; |
|
1175 } |
|
1176 |
|
1177 - (void)scrollPoint:(NSPoint)point |
|
1178 { |
|
1179 // Since we can't subclass NSTextView to do what we want, we have to second guess it here. |
|
1180 // If we get called during the handling of a key down event, we assume the call came from |
|
1181 // NSTextView, and ignore it and use our own code to decide how to page up and page down |
|
1182 // We are smarter about how far to scroll, and we have "superview scrolling" logic. |
|
1183 NSEvent *event = [[self window] currentEvent]; |
|
1184 if ([event type] == NSKeyDown) { |
|
1185 const unichar pageUp = NSPageUpFunctionKey; |
|
1186 if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageUp length:1]].length == 1) { |
|
1187 [self tryToPerform:@selector(scrollPageUp:) with:nil]; |
|
1188 return; |
|
1189 } |
|
1190 const unichar pageDown = NSPageDownFunctionKey; |
|
1191 if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageDown length:1]].length == 1) { |
|
1192 [self tryToPerform:@selector(scrollPageDown:) with:nil]; |
|
1193 return; |
|
1194 } |
|
1195 } |
|
1196 |
|
1197 [super scrollPoint:point]; |
|
1198 } |
|
1199 |
|
1200 - (NSView *)hitTest:(NSPoint)point |
|
1201 { |
|
1202 // WebHTMLView objects handle all events for objects inside them. |
|
1203 // To get those events, we prevent hit testing from AppKit. |
|
1204 |
|
1205 // But there are three exceptions to this: |
|
1206 // 1) For right mouse clicks and control clicks we don't yet have an implementation |
|
1207 // that works for nested views, so we let the hit testing go through the |
|
1208 // standard NSView code path (needs to be fixed, see bug 4361618). |
|
1209 // 2) Java depends on doing a hit test inside it's mouse moved handling, |
|
1210 // so we let the hit testing go through the standard NSView code path |
|
1211 // when the current event is a mouse move (except when we are calling |
|
1212 // from _updateMouseoverWithEvent, so we have to use a global, |
|
1213 // forceWebHTMLViewHitTest, for that) |
|
1214 // 3) The acceptsFirstMouse: and shouldDelayWindowOrderingForEvent: methods |
|
1215 // both need to figure out which view to check with inside the WebHTMLView. |
|
1216 // They use a global to change the behavior of hitTest: so they can get the |
|
1217 // right view. The global is forceNSViewHitTest and the method they use to |
|
1218 // do the hit testing is _hitViewForEvent:. (But this does not work correctly |
|
1219 // when there is HTML overlapping the view, see bug 4361626) |
|
1220 // 4) NSAccessibilityHitTest relies on this for checking the cursor position. |
|
1221 // Our check for that is whether the event is NSFlagsChanged. This works |
|
1222 // for VoiceOver's cntl-opt-f5 command (move focus to item under cursor) |
|
1223 // and Dictionary's cmd-cntl-D (open dictionary popup for item under cursor). |
|
1224 // This is of course a hack. |
|
1225 |
|
1226 BOOL captureHitsOnSubviews; |
|
1227 if (forceNSViewHitTest) |
|
1228 captureHitsOnSubviews = NO; |
|
1229 else if (forceWebHTMLViewHitTest) |
|
1230 captureHitsOnSubviews = YES; |
|
1231 else { |
|
1232 NSEvent *event = [[self window] currentEvent]; |
|
1233 captureHitsOnSubviews = !([event type] == NSMouseMoved |
|
1234 || [event type] == NSRightMouseDown |
|
1235 || ([event type] == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask) != 0) |
|
1236 || [event type] == NSFlagsChanged); |
|
1237 } |
|
1238 |
|
1239 if (!captureHitsOnSubviews) |
|
1240 return [super hitTest:point]; |
|
1241 if ([[self superview] mouse:point inRect:[self frame]]) |
|
1242 return self; |
|
1243 return nil; |
|
1244 } |
|
1245 |
|
1246 - (void)_clearLastHitViewIfSelf |
|
1247 { |
|
1248 if (lastHitView == self) |
|
1249 lastHitView = nil; |
|
1250 } |
|
1251 |
|
1252 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside |
|
1253 { |
|
1254 ASSERT(_private->trackingRectOwner == nil); |
|
1255 _private->trackingRectOwner = owner; |
|
1256 _private->trackingRectUserData = data; |
|
1257 return TRACKING_RECT_TAG; |
|
1258 } |
|
1259 |
|
1260 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag |
|
1261 { |
|
1262 ASSERT(tag == 0 || tag == TRACKING_RECT_TAG); |
|
1263 ASSERT(_private->trackingRectOwner == nil); |
|
1264 _private->trackingRectOwner = owner; |
|
1265 _private->trackingRectUserData = data; |
|
1266 return TRACKING_RECT_TAG; |
|
1267 } |
|
1268 |
|
1269 - (void)_addTrackingRects:(NSRect *)rects owner:(id)owner userDataList:(void **)userDataList assumeInsideList:(BOOL *)assumeInsideList trackingNums:(NSTrackingRectTag *)trackingNums count:(int)count |
|
1270 { |
|
1271 ASSERT(count == 1); |
|
1272 ASSERT(trackingNums[0] == 0 || trackingNums[0] == TRACKING_RECT_TAG); |
|
1273 ASSERT(_private->trackingRectOwner == nil); |
|
1274 _private->trackingRectOwner = owner; |
|
1275 _private->trackingRectUserData = userDataList[0]; |
|
1276 trackingNums[0] = TRACKING_RECT_TAG; |
|
1277 } |
|
1278 |
|
1279 - (void)removeTrackingRect:(NSTrackingRectTag)tag |
|
1280 { |
|
1281 if (tag == 0) |
|
1282 return; |
|
1283 |
|
1284 if (_private && (tag == TRACKING_RECT_TAG)) { |
|
1285 _private->trackingRectOwner = nil; |
|
1286 return; |
|
1287 } |
|
1288 |
|
1289 if (_private && (tag == _private->lastToolTipTag)) { |
|
1290 [super removeTrackingRect:tag]; |
|
1291 _private->lastToolTipTag = 0; |
|
1292 return; |
|
1293 } |
|
1294 |
|
1295 // If any other tracking rect is being removed, we don't know how it was created |
|
1296 // and it's possible there's a leak involved (see 3500217) |
|
1297 ASSERT_NOT_REACHED(); |
|
1298 } |
|
1299 |
|
1300 - (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count |
|
1301 { |
|
1302 int i; |
|
1303 for (i = 0; i < count; ++i) { |
|
1304 int tag = tags[i]; |
|
1305 if (tag == 0) |
|
1306 continue; |
|
1307 ASSERT(tag == TRACKING_RECT_TAG); |
|
1308 if (_private != nil) { |
|
1309 _private->trackingRectOwner = nil; |
|
1310 } |
|
1311 } |
|
1312 } |
|
1313 |
|
1314 - (void)_sendToolTipMouseExited |
|
1315 { |
|
1316 // Nothing matters except window, trackingNumber, and userData. |
|
1317 NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited |
|
1318 location:NSMakePoint(0, 0) |
|
1319 modifierFlags:0 |
|
1320 timestamp:0 |
|
1321 windowNumber:[[self window] windowNumber] |
|
1322 context:NULL |
|
1323 eventNumber:0 |
|
1324 trackingNumber:TRACKING_RECT_TAG |
|
1325 userData:_private->trackingRectUserData]; |
|
1326 [_private->trackingRectOwner mouseExited:fakeEvent]; |
|
1327 } |
|
1328 |
|
1329 - (void)_sendToolTipMouseEntered |
|
1330 { |
|
1331 // Nothing matters except window, trackingNumber, and userData. |
|
1332 NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered |
|
1333 location:NSMakePoint(0, 0) |
|
1334 modifierFlags:0 |
|
1335 timestamp:0 |
|
1336 windowNumber:[[self window] windowNumber] |
|
1337 context:NULL |
|
1338 eventNumber:0 |
|
1339 trackingNumber:TRACKING_RECT_TAG |
|
1340 userData:_private->trackingRectUserData]; |
|
1341 [_private->trackingRectOwner mouseEntered:fakeEvent]; |
|
1342 } |
|
1343 |
|
1344 - (void)_setToolTip:(NSString *)string |
|
1345 { |
|
1346 NSString *toolTip = [string length] == 0 ? nil : string; |
|
1347 NSString *oldToolTip = _private->toolTip; |
|
1348 if ((toolTip == nil || oldToolTip == nil) ? toolTip == oldToolTip : [toolTip isEqualToString:oldToolTip]) { |
|
1349 return; |
|
1350 } |
|
1351 if (oldToolTip) { |
|
1352 [self _sendToolTipMouseExited]; |
|
1353 [oldToolTip release]; |
|
1354 } |
|
1355 _private->toolTip = [toolTip copy]; |
|
1356 if (toolTip) { |
|
1357 // See radar 3500217 for why we remove all tooltips rather than just the single one we created. |
|
1358 [self removeAllToolTips]; |
|
1359 NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000); |
|
1360 _private->lastToolTipTag = [self addToolTipRect:wideOpenRect owner:self userData:NULL]; |
|
1361 [self _sendToolTipMouseEntered]; |
|
1362 } |
|
1363 } |
|
1364 |
|
1365 - (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data |
|
1366 { |
|
1367 return [[_private->toolTip copy] autorelease]; |
|
1368 } |
|
1369 |
|
1370 - (void)_updateMouseoverWithEvent:(NSEvent *)event |
|
1371 { |
|
1372 if (_private->closed) |
|
1373 return; |
|
1374 |
|
1375 NSView *contentView = [[event window] contentView]; |
|
1376 NSPoint locationForHitTest = [[contentView superview] convertPoint:[event locationInWindow] fromView:nil]; |
|
1377 |
|
1378 forceWebHTMLViewHitTest = YES; |
|
1379 NSView *hitView = [contentView hitTest:locationForHitTest]; |
|
1380 forceWebHTMLViewHitTest = NO; |
|
1381 |
|
1382 WebHTMLView *view = nil; |
|
1383 if ([hitView isKindOfClass:[WebHTMLView class]] && ![[(WebHTMLView *)hitView _webView] isHoverFeedbackSuspended]) |
|
1384 view = (WebHTMLView *)hitView; |
|
1385 |
|
1386 if (view) |
|
1387 [view retain]; |
|
1388 |
|
1389 if (lastHitView != view && lastHitView && [lastHitView _frame]) { |
|
1390 // If we are moving out of a view (or frame), let's pretend the mouse moved |
|
1391 // all the way out of that view. But we have to account for scrolling, because |
|
1392 // khtml doesn't understand our clipping. |
|
1393 NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect]; |
|
1394 float yScroll = visibleRect.origin.y; |
|
1395 float xScroll = visibleRect.origin.x; |
|
1396 |
|
1397 event = [NSEvent mouseEventWithType:NSMouseMoved |
|
1398 location:NSMakePoint(-1 - xScroll, -1 - yScroll ) |
|
1399 modifierFlags:[[NSApp currentEvent] modifierFlags] |
|
1400 timestamp:[NSDate timeIntervalSinceReferenceDate] |
|
1401 windowNumber:[[view window] windowNumber] |
|
1402 context:[[NSApp currentEvent] context] |
|
1403 eventNumber:0 clickCount:0 pressure:0]; |
|
1404 if (Frame* lastHitCoreFrame = core([lastHitView _frame])) |
|
1405 lastHitCoreFrame->eventHandler()->mouseMoved(event); |
|
1406 } |
|
1407 |
|
1408 lastHitView = view; |
|
1409 |
|
1410 if (view) { |
|
1411 if (Frame* coreFrame = core([view _frame])) |
|
1412 coreFrame->eventHandler()->mouseMoved(event); |
|
1413 |
|
1414 [view release]; |
|
1415 } |
|
1416 } |
|
1417 |
|
1418 // keep in sync with WebPasteboardHelper::insertablePasteboardTypes |
|
1419 + (NSArray *)_insertablePasteboardTypes |
|
1420 { |
|
1421 static NSArray *types = nil; |
|
1422 if (!types) { |
|
1423 types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType, |
|
1424 NSFilenamesPboardType, NSTIFFPboardType, NSPICTPboardType, NSURLPboardType, |
|
1425 NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, NSColorPboardType, nil]; |
|
1426 CFRetain(types); |
|
1427 } |
|
1428 return types; |
|
1429 } |
|
1430 |
|
1431 + (NSArray *)_selectionPasteboardTypes |
|
1432 { |
|
1433 // FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423). |
|
1434 return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil]; |
|
1435 } |
|
1436 |
|
1437 - (NSImage *)_dragImageForURL:(NSString*)urlString withLabel:(NSString*)label |
|
1438 { |
|
1439 BOOL drawURLString = YES; |
|
1440 BOOL clipURLString = NO, clipLabelString = NO; |
|
1441 |
|
1442 if (!label) { |
|
1443 drawURLString = NO; |
|
1444 label = urlString; |
|
1445 } |
|
1446 |
|
1447 NSFont *labelFont = [[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:DRAG_LINK_LABEL_FONT_SIZE] |
|
1448 toHaveTrait:NSBoldFontMask]; |
|
1449 NSFont *urlFont = [NSFont systemFontOfSize: DRAG_LINK_URL_FONT_SIZE]; |
|
1450 NSSize labelSize; |
|
1451 labelSize.width = [label _web_widthWithFont: labelFont]; |
|
1452 labelSize.height = [labelFont ascender] - [labelFont descender]; |
|
1453 if (labelSize.width > MAX_DRAG_LABEL_WIDTH){ |
|
1454 labelSize.width = MAX_DRAG_LABEL_WIDTH; |
|
1455 clipLabelString = YES; |
|
1456 } |
|
1457 |
|
1458 NSSize imageSize, urlStringSize; |
|
1459 imageSize.width = labelSize.width + DRAG_LABEL_BORDER_X * 2.0f; |
|
1460 imageSize.height = labelSize.height + DRAG_LABEL_BORDER_Y * 2.0f; |
|
1461 if (drawURLString) { |
|
1462 urlStringSize.width = [urlString _web_widthWithFont: urlFont]; |
|
1463 urlStringSize.height = [urlFont ascender] - [urlFont descender]; |
|
1464 imageSize.height += urlStringSize.height; |
|
1465 if (urlStringSize.width > MAX_DRAG_LABEL_WIDTH) { |
|
1466 imageSize.width = MAX(MAX_DRAG_LABEL_WIDTH + DRAG_LABEL_BORDER_X * 2.0f, MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP); |
|
1467 clipURLString = YES; |
|
1468 } else { |
|
1469 imageSize.width = MAX(labelSize.width + DRAG_LABEL_BORDER_X * 2.0f, urlStringSize.width + DRAG_LABEL_BORDER_X * 2.0f); |
|
1470 } |
|
1471 } |
|
1472 NSImage *dragImage = [[[NSImage alloc] initWithSize: imageSize] autorelease]; |
|
1473 [dragImage lockFocus]; |
|
1474 |
|
1475 [[NSColor colorWithCalibratedRed: 0.7f green: 0.7f blue: 0.7f alpha: 0.8f] set]; |
|
1476 |
|
1477 // Drag a rectangle with rounded corners/ |
|
1478 NSBezierPath *path = [NSBezierPath bezierPath]; |
|
1479 [path appendBezierPathWithOvalInRect: NSMakeRect(0.0f, 0.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; |
|
1480 [path appendBezierPathWithOvalInRect: NSMakeRect(0, imageSize.height - DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; |
|
1481 [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2.0f, imageSize.height - DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; |
|
1482 [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2.0f, 0.0f, DRAG_LABEL_RADIUS * 2.0f, DRAG_LABEL_RADIUS * 2.0f)]; |
|
1483 |
|
1484 [path appendBezierPathWithRect: NSMakeRect(DRAG_LABEL_RADIUS, 0.0f, imageSize.width - DRAG_LABEL_RADIUS * 2.0f, imageSize.height)]; |
|
1485 [path appendBezierPathWithRect: NSMakeRect(0.0f, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 10.0f, imageSize.height - 2.0f * DRAG_LABEL_RADIUS)]; |
|
1486 [path appendBezierPathWithRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS - 20.0f, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 20.0f, imageSize.height - 2.0f * DRAG_LABEL_RADIUS)]; |
|
1487 [path fill]; |
|
1488 |
|
1489 NSColor *topColor = [NSColor colorWithCalibratedWhite:0.0f alpha:0.75f]; |
|
1490 NSColor *bottomColor = [NSColor colorWithCalibratedWhite:1.0f alpha:0.5f]; |
|
1491 if (drawURLString) { |
|
1492 if (clipURLString) |
|
1493 urlString = [WebStringTruncator centerTruncateString: urlString toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2.0f) withFont:urlFont]; |
|
1494 |
|
1495 [urlString _web_drawDoubledAtPoint:NSMakePoint(DRAG_LABEL_BORDER_X, DRAG_LABEL_BORDER_Y - [urlFont descender]) |
|
1496 withTopColor:topColor bottomColor:bottomColor font:urlFont]; |
|
1497 } |
|
1498 |
|
1499 if (clipLabelString) |
|
1500 label = [WebStringTruncator rightTruncateString: label toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2.0f) withFont:labelFont]; |
|
1501 [label _web_drawDoubledAtPoint:NSMakePoint (DRAG_LABEL_BORDER_X, imageSize.height - DRAG_LABEL_BORDER_Y_OFFSET - [labelFont pointSize]) |
|
1502 withTopColor:topColor bottomColor:bottomColor font:labelFont]; |
|
1503 |
|
1504 [dragImage unlockFocus]; |
|
1505 |
|
1506 return dragImage; |
|
1507 } |
|
1508 |
|
1509 - (NSImage *)_dragImageForLinkElement:(NSDictionary *)element |
|
1510 { |
|
1511 NSURL *linkURL = [element objectForKey: WebElementLinkURLKey]; |
|
1512 |
|
1513 NSString *label = [element objectForKey: WebElementLinkLabelKey]; |
|
1514 NSString *urlString = [linkURL _web_userVisibleString]; |
|
1515 return [self _dragImageForURL:urlString withLabel:label]; |
|
1516 } |
|
1517 |
|
1518 - (void)pasteboardChangedOwner:(NSPasteboard *)pasteboard |
|
1519 { |
|
1520 [self setPromisedDragTIFFDataSource:0]; |
|
1521 } |
|
1522 |
|
1523 - (void)pasteboard:(NSPasteboard *)pasteboard provideDataForType:(NSString *)type |
|
1524 { |
|
1525 if ([type isEqual:NSRTFDPboardType] && [[pasteboard types] containsObject:WebArchivePboardType]) { |
|
1526 WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]]; |
|
1527 [pasteboard _web_writePromisedRTFDFromArchive:archive containsImage:[[pasteboard types] containsObject:NSTIFFPboardType]]; |
|
1528 [archive release]; |
|
1529 } else if ([type isEqual:NSTIFFPboardType] && [self promisedDragTIFFDataSource]) { |
|
1530 if (Image* image = [self promisedDragTIFFDataSource]->image()) |
|
1531 [pasteboard setData:(NSData *)image->getTIFFRepresentation() forType:NSTIFFPboardType]; |
|
1532 [self setPromisedDragTIFFDataSource:0]; |
|
1533 } |
|
1534 } |
|
1535 |
|
1536 - (void)_handleAutoscrollForMouseDragged:(NSEvent *)event |
|
1537 { |
|
1538 [self autoscroll:event]; |
|
1539 [self _startAutoscrollTimer:event]; |
|
1540 } |
|
1541 |
|
1542 - (WebPluginController *)_pluginController |
|
1543 { |
|
1544 return _private->pluginController; |
|
1545 } |
|
1546 |
|
1547 - (void)_layoutForPrinting |
|
1548 { |
|
1549 // Set printing mode temporarily so we can adjust the size of the view. This will allow |
|
1550 // AppKit's pagination code to use the correct height for the page content. Leaving printing |
|
1551 // mode on indefinitely would interfere with Mail's printing mechanism (at least), so we just |
|
1552 // turn it off again after adjusting the size. |
|
1553 [self _web_setPrintingModeRecursiveAndAdjustViewSize]; |
|
1554 [self _web_clearPrintingModeRecursive]; |
|
1555 } |
|
1556 |
|
1557 - (void)_startAutoscrollTimer: (NSEvent *)triggerEvent |
|
1558 { |
|
1559 if (_private->autoscrollTimer == nil) { |
|
1560 _private->autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval:AUTOSCROLL_INTERVAL |
|
1561 target:self selector:@selector(_autoscroll) userInfo:nil repeats:YES] retain]; |
|
1562 _private->autoscrollTriggerEvent = [triggerEvent retain]; |
|
1563 } |
|
1564 } |
|
1565 |
|
1566 // FIXME: _selectionRect is deprecated in favor of selectionRect, which is in protocol WebDocumentSelection. |
|
1567 // We can't remove this yet because it's still in use by Mail. |
|
1568 - (NSRect)_selectionRect |
|
1569 { |
|
1570 return [self selectionRect]; |
|
1571 } |
|
1572 |
|
1573 - (void)_stopAutoscrollTimer |
|
1574 { |
|
1575 NSTimer *timer = _private->autoscrollTimer; |
|
1576 _private->autoscrollTimer = nil; |
|
1577 [_private->autoscrollTriggerEvent release]; |
|
1578 _private->autoscrollTriggerEvent = nil; |
|
1579 [timer invalidate]; |
|
1580 [timer release]; |
|
1581 } |
|
1582 |
|
1583 - (void)_autoscroll |
|
1584 { |
|
1585 // Guarantee that the autoscroll timer is invalidated, even if we don't receive |
|
1586 // a mouse up event. |
|
1587 BOOL isStillDown = CGEventSourceButtonState(kCGEventSourceStateCombinedSessionState, kCGMouseButtonLeft); |
|
1588 if (!isStillDown){ |
|
1589 [self _stopAutoscrollTimer]; |
|
1590 return; |
|
1591 } |
|
1592 |
|
1593 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged |
|
1594 location:[[self window] convertScreenToBase:[NSEvent mouseLocation]] |
|
1595 modifierFlags:[[NSApp currentEvent] modifierFlags] |
|
1596 timestamp:[NSDate timeIntervalSinceReferenceDate] |
|
1597 windowNumber:[[self window] windowNumber] |
|
1598 context:[[NSApp currentEvent] context] |
|
1599 eventNumber:0 clickCount:0 pressure:0]; |
|
1600 [self mouseDragged:fakeEvent]; |
|
1601 } |
|
1602 |
|
1603 - (BOOL)_canEdit |
|
1604 { |
|
1605 Frame* coreFrame = core([self _frame]); |
|
1606 return coreFrame && coreFrame->editor()->canEdit(); |
|
1607 } |
|
1608 |
|
1609 - (BOOL)_canEditRichly |
|
1610 { |
|
1611 Frame* coreFrame = core([self _frame]); |
|
1612 return coreFrame && coreFrame->editor()->canEditRichly(); |
|
1613 } |
|
1614 |
|
1615 - (BOOL)_canAlterCurrentSelection |
|
1616 { |
|
1617 return [self _hasSelectionOrInsertionPoint] && [self _isEditable]; |
|
1618 } |
|
1619 |
|
1620 - (BOOL)_hasSelection |
|
1621 { |
|
1622 Frame* coreFrame = core([self _frame]); |
|
1623 return coreFrame && coreFrame->selectionController()->isRange(); |
|
1624 } |
|
1625 |
|
1626 - (BOOL)_hasSelectionOrInsertionPoint |
|
1627 { |
|
1628 Frame* coreFrame = core([self _frame]); |
|
1629 return coreFrame && coreFrame->selectionController()->isCaretOrRange(); |
|
1630 } |
|
1631 |
|
1632 - (BOOL)_hasInsertionPoint |
|
1633 { |
|
1634 Frame* coreFrame = core([self _frame]); |
|
1635 return coreFrame && coreFrame->selectionController()->isCaret(); |
|
1636 } |
|
1637 |
|
1638 - (BOOL)_isEditable |
|
1639 { |
|
1640 Frame* coreFrame = core([self _frame]); |
|
1641 return coreFrame && coreFrame->selectionController()->isContentEditable(); |
|
1642 } |
|
1643 |
|
1644 - (BOOL)_transparentBackground |
|
1645 { |
|
1646 return _private->transparentBackground; |
|
1647 } |
|
1648 |
|
1649 - (void)_setTransparentBackground:(BOOL)f |
|
1650 { |
|
1651 _private->transparentBackground = f; |
|
1652 } |
|
1653 |
|
1654 - (NSImage *)_selectionDraggingImage |
|
1655 { |
|
1656 if ([self _hasSelection]) { |
|
1657 NSImage *dragImage = core([self _frame])->selectionImage(); |
|
1658 [dragImage _web_dissolveToFraction:WebDragImageAlpha]; |
|
1659 return dragImage; |
|
1660 } |
|
1661 return nil; |
|
1662 } |
|
1663 |
|
1664 - (NSRect)_selectionDraggingRect |
|
1665 { |
|
1666 // Mail currently calls this method. We can eliminate it when Mail no longer calls it. |
|
1667 return [self selectionRect]; |
|
1668 } |
|
1669 |
|
1670 - (DOMNode *)_insertOrderedList |
|
1671 { |
|
1672 Frame* coreFrame = core([self _frame]); |
|
1673 return coreFrame ? kit(coreFrame->editor()->insertOrderedList().get()) : nil; |
|
1674 } |
|
1675 |
|
1676 - (DOMNode *)_insertUnorderedList |
|
1677 { |
|
1678 Frame* coreFrame = core([self _frame]); |
|
1679 return coreFrame ? kit(coreFrame->editor()->insertUnorderedList().get()) : nil; |
|
1680 } |
|
1681 |
|
1682 - (BOOL)_canIncreaseSelectionListLevel |
|
1683 { |
|
1684 Frame* coreFrame = core([self _frame]); |
|
1685 return coreFrame && coreFrame->editor()->canIncreaseSelectionListLevel(); |
|
1686 } |
|
1687 |
|
1688 - (BOOL)_canDecreaseSelectionListLevel |
|
1689 { |
|
1690 Frame* coreFrame = core([self _frame]); |
|
1691 return coreFrame && coreFrame->editor()->canDecreaseSelectionListLevel(); |
|
1692 } |
|
1693 |
|
1694 - (DOMNode *)_increaseSelectionListLevel |
|
1695 { |
|
1696 Frame* coreFrame = core([self _frame]); |
|
1697 return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevel().get()) : nil; |
|
1698 } |
|
1699 |
|
1700 - (DOMNode *)_increaseSelectionListLevelOrdered |
|
1701 { |
|
1702 Frame* coreFrame = core([self _frame]); |
|
1703 return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevelOrdered().get()) : nil; |
|
1704 } |
|
1705 |
|
1706 - (DOMNode *)_increaseSelectionListLevelUnordered |
|
1707 { |
|
1708 Frame* coreFrame = core([self _frame]); |
|
1709 return coreFrame ? kit(coreFrame->editor()->increaseSelectionListLevelUnordered().get()) : nil; |
|
1710 } |
|
1711 |
|
1712 - (void)_decreaseSelectionListLevel |
|
1713 { |
|
1714 Frame* coreFrame = core([self _frame]); |
|
1715 if (coreFrame) |
|
1716 coreFrame->editor()->decreaseSelectionListLevel(); |
|
1717 } |
|
1718 |
|
1719 - (void)_setHighlighter:(id<WebHTMLHighlighter>)highlighter ofType:(NSString*)type |
|
1720 { |
|
1721 if (!_private->highlighters) |
|
1722 _private->highlighters = [[NSMutableDictionary alloc] init]; |
|
1723 [_private->highlighters setObject:highlighter forKey:type]; |
|
1724 } |
|
1725 |
|
1726 - (void)_removeHighlighterOfType:(NSString*)type |
|
1727 { |
|
1728 [_private->highlighters removeObjectForKey:type]; |
|
1729 } |
|
1730 |
|
1731 - (BOOL)_web_firstResponderCausesFocusDisplay |
|
1732 { |
|
1733 return [[self window] firstResponder] == self || [[self window] firstResponder] == [self _frameView]; |
|
1734 } |
|
1735 |
|
1736 - (void)_updateActiveState |
|
1737 { |
|
1738 [self _cancelUpdateActiveStateTimer]; |
|
1739 |
|
1740 // This method does the job of updating the view based on the view's firstResponder-ness and |
|
1741 // the window key-ness of the window containing this view. This involves four kinds of |
|
1742 // drawing updates right now. |
|
1743 // |
|
1744 // The four display attributes are as follows: |
|
1745 // |
|
1746 // 1. The background color used to draw behind selected content (active | inactive color) |
|
1747 // 2. Caret blinking (blinks | does not blink) |
|
1748 // 3. The drawing of a focus ring around links in web pages. |
|
1749 // |
|
1750 // Also, this is responsible for letting the bridge know if the window has gained or lost focus |
|
1751 // so we can send focus and blur events. |
|
1752 |
|
1753 Frame* frame = core([self _frame]); |
|
1754 if (!frame) |
|
1755 return; |
|
1756 |
|
1757 Page* page = frame->page(); |
|
1758 if (!page) |
|
1759 return; |
|
1760 |
|
1761 NSWindow *window = [self window]; |
|
1762 BOOL windowIsKey = [window isKeyWindow]; |
|
1763 BOOL windowOrSheetIsKey = windowIsKey || [[window attachedSheet] isKeyWindow]; |
|
1764 |
|
1765 BOOL isActive = !_private->resigningFirstResponder && windowIsKey && [self _web_firstResponderCausesFocusDisplay]; |
|
1766 frame->setIsActive(isActive); |
|
1767 |
|
1768 Frame* focusedFrame = page->focusController()->focusedOrMainFrame(); |
|
1769 frame->setWindowHasFocus(frame == focusedFrame && windowOrSheetIsKey); |
|
1770 } |
|
1771 |
|
1772 - (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard |
|
1773 { |
|
1774 ASSERT([self _hasSelection]); |
|
1775 NSArray *types = [self pasteboardTypesForSelection]; |
|
1776 |
|
1777 // Don't write RTFD to the pasteboard when the copied attributed string has no attachments. |
|
1778 NSAttributedString *attributedString = [self selectedAttributedString]; |
|
1779 NSMutableArray *mutableTypes = nil; |
|
1780 if (![attributedString containsAttachments]) { |
|
1781 mutableTypes = [types mutableCopy]; |
|
1782 [mutableTypes removeObject:NSRTFDPboardType]; |
|
1783 types = mutableTypes; |
|
1784 } |
|
1785 |
|
1786 [pasteboard declareTypes:types owner:[self _topHTMLView]]; |
|
1787 [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:attributedString]; |
|
1788 [mutableTypes release]; |
|
1789 } |
|
1790 |
|
1791 - (void)close |
|
1792 { |
|
1793 // Check for a nil _private here incase we were created with initWithCoder. In that case, the WebView is just throwing |
|
1794 // out the archived WebHTMLView and recreating a new one if needed. So close dosen't need to do anything in that case. |
|
1795 if (!_private || _private->closed) |
|
1796 return; |
|
1797 [self _clearLastHitViewIfSelf]; |
|
1798 // FIXME: This is slow; should remove individual observers instead. |
|
1799 [[NSNotificationCenter defaultCenter] removeObserver:self]; |
|
1800 [_private->pluginController destroyAllPlugins]; |
|
1801 [_private->pluginController setDataSource:nil]; |
|
1802 // remove tooltips before clearing _private so removeTrackingRect: will work correctly |
|
1803 [self removeAllToolTips]; |
|
1804 [_private clear]; |
|
1805 _private->closed = YES; |
|
1806 Page* page = core([self _webView]); |
|
1807 if (page) |
|
1808 page->dragController()->setDraggingImageURL(KURL()); |
|
1809 } |
|
1810 |
|
1811 - (BOOL)_hasHTMLDocument |
|
1812 { |
|
1813 Frame* coreFrame = core([self _frame]); |
|
1814 if (!coreFrame) |
|
1815 return NO; |
|
1816 Document* document = coreFrame->document(); |
|
1817 return document && document->isHTMLDocument(); |
|
1818 } |
|
1819 |
|
1820 - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard |
|
1821 forType:(NSString *)pboardType |
|
1822 inContext:(DOMRange *)context |
|
1823 subresources:(NSArray **)subresources |
|
1824 { |
|
1825 if (pboardType == WebArchivePboardType) { |
|
1826 WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]]; |
|
1827 if (subresources) |
|
1828 *subresources = [archive subresources]; |
|
1829 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithArchive:archive]; |
|
1830 [archive release]; |
|
1831 return fragment; |
|
1832 } |
|
1833 if (pboardType == NSFilenamesPboardType) |
|
1834 return [self _documentFragmentWithPaths:[pasteboard propertyListForType:NSFilenamesPboardType]]; |
|
1835 |
|
1836 if (pboardType == NSHTMLPboardType) { |
|
1837 NSString *HTMLString = [pasteboard stringForType:NSHTMLPboardType]; |
|
1838 // This is a hack to make Microsoft's HTML pasteboard data work. See 3778785. |
|
1839 if ([HTMLString hasPrefix:@"Version:"]) { |
|
1840 NSRange range = [HTMLString rangeOfString:@"<html" options:NSCaseInsensitiveSearch]; |
|
1841 if (range.location != NSNotFound) |
|
1842 HTMLString = [HTMLString substringFromIndex:range.location]; |
|
1843 } |
|
1844 if ([HTMLString length] == 0) |
|
1845 return nil; |
|
1846 |
|
1847 return [[self _bridge] documentFragmentWithMarkupString:HTMLString baseURLString:nil]; |
|
1848 } |
|
1849 |
|
1850 // The _hasHTMLDocument clause here is a workaround for a bug in NSAttributedString: Radar 5052369. |
|
1851 // If we call _documentFromRange on an XML document we'll get "setInnerHTML: method not found". |
|
1852 // FIXME: Remove this once bug 5052369 is fixed. |
|
1853 if ([self _hasHTMLDocument] && pboardType == NSRTFPboardType || pboardType == NSRTFDPboardType) { |
|
1854 NSAttributedString *string = nil; |
|
1855 if (pboardType == NSRTFDPboardType) |
|
1856 string = [[NSAttributedString alloc] initWithRTFD:[pasteboard dataForType:NSRTFDPboardType] documentAttributes:NULL]; |
|
1857 if (string == nil) |
|
1858 string = [[NSAttributedString alloc] initWithRTF:[pasteboard dataForType:NSRTFPboardType] documentAttributes:NULL]; |
|
1859 if (string == nil) |
|
1860 return nil; |
|
1861 |
|
1862 NSDictionary *documentAttributes = [[NSDictionary alloc] initWithObjectsAndKeys: |
|
1863 [[self class] _excludedElementsForAttributedStringConversion], NSExcludedElementsDocumentAttribute, |
|
1864 self, @"WebResourceHandler", nil]; |
|
1865 NSArray *s; |
|
1866 |
|
1867 BOOL wasDeferringCallbacks = [[self _webView] defersCallbacks]; |
|
1868 if (!wasDeferringCallbacks) |
|
1869 [[self _webView] setDefersCallbacks:YES]; |
|
1870 |
|
1871 DOMDocumentFragment *fragment = [string _documentFromRange:NSMakeRange(0, [string length]) |
|
1872 document:[[self _frame] DOMDocument] |
|
1873 documentAttributes:documentAttributes |
|
1874 subresources:&s]; |
|
1875 if (subresources) |
|
1876 *subresources = s; |
|
1877 |
|
1878 NSEnumerator *e = [s objectEnumerator]; |
|
1879 WebResource *r; |
|
1880 while ((r = [e nextObject])) |
|
1881 [[self _dataSource] addSubresource:r]; |
|
1882 |
|
1883 if (!wasDeferringCallbacks) |
|
1884 [[self _webView] setDefersCallbacks:NO]; |
|
1885 |
|
1886 [documentAttributes release]; |
|
1887 [string release]; |
|
1888 return fragment; |
|
1889 } |
|
1890 if (pboardType == NSTIFFPboardType) { |
|
1891 WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSTIFFPboardType] |
|
1892 URL:uniqueURLWithRelativePart(@"image.tiff") |
|
1893 MIMEType:@"image/tiff" |
|
1894 textEncodingName:nil |
|
1895 frameName:nil]; |
|
1896 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource]; |
|
1897 [resource release]; |
|
1898 return fragment; |
|
1899 } |
|
1900 if (pboardType == NSPICTPboardType) { |
|
1901 WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPICTPboardType] |
|
1902 URL:uniqueURLWithRelativePart(@"image.pict") |
|
1903 MIMEType:@"image/pict" |
|
1904 textEncodingName:nil |
|
1905 frameName:nil]; |
|
1906 DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource]; |
|
1907 [resource release]; |
|
1908 return fragment; |
|
1909 } |
|
1910 if (pboardType == NSURLPboardType) { |
|
1911 NSURL *URL = [NSURL URLFromPasteboard:pasteboard]; |
|
1912 DOMDocument* document = [[self _frame] DOMDocument]; |
|
1913 ASSERT(document); |
|
1914 if (!document) |
|
1915 return nil; |
|
1916 DOMHTMLAnchorElement *anchor = (DOMHTMLAnchorElement *)[document createElement:@"a"]; |
|
1917 NSString *URLString = [URL _web_originalDataAsString]; |
|
1918 if ([URLString length] == 0) |
|
1919 return nil; |
|
1920 NSString *URLTitleString = [pasteboard stringForType:WebURLNamePboardType]; |
|
1921 DOMText *text = [document createTextNode:URLTitleString]; |
|
1922 [anchor setHref:URLString]; |
|
1923 [anchor appendChild:text]; |
|
1924 DOMDocumentFragment *fragment = [document createDocumentFragment]; |
|
1925 [fragment appendChild:anchor]; |
|
1926 return fragment; |
|
1927 } |
|
1928 if (pboardType == NSStringPboardType) |
|
1929 return [[self _bridge] documentFragmentWithText:[pasteboard stringForType:NSStringPboardType] |
|
1930 inContext:context]; |
|
1931 |
|
1932 return nil; |
|
1933 } |
|
1934 |
|
1935 @end |
|
1936 |
|
1937 @implementation NSView (WebHTMLViewFileInternal) |
|
1938 |
|
1939 - (void)_web_addDescendantWebHTMLViewsToArray:(NSMutableArray *)array |
|
1940 { |
|
1941 unsigned count = [_subviews count]; |
|
1942 for (unsigned i = 0; i < count; ++i) { |
|
1943 NSView *child = [_subviews objectAtIndex:i]; |
|
1944 if ([child isKindOfClass:[WebHTMLView class]]) |
|
1945 [array addObject:child]; |
|
1946 [child _web_addDescendantWebHTMLViewsToArray:array]; |
|
1947 } |
|
1948 } |
|
1949 |
|
1950 @end |
|
1951 |
|
1952 @implementation NSMutableDictionary (WebHTMLViewFileInternal) |
|
1953 |
|
1954 - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key |
|
1955 { |
|
1956 if (object == nil) { |
|
1957 [self removeObjectForKey:key]; |
|
1958 } else { |
|
1959 [self setObject:object forKey:key]; |
|
1960 } |
|
1961 } |
|
1962 |
|
1963 @end |
|
1964 |
|
1965 #ifdef BUILDING_ON_TIGER |
|
1966 |
|
1967 // The following is a workaround for |
|
1968 // <rdar://problem/3429631> window stops getting mouse moved events after first tooltip appears |
|
1969 // The trick is to define a category on NSToolTipPanel that implements setAcceptsMouseMovedEvents:. |
|
1970 // Since the category will be searched before the real class, we'll prevent the flag from being |
|
1971 // set on the tool tip panel. |
|
1972 |
|
1973 @interface NSToolTipPanel : NSPanel |
|
1974 @end |
|
1975 |
|
1976 @interface NSToolTipPanel (WebHTMLViewFileInternal) |
|
1977 @end |
|
1978 |
|
1979 @implementation NSToolTipPanel (WebHTMLViewFileInternal) |
|
1980 |
|
1981 - (void)setAcceptsMouseMovedEvents:(BOOL)flag |
|
1982 { |
|
1983 // Do nothing, preventing the tool tip panel from trying to accept mouse-moved events. |
|
1984 } |
|
1985 |
|
1986 @end |
|
1987 |
|
1988 #endif |
|
1989 |
|
1990 @interface NSArray (WebHTMLView) |
|
1991 - (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object; |
|
1992 @end |
|
1993 |
|
1994 @implementation WebHTMLView |
|
1995 |
|
1996 + (void)initialize |
|
1997 { |
|
1998 [NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes] |
|
1999 returnTypes:[[self class] _insertablePasteboardTypes]]; |
|
2000 _NSInitializeKillRing(); |
|
2001 #ifndef BUILDING_ON_TIGER |
|
2002 WebCoreObjCFinalizeOnMainThread(self); |
|
2003 #endif |
|
2004 } |
|
2005 |
|
2006 - (id)initWithFrame:(NSRect)frame |
|
2007 { |
|
2008 self = [super initWithFrame:frame]; |
|
2009 if (!self) |
|
2010 return nil; |
|
2011 |
|
2012 [self setFocusRingType:NSFocusRingTypeNone]; |
|
2013 |
|
2014 // Make all drawing go through us instead of subviews. |
|
2015 [self _setDrawsOwnDescendants:YES]; |
|
2016 |
|
2017 _private = [[WebHTMLViewPrivate alloc] init]; |
|
2018 |
|
2019 _private->pluginController = [[WebPluginController alloc] initWithDocumentView:self]; |
|
2020 _private->needsLayout = YES; |
|
2021 |
|
2022 return self; |
|
2023 } |
|
2024 |
|
2025 - (void)dealloc |
|
2026 { |
|
2027 // We can't assert that close has already been called because |
|
2028 // this view can be removed from it's superview, even though |
|
2029 // it could be needed later, so close if needed. |
|
2030 [self close]; |
|
2031 [_private release]; |
|
2032 _private = nil; |
|
2033 [super dealloc]; |
|
2034 } |
|
2035 |
|
2036 - (void)finalize |
|
2037 { |
|
2038 ASSERT_MAIN_THREAD(); |
|
2039 // We can't assert that close has already been called because |
|
2040 // this view can be removed from it's superview, even though |
|
2041 // it could be needed later, so close if needed. |
|
2042 [self close]; |
|
2043 [super finalize]; |
|
2044 } |
|
2045 |
|
2046 // Returns YES if the delegate returns YES (so we should do no more work). |
|
2047 - (BOOL)callDelegateDoCommandBySelectorIfNeeded:(SEL)selector |
|
2048 { |
|
2049 BOOL callerAlreadyCalledDelegate = _private->selectorForDoCommandBySelector == selector; |
|
2050 _private->selectorForDoCommandBySelector = 0; |
|
2051 if (callerAlreadyCalledDelegate) |
|
2052 return NO; |
|
2053 WebView *webView = [self _webView]; |
|
2054 return [[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector]; |
|
2055 } |
|
2056 |
|
2057 - (void)callWebCoreCommand:(SEL)selector |
|
2058 { |
|
2059 if ([self callDelegateDoCommandBySelectorIfNeeded:selector]) |
|
2060 return; |
|
2061 |
|
2062 Frame* coreFrame = core([self _frame]); |
|
2063 if (!coreFrame) |
|
2064 return; |
|
2065 |
|
2066 // Capitalize the first letter of the selector, since we use capitalized command |
|
2067 // names in the Editor object (why?). And remove the trailing colon. |
|
2068 const char* selectorName = sel_getName(selector); |
|
2069 size_t selectorNameLength = strlen(selectorName); |
|
2070 ASSERT(selectorNameLength >= 2); |
|
2071 ASSERT(selectorName[selectorNameLength - 1] == ':'); |
|
2072 Vector<char, 256> commandName(selectorNameLength - 1 + 1); |
|
2073 commandName[0] = toupper(selectorName[0]); |
|
2074 memcpy(&commandName[1], &selectorName[1], selectorNameLength - 2); |
|
2075 commandName[selectorNameLength - 1] = 0; |
|
2076 |
|
2077 coreFrame->editor()->execCommand(commandName.data()); |
|
2078 } |
|
2079 |
|
2080 // These commands are forwarded to the Editor object in WebCore. |
|
2081 // Ideally we'd do this for all editing commands; more of the code |
|
2082 // should be moved from here to there, and more commands should be |
|
2083 // added to this list. |
|
2084 |
|
2085 #define WEBCORE_COMMAND(command) - (void)command:(id)sender { [self callWebCoreCommand:_cmd]; } |
|
2086 |
|
2087 WEBCORE_COMMAND(deleteWordBackward) |
|
2088 WEBCORE_COMMAND(deleteWordForward) |
|
2089 WEBCORE_COMMAND(insertBacktab) |
|
2090 WEBCORE_COMMAND(insertLineBreak) |
|
2091 WEBCORE_COMMAND(insertNewline) |
|
2092 WEBCORE_COMMAND(insertTab) |
|
2093 WEBCORE_COMMAND(moveBackward) |
|
2094 WEBCORE_COMMAND(moveBackwardAndModifySelection) |
|
2095 WEBCORE_COMMAND(moveDown) |
|
2096 WEBCORE_COMMAND(moveDownAndModifySelection) |
|
2097 WEBCORE_COMMAND(moveForward) |
|
2098 WEBCORE_COMMAND(moveForwardAndModifySelection) |
|
2099 WEBCORE_COMMAND(moveLeft) |
|
2100 WEBCORE_COMMAND(moveLeftAndModifySelection) |
|
2101 WEBCORE_COMMAND(moveParagraphBackwardAndModifySelection) |
|
2102 WEBCORE_COMMAND(moveParagraphForwardAndModifySelection) |
|
2103 WEBCORE_COMMAND(moveRight) |
|
2104 WEBCORE_COMMAND(moveRightAndModifySelection) |
|
2105 WEBCORE_COMMAND(moveToBeginningOfDocument) |
|
2106 WEBCORE_COMMAND(moveToBeginningOfDocumentAndModifySelection) |
|
2107 WEBCORE_COMMAND(moveToBeginningOfLine) |
|
2108 WEBCORE_COMMAND(moveToBeginningOfLineAndModifySelection) |
|
2109 WEBCORE_COMMAND(moveToBeginningOfParagraph) |
|
2110 WEBCORE_COMMAND(moveToBeginningOfParagraphAndModifySelection) |
|
2111 WEBCORE_COMMAND(moveToBeginningOfSentence) |
|
2112 WEBCORE_COMMAND(moveToBeginningOfSentenceAndModifySelection) |
|
2113 WEBCORE_COMMAND(moveToEndOfDocument) |
|
2114 WEBCORE_COMMAND(moveToEndOfDocumentAndModifySelection) |
|
2115 WEBCORE_COMMAND(moveToEndOfLine) |
|
2116 WEBCORE_COMMAND(moveToEndOfLineAndModifySelection) |
|
2117 WEBCORE_COMMAND(moveToEndOfParagraph) |
|
2118 WEBCORE_COMMAND(moveToEndOfParagraphAndModifySelection) |
|
2119 WEBCORE_COMMAND(moveToEndOfSentence) |
|
2120 WEBCORE_COMMAND(moveToEndOfSentenceAndModifySelection) |
|
2121 WEBCORE_COMMAND(moveUp) |
|
2122 WEBCORE_COMMAND(moveUpAndModifySelection) |
|
2123 WEBCORE_COMMAND(moveWordBackward) |
|
2124 WEBCORE_COMMAND(moveWordBackwardAndModifySelection) |
|
2125 WEBCORE_COMMAND(moveWordForward) |
|
2126 WEBCORE_COMMAND(moveWordForwardAndModifySelection) |
|
2127 WEBCORE_COMMAND(moveWordLeft) |
|
2128 WEBCORE_COMMAND(moveWordLeftAndModifySelection) |
|
2129 WEBCORE_COMMAND(moveWordRight) |
|
2130 WEBCORE_COMMAND(moveWordRightAndModifySelection) |
|
2131 |
|
2132 #undef WEBCORE_COMMAND |
|
2133 |
|
2134 #define COMMAND_PROLOGUE if ([self callDelegateDoCommandBySelectorIfNeeded:_cmd]) return; |
|
2135 |
|
2136 - (IBAction)takeFindStringFromSelection:(id)sender |
|
2137 { |
|
2138 COMMAND_PROLOGUE |
|
2139 |
|
2140 if (![self _hasSelection]) { |
|
2141 NSBeep(); |
|
2142 return; |
|
2143 } |
|
2144 |
|
2145 [NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self]; |
|
2146 } |
|
2147 |
|
2148 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types |
|
2149 { |
|
2150 [pasteboard declareTypes:types owner:[self _topHTMLView]]; |
|
2151 [self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard]; |
|
2152 return YES; |
|
2153 } |
|
2154 |
|
2155 - (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pasteboard |
|
2156 { |
|
2157 Frame* coreFrame = core([self _frame]); |
|
2158 if (!coreFrame) |
|
2159 return NO; |
|
2160 if (coreFrame->selectionController()->isContentRichlyEditable()) |
|
2161 [self _pasteWithPasteboard:pasteboard allowPlainText:YES]; |
|
2162 else |
|
2163 [self _pasteAsPlainTextWithPasteboard:pasteboard]; |
|
2164 return YES; |
|
2165 } |
|
2166 |
|
2167 - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType |
|
2168 { |
|
2169 if (sendType != nil && [[self pasteboardTypesForSelection] containsObject:sendType] && [self _hasSelection]) { |
|
2170 return self; |
|
2171 } else if (returnType != nil && [[[self class] _insertablePasteboardTypes] containsObject:returnType] && [self _isEditable]) { |
|
2172 return self; |
|
2173 } |
|
2174 return [[self nextResponder] validRequestorForSendType:sendType returnType:returnType]; |
|
2175 } |
|
2176 |
|
2177 - (void)selectAll:(id)sender |
|
2178 { |
|
2179 COMMAND_PROLOGUE |
|
2180 |
|
2181 [self selectAll]; |
|
2182 } |
|
2183 |
|
2184 // jumpToSelection is the old name for what AppKit now calls centerSelectionInVisibleArea. Safari |
|
2185 // was using the old jumpToSelection selector in its menu. Newer versions of Safari will us the |
|
2186 // selector centerSelectionInVisibleArea. We'll leave this old selector in place for two reasons: |
|
2187 // (1) compatibility between older Safari and newer WebKit; (2) other WebKit-based applications |
|
2188 // might be using the jumpToSelection: selector, and we don't want to break them. |
|
2189 - (void)jumpToSelection:(id)sender |
|
2190 { |
|
2191 COMMAND_PROLOGUE |
|
2192 |
|
2193 if (Frame* coreFrame = core([self _frame])) |
|
2194 coreFrame->revealSelection(RenderLayer::gAlignCenterAlways); |
|
2195 } |
|
2196 |
|
2197 - (BOOL)validateUserInterfaceItemWithoutDelegate:(id <NSValidatedUserInterfaceItem>)item |
|
2198 { |
|
2199 SEL action = [item action]; |
|
2200 Frame* frame = core([self _frame]); |
|
2201 |
|
2202 if (Document* doc = frame->document()) { |
|
2203 if (doc->isPluginDocument()) |
|
2204 return NO; |
|
2205 |
|
2206 if (doc->isImageDocument()) { |
|
2207 if (action == @selector(copy:)) |
|
2208 return frame->loader()->isComplete(); |
|
2209 |
|
2210 return NO; |
|
2211 } |
|
2212 } |
|
2213 |
|
2214 if (action == @selector(changeSpelling:) |
|
2215 || action == @selector(_changeSpellingFromMenu:) |
|
2216 || action == @selector(checkSpelling:) |
|
2217 || action == @selector(complete:) |
|
2218 || action == @selector(deleteBackward:) |
|
2219 || action == @selector(deleteBackwardByDecomposingPreviousCharacter:) |
|
2220 || action == @selector(deleteForward:) |
|
2221 || action == @selector(deleteToBeginningOfLine:) |
|
2222 || action == @selector(deleteToBeginningOfParagraph:) |
|
2223 || action == @selector(deleteToEndOfLine:) |
|
2224 || action == @selector(deleteToEndOfParagraph:) |
|
2225 || action == @selector(deleteToMark:) |
|
2226 || action == @selector(deleteWordBackward:) |
|
2227 || action == @selector(deleteWordForward:) |
|
2228 || action == @selector(insertBacktab:) |
|
2229 || action == @selector(insertLineBreak:) |
|
2230 || action == @selector(insertNewline:) |
|
2231 || action == @selector(insertNewlineIgnoringFieldEditor:) |
|
2232 || action == @selector(insertParagraphSeparator:) |
|
2233 || action == @selector(insertTab:) |
|
2234 || action == @selector(insertTabIgnoringFieldEditor:) |
|
2235 || action == @selector(moveBackward:) |
|
2236 || action == @selector(moveBackwardAndModifySelection:) |
|
2237 || action == @selector(moveDown:) |
|
2238 || action == @selector(moveDownAndModifySelection:) |
|
2239 || action == @selector(moveForward:) |
|
2240 || action == @selector(moveForwardAndModifySelection:) |
|
2241 || action == @selector(moveLeft:) |
|
2242 || action == @selector(moveLeftAndModifySelection:) |
|
2243 || action == @selector(moveParagraphBackwardAndModifySelection:) |
|
2244 || action == @selector(moveParagraphForwardAndModifySelection:) |
|
2245 || action == @selector(moveRight:) |
|
2246 || action == @selector(moveRightAndModifySelection:) |
|
2247 || action == @selector(moveToBeginningOfDocument:) |
|
2248 || action == @selector(moveToBeginningOfDocumentAndModifySelection:) |
|
2249 || action == @selector(moveToBeginningOfSentence:) |
|
2250 || action == @selector(moveToBeginningOfSentenceAndModifySelection:) |
|
2251 || action == @selector(moveToBeginningOfLine:) |
|
2252 || action == @selector(moveToBeginningOfLineAndModifySelection:) |
|
2253 || action == @selector(moveToBeginningOfParagraph:) |
|
2254 || action == @selector(moveToBeginningOfParagraphAndModifySelection:) |
|
2255 || action == @selector(moveToEndOfDocument:) |
|
2256 || action == @selector(moveToEndOfDocumentAndModifySelection:) |
|
2257 || action == @selector(moveToEndOfSentence:) |
|
2258 || action == @selector(moveToEndOfSentenceAndModifySelection:) |
|
2259 || action == @selector(moveToEndOfLine:) |
|
2260 || action == @selector(moveToEndOfLineAndModifySelection:) |
|
2261 || action == @selector(moveToEndOfParagraph:) |
|
2262 || action == @selector(moveToEndOfParagraphAndModifySelection:) |
|
2263 || action == @selector(moveUp:) |
|
2264 || action == @selector(moveUpAndModifySelection:) |
|
2265 || action == @selector(moveWordBackward:) |
|
2266 || action == @selector(moveWordBackwardAndModifySelection:) |
|
2267 || action == @selector(moveWordForward:) |
|
2268 || action == @selector(moveWordForwardAndModifySelection:) |
|
2269 || action == @selector(moveWordLeft:) |
|
2270 || action == @selector(moveWordLeftAndModifySelection:) |
|
2271 || action == @selector(moveWordRight:) |
|
2272 || action == @selector(moveWordRightAndModifySelection:) |
|
2273 || action == @selector(pageDown:) |
|
2274 || action == @selector(pageDownAndModifySelection:) |
|
2275 || action == @selector(pageUp:) |
|
2276 || action == @selector(pageUpAndModifySelection:) |
|
2277 || action == @selector(pasteFont:) |
|
2278 || action == @selector(transpose:) |
|
2279 || action == @selector(yank:) |
|
2280 || action == @selector(yankAndSelect:)) |
|
2281 return [self _canEdit]; |
|
2282 |
|
2283 if (action == @selector(showGuessPanel:)) { |
|
2284 #ifndef BUILDING_ON_TIGER |
|
2285 // Match OS X AppKit behavior for post-Tiger. Don't change Tiger behavior. |
|
2286 NSMenuItem *menuItem = (NSMenuItem *)item; |
|
2287 if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
|
2288 BOOL panelShowing = [[[NSSpellChecker sharedSpellChecker] spellingPanel] isVisible]; |
|
2289 [menuItem setTitle:panelShowing ? UI_STRING("Hide Spelling and Grammar", "menu item title") : UI_STRING("Show Spelling and Grammar", "menu item title")]; |
|
2290 } |
|
2291 #endif |
|
2292 return [self _canEdit]; |
|
2293 } |
|
2294 |
|
2295 if (action == @selector(changeBaseWritingDirection:)) { |
|
2296 NSMenuItem *menuItem = (NSMenuItem *)item; |
|
2297 if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
|
2298 NSWritingDirection writingDirection = static_cast<NSWritingDirection>([item tag]); |
|
2299 if (writingDirection == NSWritingDirectionNatural) { |
|
2300 [menuItem setState:NO]; |
|
2301 return NO; |
|
2302 } |
|
2303 DOMCSSStyleDeclaration* style = [self _emptyStyle]; |
|
2304 [style setDirection:writingDirection == NSWritingDirectionLeftToRight ? @"LTR" : @"RTL"]; |
|
2305 [menuItem setState:[[self _bridge] selectionHasStyle:style]]; |
|
2306 } |
|
2307 return [self _canEdit]; |
|
2308 } |
|
2309 |
|
2310 if (action == @selector(toggleBaseWritingDirection:)) { |
|
2311 NSMenuItem *menuItem = (NSMenuItem *)item; |
|
2312 if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
|
2313 DOMCSSStyleDeclaration* rtl = [self _emptyStyle]; |
|
2314 [rtl setDirection:@"RTL"]; |
|
2315 // Take control of the title of the menu item, instead of just checking/unchecking it because otherwise |
|
2316 // we don't know what the check would mean. |
|
2317 [menuItem setTitle:[[self _bridge] selectionHasStyle:rtl] ? UI_STRING("Left to Right", "Left to Right context menu item") : UI_STRING("Right to Left", "Right to Left context menu item")]; |
|
2318 } |
|
2319 return [self _canEdit]; |
|
2320 } |
|
2321 |
|
2322 if (action == @selector(alignCenter:) |
|
2323 || action == @selector(alignLeft:) |
|
2324 || action == @selector(alignJustified:) |
|
2325 || action == @selector(alignRight:) |
|
2326 || action == @selector(changeAttributes:) |
|
2327 || action == @selector(changeColor:) |
|
2328 || action == @selector(changeFont:) |
|
2329 || action == @selector(indent:) |
|
2330 || action == @selector(outdent:)) |
|
2331 return [self _canEditRichly]; |
|
2332 |
|
2333 if (action == @selector(capitalizeWord:) |
|
2334 || action == @selector(lowercaseWord:) |
|
2335 || action == @selector(uppercaseWord:)) |
|
2336 return [self _hasSelection] && [self _isEditable]; |
|
2337 |
|
2338 if (action == @selector(centerSelectionInVisibleArea:) |
|
2339 || action == @selector(jumpToSelection:) |
|
2340 || action == @selector(copyFont:) |
|
2341 || action == @selector(setMark:)) |
|
2342 return [self _hasSelection] || ([self _isEditable] && [self _hasInsertionPoint]); |
|
2343 |
|
2344 if (action == @selector(changeDocumentBackgroundColor:)) |
|
2345 return [[self _webView] isEditable] && [self _canEditRichly]; |
|
2346 |
|
2347 if (action == @selector(copy:)) |
|
2348 return (frame && frame->editor()->canDHTMLCopy()) || frame->editor()->canCopy(); |
|
2349 |
|
2350 if (action == @selector(cut:)) |
|
2351 return (frame && frame->editor()->canDHTMLCut()) || frame->editor()->canCut(); |
|
2352 |
|
2353 if (action == @selector(delete:)) |
|
2354 return (frame && frame->editor()->canDelete()); |
|
2355 |
|
2356 if (action == @selector(_ignoreSpellingFromMenu:) |
|
2357 || action == @selector(_learnSpellingFromMenu:) |
|
2358 || action == @selector(takeFindStringFromSelection:)) |
|
2359 return [self _hasSelection]; |
|
2360 |
|
2361 if (action == @selector(paste:) || action == @selector(pasteAsPlainText:)) |
|
2362 return (frame && frame->editor()->canDHTMLPaste()) || frame->editor()->canPaste(); |
|
2363 |
|
2364 if (action == @selector(pasteAsRichText:)) |
|
2365 return frame && (frame->editor()->canDHTMLPaste() |
|
2366 || (frame->editor()->canPaste() && frame->selectionController()->isContentRichlyEditable())); |
|
2367 |
|
2368 if (action == @selector(performFindPanelAction:)) |
|
2369 // FIXME: Not yet implemented. |
|
2370 return NO; |
|
2371 |
|
2372 if (action == @selector(selectToMark:) |
|
2373 || action == @selector(swapWithMark:)) |
|
2374 return [self _hasSelectionOrInsertionPoint] && [[self _bridge] markDOMRange] != nil; |
|
2375 |
|
2376 if (action == @selector(subscript:)) { |
|
2377 NSMenuItem *menuItem = (NSMenuItem *)item; |
|
2378 if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
|
2379 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
2380 [style setVerticalAlign:@"sub"]; |
|
2381 [menuItem setState:[[self _bridge] selectionHasStyle:style]]; |
|
2382 } |
|
2383 return [self _canEditRichly]; |
|
2384 } |
|
2385 |
|
2386 if (action == @selector(superscript:)) { |
|
2387 NSMenuItem *menuItem = (NSMenuItem *)item; |
|
2388 if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
|
2389 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
2390 [style setVerticalAlign:@"super"]; |
|
2391 [menuItem setState:[[self _bridge] selectionHasStyle:style]]; |
|
2392 } |
|
2393 return [self _canEditRichly]; |
|
2394 } |
|
2395 |
|
2396 if (action == @selector(underline:)) { |
|
2397 NSMenuItem *menuItem = (NSMenuItem *)item; |
|
2398 if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
|
2399 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
2400 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""]; |
|
2401 [menuItem setState:[[self _bridge] selectionHasStyle:style]]; |
|
2402 } |
|
2403 return [self _canEditRichly]; |
|
2404 } |
|
2405 |
|
2406 if (action == @selector(unscript:)) { |
|
2407 NSMenuItem *menuItem = (NSMenuItem *)item; |
|
2408 if ([menuItem isKindOfClass:[NSMenuItem class]]) { |
|
2409 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
2410 [style setVerticalAlign:@"baseline"]; |
|
2411 [menuItem setState:[[self _bridge] selectionHasStyle:style]]; |
|
2412 } |
|
2413 return [self _canEditRichly]; |
|
2414 } |
|
2415 |
|
2416 if (action == @selector(_lookUpInDictionaryFromMenu:)) { |
|
2417 return [self _hasSelection]; |
|
2418 } |
|
2419 |
|
2420 #ifndef BUILDING_ON_TIGER |
|
2421 if (action == @selector(toggleGrammarChecking:)) { |
|
2422 // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must validate |
|
2423 // the selector here because we implement it here, and we must implement it here because the AppKit |
|
2424 // code checks the first responder. |
|
2425 BOOL checkMark = [self isGrammarCheckingEnabled]; |
|
2426 if ([(NSObject *)item isKindOfClass:[NSMenuItem class]]) { |
|
2427 NSMenuItem *menuItem = (NSMenuItem *)item; |
|
2428 [menuItem setState:checkMark ? NSOnState : NSOffState]; |
|
2429 } |
|
2430 return YES; |
|
2431 } |
|
2432 #endif |
|
2433 |
|
2434 return YES; |
|
2435 } |
|
2436 |
|
2437 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item |
|
2438 { |
|
2439 BOOL result = [self validateUserInterfaceItemWithoutDelegate:item]; |
|
2440 return CallUIDelegateReturningBoolean(result, [self _webView], @selector(webView:validateUserInterfaceItem:defaultValidation:), item, result); |
|
2441 } |
|
2442 |
|
2443 - (BOOL)acceptsFirstResponder |
|
2444 { |
|
2445 // Don't accept first responder when we first click on this view. |
|
2446 // We have to pass the event down through WebCore first to be sure we don't hit a subview. |
|
2447 // Do accept first responder at any other time, for example from keyboard events, |
|
2448 // or from calls back from WebCore once we begin mouse-down event handling. |
|
2449 NSEvent *event = [NSApp currentEvent]; |
|
2450 if ([event type] == NSLeftMouseDown |
|
2451 && !_private->handlingMouseDownEvent |
|
2452 && NSPointInRect([event locationInWindow], [self convertRect:[self visibleRect] toView:nil])) { |
|
2453 return NO; |
|
2454 } |
|
2455 return YES; |
|
2456 } |
|
2457 |
|
2458 - (BOOL)maintainsInactiveSelection |
|
2459 { |
|
2460 // This method helps to determine whether the WebHTMLView should maintain |
|
2461 // an inactive selection when it's not first responder. |
|
2462 // Traditionally, these views have not maintained such selections, |
|
2463 // clearing them when the view was not first responder. However, |
|
2464 // to fix bugs like this one: |
|
2465 // <rdar://problem/3672088>: "Editable WebViews should maintain a selection even |
|
2466 // when they're not firstResponder" |
|
2467 // it was decided to add a switch to act more like an NSTextView. |
|
2468 |
|
2469 if ([[self _webView] maintainsInactiveSelection]) |
|
2470 return YES; |
|
2471 |
|
2472 // Predict the case where we are losing first responder status only to |
|
2473 // gain it back again. Want to keep the selection in that case. |
|
2474 id nextResponder = [[self window] _newFirstResponderAfterResigning]; |
|
2475 if ([nextResponder isKindOfClass:[NSScrollView class]]) { |
|
2476 id contentView = [nextResponder contentView]; |
|
2477 if (contentView) |
|
2478 nextResponder = contentView; |
|
2479 } |
|
2480 if ([nextResponder isKindOfClass:[NSClipView class]]) { |
|
2481 id documentView = [nextResponder documentView]; |
|
2482 if (documentView) |
|
2483 nextResponder = documentView; |
|
2484 } |
|
2485 if (nextResponder == self) |
|
2486 return YES; |
|
2487 |
|
2488 Frame* coreFrame = core([self _frame]); |
|
2489 bool selectionIsEditable = coreFrame && coreFrame->selectionController()->isContentEditable(); |
|
2490 bool nextResponderIsInWebView = [nextResponder isKindOfClass:[NSView class]] |
|
2491 && [nextResponder isDescendantOf:[[[self _webView] mainFrame] frameView]]; |
|
2492 |
|
2493 return selectionIsEditable && nextResponderIsInWebView; |
|
2494 } |
|
2495 |
|
2496 - (void)addMouseMovedObserver |
|
2497 { |
|
2498 if (!_private->dataSource || ![self _isTopHTMLView]) |
|
2499 return; |
|
2500 |
|
2501 // Unless the Dashboard asks us to do this for all windows, keep an observer going only for the key window. |
|
2502 if (!([[self window] isKeyWindow] || [[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows])) |
|
2503 return; |
|
2504 |
|
2505 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mouseMovedNotification:) |
|
2506 name:WKMouseMovedNotification() object:nil]; |
|
2507 [self _frameOrBoundsChanged]; |
|
2508 } |
|
2509 |
|
2510 - (void)removeMouseMovedObserverUnconditionally |
|
2511 { |
|
2512 [[NSNotificationCenter defaultCenter] removeObserver:self |
|
2513 name:WKMouseMovedNotification() object:nil]; |
|
2514 } |
|
2515 |
|
2516 - (void)removeMouseMovedObserver |
|
2517 { |
|
2518 // Don't remove the observer if we're running the Dashboard. |
|
2519 if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysSendMouseEventsToAllWindows]) |
|
2520 return; |
|
2521 |
|
2522 [[self _webView] _mouseDidMoveOverElement:nil modifierFlags:0]; |
|
2523 [self removeMouseMovedObserverUnconditionally]; |
|
2524 } |
|
2525 |
|
2526 - (void)addSuperviewObservers |
|
2527 { |
|
2528 // We watch the bounds of our superview, so that we can do a layout when the size |
|
2529 // of the superview changes. This is different from other scrollable things that don't |
|
2530 // need this kind of thing because their layout doesn't change. |
|
2531 |
|
2532 // We need to pay attention to both height and width because our "layout" has to change |
|
2533 // to extend the background the full height of the space and because some elements have |
|
2534 // sizes that are based on the total size of the view. |
|
2535 |
|
2536 NSView *superview = [self superview]; |
|
2537 if (superview && [self window]) { |
|
2538 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged) |
|
2539 name:NSViewFrameDidChangeNotification object:superview]; |
|
2540 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged) |
|
2541 name:NSViewBoundsDidChangeNotification object:superview]; |
|
2542 |
|
2543 // In addition to registering for frame/bounds change notifications, call -_frameOrBoundsChanged. |
|
2544 // It will check the current size/scroll against the previous layout's size/scroll. We need to |
|
2545 // do this here to catch the case where the WebView is laid out at one size, removed from its |
|
2546 // window, resized, and inserted into another window. Our frame/bounds changed notifications |
|
2547 // will not be sent in that situation, since we only watch for changes while in the view hierarchy. |
|
2548 [self _frameOrBoundsChanged]; |
|
2549 } |
|
2550 } |
|
2551 |
|
2552 - (void)removeSuperviewObservers |
|
2553 { |
|
2554 NSView *superview = [self superview]; |
|
2555 if (superview && [self window]) { |
|
2556 [[NSNotificationCenter defaultCenter] removeObserver:self |
|
2557 name:NSViewFrameDidChangeNotification object:superview]; |
|
2558 [[NSNotificationCenter defaultCenter] removeObserver:self |
|
2559 name:NSViewBoundsDidChangeNotification object:superview]; |
|
2560 } |
|
2561 } |
|
2562 |
|
2563 - (void)addWindowObservers |
|
2564 { |
|
2565 NSWindow *window = [self window]; |
|
2566 if (window) { |
|
2567 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeKey:) |
|
2568 name:NSWindowDidBecomeKeyNotification object:nil]; |
|
2569 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResignKey:) |
|
2570 name:NSWindowDidResignKeyNotification object:nil]; |
|
2571 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillClose:) |
|
2572 name:NSWindowWillCloseNotification object:window]; |
|
2573 } |
|
2574 } |
|
2575 |
|
2576 - (void)removeWindowObservers |
|
2577 { |
|
2578 NSWindow *window = [self window]; |
|
2579 if (window) { |
|
2580 [[NSNotificationCenter defaultCenter] removeObserver:self |
|
2581 name:NSWindowDidBecomeKeyNotification object:nil]; |
|
2582 [[NSNotificationCenter defaultCenter] removeObserver:self |
|
2583 name:NSWindowDidResignKeyNotification object:nil]; |
|
2584 [[NSNotificationCenter defaultCenter] removeObserver:self |
|
2585 name:NSWindowWillCloseNotification object:window]; |
|
2586 } |
|
2587 } |
|
2588 |
|
2589 - (void)viewWillMoveToSuperview:(NSView *)newSuperview |
|
2590 { |
|
2591 [self removeSuperviewObservers]; |
|
2592 } |
|
2593 |
|
2594 - (void)viewDidMoveToSuperview |
|
2595 { |
|
2596 // Do this here in case the text size multiplier changed when a non-HTML |
|
2597 // view was installed. |
|
2598 if ([self superview] != nil) { |
|
2599 [self _updateTextSizeMultiplier]; |
|
2600 [self addSuperviewObservers]; |
|
2601 } |
|
2602 } |
|
2603 |
|
2604 static void _updateActiveStateTimerCallback(CFRunLoopTimerRef timer, void *info) |
|
2605 { |
|
2606 WebHTMLView *view = (WebHTMLView *)info; |
|
2607 [view _updateActiveState]; |
|
2608 } |
|
2609 |
|
2610 - (void)viewWillMoveToWindow:(NSWindow *)window |
|
2611 { |
|
2612 // Don't do anything if we aren't initialized. This happens |
|
2613 // when decoding a WebView. When WebViews are decoded their subviews |
|
2614 // are created by initWithCoder: and so won't be normally |
|
2615 // initialized. The stub views are discarded by WebView. |
|
2616 if (!_private) |
|
2617 return; |
|
2618 |
|
2619 // FIXME: Some of these calls may not work because this view may be already removed from it's superview. |
|
2620 [self removeMouseMovedObserverUnconditionally]; |
|
2621 [self removeWindowObservers]; |
|
2622 [self removeSuperviewObservers]; |
|
2623 [self _cancelUpdateMouseoverTimer]; |
|
2624 [self _cancelUpdateActiveStateTimer]; |
|
2625 |
|
2626 [[self _pluginController] stopAllPlugins]; |
|
2627 } |
|
2628 |
|
2629 - (void)viewDidMoveToWindow |
|
2630 { |
|
2631 // Don't do anything if we aren't initialized. This happens |
|
2632 // when decoding a WebView. When WebViews are decoded their subviews |
|
2633 // are created by initWithCoder: and so won't be normally |
|
2634 // initialized. The stub views are discarded by WebView. |
|
2635 if (!_private) |
|
2636 return; |
|
2637 |
|
2638 [self _stopAutoscrollTimer]; |
|
2639 if ([self window]) { |
|
2640 _private->lastScrollPosition = [[self superview] bounds].origin; |
|
2641 [self addWindowObservers]; |
|
2642 [self addSuperviewObservers]; |
|
2643 [self addMouseMovedObserver]; |
|
2644 |
|
2645 // Schedule this update, rather than making the call right now. |
|
2646 // The reason is that placing the caret in the just-installed view requires |
|
2647 // the HTML/XML document to be available on the WebCore side, but it is not |
|
2648 // at the time this code is running. However, it will be there on the next |
|
2649 // crank of the run loop. Doing this helps to make a blinking caret appear |
|
2650 // in a new, empty window "automatic". |
|
2651 if (!_private->updateActiveStateTimer) { |
|
2652 CFRunLoopTimerContext context = { 0, self, NULL, NULL, NULL }; |
|
2653 _private->updateActiveStateTimer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 0, 0, 0, |
|
2654 _updateActiveStateTimerCallback, &context); |
|
2655 CFRunLoopAddTimer(CFRunLoopGetCurrent(), _private->updateActiveStateTimer, kCFRunLoopDefaultMode); |
|
2656 } |
|
2657 |
|
2658 [[self _pluginController] startAllPlugins]; |
|
2659 |
|
2660 _private->lastScrollPosition = NSZeroPoint; |
|
2661 } |
|
2662 } |
|
2663 |
|
2664 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow |
|
2665 { |
|
2666 [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewWillMoveToHostWindow:) withObject:hostWindow]; |
|
2667 } |
|
2668 |
|
2669 - (void)viewDidMoveToHostWindow |
|
2670 { |
|
2671 [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewDidMoveToHostWindow) withObject:nil]; |
|
2672 } |
|
2673 |
|
2674 |
|
2675 - (void)addSubview:(NSView *)view |
|
2676 { |
|
2677 [super addSubview:view]; |
|
2678 |
|
2679 if ([WebPluginController isPlugInView:view]) |
|
2680 [[self _pluginController] addPlugin:view]; |
|
2681 } |
|
2682 |
|
2683 - (void)willRemoveSubview:(NSView *)subview |
|
2684 { |
|
2685 if ([WebPluginController isPlugInView:subview]) |
|
2686 [[self _pluginController] destroyPlugin:subview]; |
|
2687 |
|
2688 [super willRemoveSubview:subview]; |
|
2689 } |
|
2690 |
|
2691 - (void)reapplyStyles |
|
2692 { |
|
2693 if (!_private->needsToApplyStyles) { |
|
2694 return; |
|
2695 } |
|
2696 |
|
2697 #ifdef _KWQ_TIMING |
|
2698 double start = CFAbsoluteTimeGetCurrent(); |
|
2699 #endif |
|
2700 |
|
2701 [[self _bridge] reapplyStylesForDeviceType: |
|
2702 _private->printing ? WebCoreDevicePrinter : WebCoreDeviceScreen]; |
|
2703 |
|
2704 #ifdef _KWQ_TIMING |
|
2705 double thisTime = CFAbsoluteTimeGetCurrent() - start; |
|
2706 LOG(Timing, "%s apply style seconds = %f", [self URL], thisTime); |
|
2707 #endif |
|
2708 |
|
2709 _private->needsToApplyStyles = NO; |
|
2710 } |
|
2711 |
|
2712 // Do a layout, but set up a new fixed width for the purposes of doing printing layout. |
|
2713 // minPageWidth==0 implies a non-printing layout |
|
2714 - (void)layoutToMinimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustingViewSize:(BOOL)adjustViewSize |
|
2715 { |
|
2716 [self reapplyStyles]; |
|
2717 |
|
2718 if (!_private->needsLayout && ![[self _bridge] needsLayout]) |
|
2719 return; |
|
2720 |
|
2721 #ifdef _KWQ_TIMING |
|
2722 double start = CFAbsoluteTimeGetCurrent(); |
|
2723 #endif |
|
2724 |
|
2725 LOG(View, "%@ doing layout", self); |
|
2726 |
|
2727 if (minPageWidth > 0.0) { |
|
2728 [[self _bridge] forceLayoutWithMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize]; |
|
2729 } else { |
|
2730 [[self _bridge] forceLayoutAdjustingViewSize:adjustViewSize]; |
|
2731 } |
|
2732 _private->needsLayout = NO; |
|
2733 |
|
2734 if (!_private->printing) { |
|
2735 // get size of the containing dynamic scrollview, so |
|
2736 // appearance and disappearance of scrollbars will not show up |
|
2737 // as a size change |
|
2738 NSSize newLayoutFrameSize = [[[self superview] superview] frame].size; |
|
2739 if (_private->laidOutAtLeastOnce && !NSEqualSizes(_private->lastLayoutFrameSize, newLayoutFrameSize)) { |
|
2740 [[self _bridge] sendResizeEvent]; |
|
2741 if ([[self _bridge] needsLayout]) |
|
2742 [[self _bridge] forceLayoutAdjustingViewSize:NO]; |
|
2743 } |
|
2744 _private->laidOutAtLeastOnce = YES; |
|
2745 _private->lastLayoutSize = [(NSClipView *)[self superview] documentVisibleRect].size; |
|
2746 _private->lastLayoutFrameSize = newLayoutFrameSize; |
|
2747 } |
|
2748 |
|
2749 #ifdef _KWQ_TIMING |
|
2750 double thisTime = CFAbsoluteTimeGetCurrent() - start; |
|
2751 LOG(Timing, "%s layout seconds = %f", [self URL], thisTime); |
|
2752 #endif |
|
2753 } |
|
2754 |
|
2755 - (void)layout |
|
2756 { |
|
2757 [self layoutToMinimumPageWidth:0.0f maximumPageWidth:0.0f adjustingViewSize:NO]; |
|
2758 } |
|
2759 |
|
2760 - (NSMenu *)menuForEvent:(NSEvent *)event |
|
2761 { |
|
2762 [_private->compController endRevertingChange:NO moveLeft:NO]; |
|
2763 |
|
2764 _private->handlingMouseDownEvent = YES; |
|
2765 BOOL handledEvent = NO; |
|
2766 Frame* coreFrame = core([self _frame]); |
|
2767 |
|
2768 if (!coreFrame) { |
|
2769 _private->handlingMouseDownEvent = NO; |
|
2770 return nil; |
|
2771 } |
|
2772 |
|
2773 Page* page = coreFrame->page(); |
|
2774 if (!page) |
|
2775 return nil; |
|
2776 |
|
2777 page->contextMenuController()->clearContextMenu(); |
|
2778 handledEvent = coreFrame->eventHandler()->sendContextMenuEvent(PlatformMouseEvent(event)); |
|
2779 _private->handlingMouseDownEvent = NO; |
|
2780 |
|
2781 if (!handledEvent) |
|
2782 return nil; |
|
2783 |
|
2784 ContextMenu* coreMenu = page->contextMenuController()->contextMenu(); |
|
2785 if (!coreMenu) |
|
2786 return nil; |
|
2787 |
|
2788 NSArray* menuItems = coreMenu->platformDescription(); |
|
2789 NSMenu* menu = nil; |
|
2790 if (menuItems && [menuItems count] > 0) { |
|
2791 menu = [[[NSMenu alloc] init] autorelease]; |
|
2792 for (unsigned i = 0; i < [menuItems count]; i++) |
|
2793 [menu addItem:[menuItems objectAtIndex:i]]; |
|
2794 } |
|
2795 |
|
2796 return menu; |
|
2797 } |
|
2798 |
|
2799 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag |
|
2800 { |
|
2801 return [self searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:NO]; |
|
2802 } |
|
2803 |
|
2804 - (void)clearFocus |
|
2805 { |
|
2806 Frame* coreFrame = core([self _frame]); |
|
2807 if (!coreFrame) |
|
2808 return; |
|
2809 Document* document = coreFrame->document(); |
|
2810 if (!document) |
|
2811 return; |
|
2812 |
|
2813 document->setFocusedNode(0); |
|
2814 } |
|
2815 |
|
2816 - (BOOL)isOpaque |
|
2817 { |
|
2818 return [[self _webView] drawsBackground]; |
|
2819 } |
|
2820 |
|
2821 - (void)setNeedsDisplay:(BOOL)flag |
|
2822 { |
|
2823 LOG(View, "%@ flag = %d", self, (int)flag); |
|
2824 [super setNeedsDisplay: flag]; |
|
2825 } |
|
2826 |
|
2827 - (void)setNeedsLayout: (BOOL)flag |
|
2828 { |
|
2829 LOG(View, "%@ flag = %d", self, (int)flag); |
|
2830 _private->needsLayout = flag; |
|
2831 } |
|
2832 |
|
2833 |
|
2834 - (void)setNeedsToApplyStyles: (BOOL)flag |
|
2835 { |
|
2836 LOG(View, "%@ flag = %d", self, (int)flag); |
|
2837 _private->needsToApplyStyles = flag; |
|
2838 } |
|
2839 |
|
2840 - (void)drawSingleRect:(NSRect)rect |
|
2841 { |
|
2842 [NSGraphicsContext saveGraphicsState]; |
|
2843 NSRectClip(rect); |
|
2844 |
|
2845 ASSERT([[self superview] isKindOfClass:[WebClipView class]]); |
|
2846 |
|
2847 [(WebClipView *)[self superview] setAdditionalClip:rect]; |
|
2848 |
|
2849 NS_DURING { |
|
2850 if ([self _transparentBackground]) { |
|
2851 [[NSColor clearColor] set]; |
|
2852 NSRectFill (rect); |
|
2853 } |
|
2854 |
|
2855 [[self _bridge] drawRect:rect]; |
|
2856 |
|
2857 // This hack is needed for <rdar://problem/5023545>. We can hit a race condition where drawRect will be |
|
2858 // called after the WebView has closed. If the client did not properly close the WebView and set the |
|
2859 // UIDelegate to nil, then the UIDelegate will be stale and this code will crash. |
|
2860 static BOOL version3OrLaterClient = WebKitLinkedOnOrAfter(WEBKIT_FIRST_VERSION_WITHOUT_QUICKBOOKS_QUIRK); |
|
2861 if (version3OrLaterClient) { |
|
2862 WebView *webView = [self _webView]; |
|
2863 [[webView _UIDelegateForwarder] webView:webView didDrawRect:[webView convertRect:rect fromView:self]]; |
|
2864 } |
|
2865 |
|
2866 [(WebClipView *)[self superview] resetAdditionalClip]; |
|
2867 |
|
2868 [NSGraphicsContext restoreGraphicsState]; |
|
2869 } NS_HANDLER { |
|
2870 [(WebClipView *)[self superview] resetAdditionalClip]; |
|
2871 [NSGraphicsContext restoreGraphicsState]; |
|
2872 LOG_ERROR("Exception caught while drawing: %@", localException); |
|
2873 [localException raise]; |
|
2874 } NS_ENDHANDLER |
|
2875 } |
|
2876 |
|
2877 - (void)drawRect:(NSRect)rect |
|
2878 { |
|
2879 ASSERT_MAIN_THREAD(); |
|
2880 LOG(View, "%@ drawing", self); |
|
2881 |
|
2882 const NSRect *rects; |
|
2883 NSInteger count; |
|
2884 [self getRectsBeingDrawn:&rects count:&count]; |
|
2885 |
|
2886 BOOL subviewsWereSetAside = _private->subviewsSetAside; |
|
2887 if (subviewsWereSetAside) |
|
2888 [self _restoreSubviews]; |
|
2889 |
|
2890 #ifdef _KWQ_TIMING |
|
2891 double start = CFAbsoluteTimeGetCurrent(); |
|
2892 #endif |
|
2893 |
|
2894 // If count == 0 here, use the rect passed in for drawing. This is a workaround for: |
|
2895 // <rdar://problem/3908282> REGRESSION (Mail): No drag image dragging selected text in Blot and Mail |
|
2896 // The reason for the workaround is that this method is called explicitly from the code |
|
2897 // to generate a drag image, and at that time, getRectsBeingDrawn:count: will return a zero count. |
|
2898 const int cRectThreshold = 10; |
|
2899 const float cWastedSpaceThreshold = 0.75f; |
|
2900 BOOL useUnionedRect = (count <= 1) || (count > cRectThreshold); |
|
2901 if (!useUnionedRect) { |
|
2902 // Attempt to guess whether or not we should use the unioned rect or the individual rects. |
|
2903 // We do this by computing the percentage of "wasted space" in the union. If that wasted space |
|
2904 // is too large, then we will do individual rect painting instead. |
|
2905 float unionPixels = (rect.size.width * rect.size.height); |
|
2906 float singlePixels = 0; |
|
2907 for (int i = 0; i < count; ++i) |
|
2908 singlePixels += rects[i].size.width * rects[i].size.height; |
|
2909 float wastedSpace = 1 - (singlePixels / unionPixels); |
|
2910 if (wastedSpace <= cWastedSpaceThreshold) |
|
2911 useUnionedRect = YES; |
|
2912 } |
|
2913 |
|
2914 if (useUnionedRect) |
|
2915 [self drawSingleRect:rect]; |
|
2916 else |
|
2917 for (int i = 0; i < count; ++i) |
|
2918 [self drawSingleRect:rects[i]]; |
|
2919 |
|
2920 #ifdef _KWQ_TIMING |
|
2921 double thisTime = CFAbsoluteTimeGetCurrent() - start; |
|
2922 LOG(Timing, "%s draw seconds = %f", widget->part()->baseURL().URL().latin1(), thisTime); |
|
2923 #endif |
|
2924 |
|
2925 if (subviewsWereSetAside) |
|
2926 [self _setAsideSubviews]; |
|
2927 } |
|
2928 |
|
2929 // Turn off the additional clip while computing our visibleRect. |
|
2930 - (NSRect)visibleRect |
|
2931 { |
|
2932 if (!([[self superview] isKindOfClass:[WebClipView class]])) |
|
2933 return [super visibleRect]; |
|
2934 |
|
2935 WebClipView *clipView = (WebClipView *)[self superview]; |
|
2936 |
|
2937 BOOL hasAdditionalClip = [clipView hasAdditionalClip]; |
|
2938 if (!hasAdditionalClip) { |
|
2939 return [super visibleRect]; |
|
2940 } |
|
2941 |
|
2942 NSRect additionalClip = [clipView additionalClip]; |
|
2943 [clipView resetAdditionalClip]; |
|
2944 NSRect visibleRect = [super visibleRect]; |
|
2945 [clipView setAdditionalClip:additionalClip]; |
|
2946 return visibleRect; |
|
2947 } |
|
2948 |
|
2949 - (BOOL)isFlipped |
|
2950 { |
|
2951 return YES; |
|
2952 } |
|
2953 |
|
2954 - (void)windowDidBecomeKey:(NSNotification *)notification |
|
2955 { |
|
2956 NSWindow *keyWindow = [notification object]; |
|
2957 |
|
2958 if (keyWindow == [self window]) |
|
2959 [self addMouseMovedObserver]; |
|
2960 |
|
2961 if (keyWindow == [self window] || keyWindow == [[self window] attachedSheet]) |
|
2962 [self _updateActiveState]; |
|
2963 } |
|
2964 |
|
2965 - (void)windowDidResignKey:(NSNotification *)notification |
|
2966 { |
|
2967 NSWindow *formerKeyWindow = [notification object]; |
|
2968 |
|
2969 if (formerKeyWindow == [self window]) |
|
2970 [self removeMouseMovedObserver]; |
|
2971 |
|
2972 if (formerKeyWindow == [self window] || formerKeyWindow == [[self window] attachedSheet]) { |
|
2973 [self _updateActiveState]; |
|
2974 [_private->compController endRevertingChange:NO moveLeft:NO]; |
|
2975 } |
|
2976 } |
|
2977 |
|
2978 - (void)windowWillClose:(NSNotification *)notification |
|
2979 { |
|
2980 [_private->compController endRevertingChange:NO moveLeft:NO]; |
|
2981 [[self _pluginController] destroyAllPlugins]; |
|
2982 } |
|
2983 |
|
2984 - (void)scrollWheel:(NSEvent *)event |
|
2985 { |
|
2986 [self retain]; |
|
2987 Frame* frame = core([self _frame]); |
|
2988 if (!frame || !frame->eventHandler()->wheelEvent(event)) |
|
2989 [[self nextResponder] scrollWheel:event]; |
|
2990 [self release]; |
|
2991 } |
|
2992 |
|
2993 - (BOOL)_isSelectionEvent:(NSEvent *)event |
|
2994 { |
|
2995 NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil]; |
|
2996 return [[[self elementAtPoint:point allowShadowContent:YES] objectForKey:WebElementIsSelectedKey] boolValue]; |
|
2997 } |
|
2998 |
|
2999 - (BOOL)acceptsFirstMouse:(NSEvent *)event |
|
3000 { |
|
3001 NSView *hitView = [self _hitViewForEvent:event]; |
|
3002 WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil; |
|
3003 |
|
3004 if ([[self _webView] _dashboardBehavior:WebDashboardBehaviorAlwaysAcceptsFirstMouse]) |
|
3005 return YES; |
|
3006 |
|
3007 if (hitHTMLView) { |
|
3008 bool result = false; |
|
3009 if (Frame* coreFrame = core([hitHTMLView _frame])) { |
|
3010 coreFrame->eventHandler()->setActivationEventNumber([event eventNumber]); |
|
3011 [hitHTMLView _setMouseDownEvent:event]; |
|
3012 if ([hitHTMLView _isSelectionEvent:event]) |
|
3013 result = coreFrame->eventHandler()->eventMayStartDrag(event); |
|
3014 [hitHTMLView _setMouseDownEvent:nil]; |
|
3015 } |
|
3016 return result; |
|
3017 } |
|
3018 return [hitView acceptsFirstMouse:event]; |
|
3019 } |
|
3020 |
|
3021 - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event |
|
3022 { |
|
3023 NSView *hitView = [self _hitViewForEvent:event]; |
|
3024 WebHTMLView *hitHTMLView = [hitView isKindOfClass:[self class]] ? (WebHTMLView *)hitView : nil; |
|
3025 if (hitHTMLView) { |
|
3026 bool result = false; |
|
3027 if ([hitHTMLView _isSelectionEvent:event]) |
|
3028 if (Frame* coreFrame = core([hitHTMLView _frame])) { |
|
3029 [hitHTMLView _setMouseDownEvent:event]; |
|
3030 result = coreFrame->eventHandler()->eventMayStartDrag(event); |
|
3031 [hitHTMLView _setMouseDownEvent:nil]; |
|
3032 } |
|
3033 return result; |
|
3034 } |
|
3035 return [hitView shouldDelayWindowOrderingForEvent:event]; |
|
3036 } |
|
3037 |
|
3038 - (void)mouseDown:(NSEvent *)event |
|
3039 { |
|
3040 [self retain]; |
|
3041 |
|
3042 _private->handlingMouseDownEvent = YES; |
|
3043 |
|
3044 // Record the mouse down position so we can determine drag hysteresis. |
|
3045 [self _setMouseDownEvent:event]; |
|
3046 |
|
3047 NSInputManager *currentInputManager = [NSInputManager currentInputManager]; |
|
3048 if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) |
|
3049 goto done; |
|
3050 |
|
3051 [_private->compController endRevertingChange:NO moveLeft:NO]; |
|
3052 |
|
3053 // If the web page handles the context menu event and menuForEvent: returns nil, we'll get control click events here. |
|
3054 // We don't want to pass them along to KHTML a second time. |
|
3055 if (!([event modifierFlags] & NSControlKeyMask)) { |
|
3056 _private->ignoringMouseDraggedEvents = NO; |
|
3057 |
|
3058 // Don't do any mouseover while the mouse is down. |
|
3059 [self _cancelUpdateMouseoverTimer]; |
|
3060 |
|
3061 // Let WebCore get a chance to deal with the event. This will call back to us |
|
3062 // to start the autoscroll timer if appropriate. |
|
3063 if (Frame* coreframe = core([self _frame])) |
|
3064 coreframe->eventHandler()->mouseDown(event); |
|
3065 } |
|
3066 |
|
3067 done: |
|
3068 [_private->firstResponderTextViewAtMouseDownTime release]; |
|
3069 _private->firstResponderTextViewAtMouseDownTime = nil; |
|
3070 |
|
3071 _private->handlingMouseDownEvent = NO; |
|
3072 |
|
3073 [self release]; |
|
3074 } |
|
3075 |
|
3076 - (void)dragImage:(NSImage *)dragImage |
|
3077 at:(NSPoint)at |
|
3078 offset:(NSSize)offset |
|
3079 event:(NSEvent *)event |
|
3080 pasteboard:(NSPasteboard *)pasteboard |
|
3081 source:(id)source |
|
3082 slideBack:(BOOL)slideBack |
|
3083 { |
|
3084 ASSERT(self == [self _topHTMLView]); |
|
3085 [super dragImage:dragImage at:at offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack]; |
|
3086 } |
|
3087 |
|
3088 - (void)mouseDragged:(NSEvent *)event |
|
3089 { |
|
3090 NSInputManager *currentInputManager = [NSInputManager currentInputManager]; |
|
3091 if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) |
|
3092 return; |
|
3093 |
|
3094 [self retain]; |
|
3095 |
|
3096 if (!_private->ignoringMouseDraggedEvents) |
|
3097 if (Frame* coreframe = core([self _frame])) |
|
3098 coreframe->eventHandler()->mouseDragged(event); |
|
3099 |
|
3100 [self release]; |
|
3101 } |
|
3102 |
|
3103 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal |
|
3104 { |
|
3105 ASSERT(![self _webView] || [self _isTopHTMLView]); |
|
3106 |
|
3107 Page *page = core([self _webView]); |
|
3108 |
|
3109 if (!page) |
|
3110 return NSDragOperationNone; |
|
3111 |
|
3112 if (page->dragController()->dragOperation() == DragOperationNone) |
|
3113 return NSDragOperationGeneric | NSDragOperationCopy; |
|
3114 |
|
3115 return (NSDragOperation)page->dragController()->dragOperation(); |
|
3116 } |
|
3117 |
|
3118 - (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenLoc |
|
3119 { |
|
3120 ASSERT(![self _webView] || [self _isTopHTMLView]); |
|
3121 |
|
3122 NSPoint windowImageLoc = [[self window] convertScreenToBase:screenLoc]; |
|
3123 NSPoint windowMouseLoc = windowImageLoc; |
|
3124 |
|
3125 if (Page* page = core([self _webView])) { |
|
3126 DragController* dragController = page->dragController(); |
|
3127 NSPoint windowMouseLoc = NSMakePoint(windowImageLoc.x + dragController->dragOffset().x(), windowImageLoc.y + dragController->dragOffset().y()); |
|
3128 } |
|
3129 |
|
3130 [[self _bridge] dragSourceMovedTo:windowMouseLoc]; |
|
3131 } |
|
3132 |
|
3133 - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation |
|
3134 { |
|
3135 ASSERT(![self _webView] || [self _isTopHTMLView]); |
|
3136 |
|
3137 NSPoint windowImageLoc = [[self window] convertScreenToBase:aPoint]; |
|
3138 NSPoint windowMouseLoc = windowImageLoc; |
|
3139 |
|
3140 if (Page* page = core([self _webView])) { |
|
3141 DragController* dragController = page->dragController(); |
|
3142 windowMouseLoc = NSMakePoint(windowImageLoc.x + dragController->dragOffset().x(), windowImageLoc.y + dragController->dragOffset().y()); |
|
3143 dragController->dragEnded(); |
|
3144 } |
|
3145 |
|
3146 [[self _bridge] dragSourceEndedAt:windowMouseLoc operation:operation]; |
|
3147 |
|
3148 // Prevent queued mouseDragged events from coming after the drag and fake mouseUp event. |
|
3149 _private->ignoringMouseDraggedEvents = YES; |
|
3150 |
|
3151 // Once the dragging machinery kicks in, we no longer get mouse drags or the up event. |
|
3152 // WebCore expects to get balanced down/up's, so we must fake up a mouseup. |
|
3153 NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp |
|
3154 location:windowMouseLoc |
|
3155 modifierFlags:[[NSApp currentEvent] modifierFlags] |
|
3156 timestamp:[NSDate timeIntervalSinceReferenceDate] |
|
3157 windowNumber:[[self window] windowNumber] |
|
3158 context:[[NSApp currentEvent] context] |
|
3159 eventNumber:0 clickCount:0 pressure:0]; |
|
3160 [self mouseUp:fakeEvent]; // This will also update the mouseover state. |
|
3161 } |
|
3162 |
|
3163 - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination |
|
3164 { |
|
3165 NSFileWrapper *wrapper = nil; |
|
3166 |
|
3167 if (WebCore::CachedResource* tiffResource = [self promisedDragTIFFDataSource]) { |
|
3168 |
|
3169 SharedBuffer *buffer = tiffResource->data(); |
|
3170 if (!buffer) |
|
3171 goto noPromisedData; |
|
3172 |
|
3173 NSData *data = buffer->createNSData(); |
|
3174 NSURLResponse *response = tiffResource->response().nsURLResponse(); |
|
3175 |
|
3176 wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:data] autorelease]; |
|
3177 [wrapper setPreferredFilename:[response suggestedFilename]]; |
|
3178 } |
|
3179 |
|
3180 noPromisedData: |
|
3181 |
|
3182 if (!wrapper) { |
|
3183 ASSERT(![self _webView] || [self _isTopHTMLView]); |
|
3184 Page* page = core([self _webView]); |
|
3185 |
|
3186 //If a load occurs midway through a drag, the view may be detached, which gives |
|
3187 //us no ability to get to the original Page, so we cannot access any drag state |
|
3188 //FIXME: is there a way to recover? |
|
3189 if (!page) |
|
3190 return nil; |
|
3191 |
|
3192 KURL imageURL = page->dragController()->draggingImageURL(); |
|
3193 ASSERT(!imageURL.isEmpty()); |
|
3194 |
|
3195 wrapper = [[self _dataSource] _fileWrapperForURL:imageURL.getNSURL()]; |
|
3196 } |
|
3197 |
|
3198 if (wrapper == nil) { |
|
3199 LOG_ERROR("Failed to create image file."); |
|
3200 return nil; |
|
3201 } |
|
3202 |
|
3203 // FIXME: Report an error if we fail to create a file. |
|
3204 NSString *path = [[dropDestination path] stringByAppendingPathComponent:[wrapper preferredFilename]]; |
|
3205 path = [[NSFileManager defaultManager] _webkit_pathWithUniqueFilenameForPath:path]; |
|
3206 if (![wrapper writeToFile:path atomically:NO updateFilenames:YES]) |
|
3207 LOG_ERROR("Failed to create image file via -[NSFileWrapper writeToFile:atomically:updateFilenames:]"); |
|
3208 |
|
3209 return [NSArray arrayWithObject:[path lastPathComponent]]; |
|
3210 } |
|
3211 |
|
3212 - (void)mouseUp:(NSEvent *)event |
|
3213 { |
|
3214 [self _setMouseDownEvent:nil]; |
|
3215 |
|
3216 NSInputManager *currentInputManager = [NSInputManager currentInputManager]; |
|
3217 if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event]) |
|
3218 return; |
|
3219 |
|
3220 [self retain]; |
|
3221 |
|
3222 [self _stopAutoscrollTimer]; |
|
3223 if (Frame* coreframe = core([self _frame])) |
|
3224 coreframe->eventHandler()->mouseUp(event); |
|
3225 [self _updateMouseoverWithFakeEvent]; |
|
3226 |
|
3227 [self release]; |
|
3228 } |
|
3229 |
|
3230 - (void)mouseMovedNotification:(NSNotification *)notification |
|
3231 { |
|
3232 [self _updateMouseoverWithEvent:[[notification userInfo] objectForKey:@"NSEvent"]]; |
|
3233 } |
|
3234 |
|
3235 // returning YES from this method is the way we tell AppKit that it is ok for this view |
|
3236 // to be in the key loop even when "tab to all controls" is not on. |
|
3237 - (BOOL)needsPanelToBecomeKey |
|
3238 { |
|
3239 return YES; |
|
3240 } |
|
3241 |
|
3242 - (BOOL)becomeFirstResponder |
|
3243 { |
|
3244 NSSelectionDirection direction = NSDirectSelection; |
|
3245 if (![[self _webView] _isPerformingProgrammaticFocus] && !_private->willBecomeFirstResponderForNodeFocus) |
|
3246 direction = [[self window] keyViewSelectionDirection]; |
|
3247 _private->willBecomeFirstResponderForNodeFocus = NO; |
|
3248 |
|
3249 [self _updateActiveState]; |
|
3250 [self _updateFontPanel]; |
|
3251 |
|
3252 Frame* frame = core([self _frame]); |
|
3253 if (!frame) |
|
3254 return YES; |
|
3255 |
|
3256 frame->editor()->setStartNewKillRingSequence(true); |
|
3257 |
|
3258 if (direction == NSDirectSelection) |
|
3259 return YES; |
|
3260 |
|
3261 Page* page = frame->page(); |
|
3262 if (!page) |
|
3263 return YES; |
|
3264 |
|
3265 page->focusController()->setFocusedFrame(frame); |
|
3266 if (Document* document = frame->document()) |
|
3267 document->setFocusedNode(0); |
|
3268 page->focusController()->setInitialFocus(direction == NSSelectingNext ? FocusDirectionForward : FocusDirectionBackward, |
|
3269 frame->eventHandler()->currentKeyboardEvent().get()); |
|
3270 return YES; |
|
3271 } |
|
3272 |
|
3273 - (BOOL)resignFirstResponder |
|
3274 { |
|
3275 BOOL resign = [super resignFirstResponder]; |
|
3276 if (resign) { |
|
3277 [_private->compController endRevertingChange:NO moveLeft:NO]; |
|
3278 _private->resigningFirstResponder = YES; |
|
3279 if (![self maintainsInactiveSelection]) { |
|
3280 [self deselectAll]; |
|
3281 if (![[self _webView] _isPerformingProgrammaticFocus]) |
|
3282 [self clearFocus]; |
|
3283 } |
|
3284 [self _updateActiveState]; |
|
3285 _private->resigningFirstResponder = NO; |
|
3286 _private->willBecomeFirstResponderForNodeFocus = NO; |
|
3287 } |
|
3288 return resign; |
|
3289 } |
|
3290 |
|
3291 - (void)setDataSource:(WebDataSource *)dataSource |
|
3292 { |
|
3293 ASSERT(dataSource); |
|
3294 if (_private->dataSource != dataSource) { |
|
3295 ASSERT(!_private->closed); |
|
3296 [dataSource retain]; |
|
3297 [_private->dataSource release]; |
|
3298 _private->dataSource = dataSource; |
|
3299 [_private->pluginController setDataSource:dataSource]; |
|
3300 [self addMouseMovedObserver]; |
|
3301 } |
|
3302 } |
|
3303 |
|
3304 - (void)dataSourceUpdated:(WebDataSource *)dataSource |
|
3305 { |
|
3306 } |
|
3307 |
|
3308 // This is an override of an NSControl method that wants to repaint the entire view when the window resigns/becomes |
|
3309 // key. WebHTMLView is an NSControl only because it hosts NSCells that are painted by WebCore's Aqua theme |
|
3310 // renderer (and those cells must be hosted by an enclosing NSControl in order to paint properly). |
|
3311 - (void)updateCell:(NSCell*)cell |
|
3312 { |
|
3313 } |
|
3314 |
|
3315 // Does setNeedsDisplay:NO as a side effect when printing is ending. |
|
3316 // pageWidth != 0 implies we will relayout to a new width |
|
3317 - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize |
|
3318 { |
|
3319 WebFrame *frame = [self _frame]; |
|
3320 NSArray *subframes = [frame childFrames]; |
|
3321 unsigned n = [subframes count]; |
|
3322 unsigned i; |
|
3323 for (i = 0; i != n; ++i) { |
|
3324 WebFrame *subframe = [subframes objectAtIndex:i]; |
|
3325 WebFrameView *frameView = [subframe frameView]; |
|
3326 if ([[subframe _dataSource] _isDocumentHTML]) { |
|
3327 [(WebHTMLView *)[frameView documentView] _setPrinting:printing minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:adjustViewSize]; |
|
3328 } |
|
3329 } |
|
3330 |
|
3331 if (printing != _private->printing) { |
|
3332 [_private->pageRects release]; |
|
3333 _private->pageRects = nil; |
|
3334 _private->printing = printing; |
|
3335 if (!printing) |
|
3336 _private->avoidingPrintOrphan = NO; |
|
3337 [self setNeedsToApplyStyles:YES]; |
|
3338 [self setNeedsLayout:YES]; |
|
3339 [self layoutToMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize]; |
|
3340 if (!printing) { |
|
3341 // Can't do this when starting printing or nested printing won't work, see 3491427. |
|
3342 [self setNeedsDisplay:NO]; |
|
3343 } |
|
3344 } |
|
3345 } |
|
3346 |
|
3347 - (BOOL)canPrintHeadersAndFooters |
|
3348 { |
|
3349 return YES; |
|
3350 } |
|
3351 |
|
3352 // This is needed for the case where the webview is embedded in the view that's being printed. |
|
3353 // It shouldn't be called when the webview is being printed directly. |
|
3354 - (void)adjustPageHeightNew:(float *)newBottom top:(float)oldTop bottom:(float)oldBottom limit:(float)bottomLimit |
|
3355 { |
|
3356 // This helps when we print as part of a larger print process. |
|
3357 // If the WebHTMLView itself is what we're printing, then we will never have to do this. |
|
3358 BOOL wasInPrintingMode = _private->printing; |
|
3359 if (!wasInPrintingMode) |
|
3360 [self _setPrinting:YES minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; |
|
3361 |
|
3362 [[self _bridge] adjustPageHeightNew:newBottom top:oldTop bottom:oldBottom limit:bottomLimit]; |
|
3363 |
|
3364 if (!wasInPrintingMode) { |
|
3365 NSPrintOperation *currenPrintOperation = [NSPrintOperation currentOperation]; |
|
3366 if (currenPrintOperation) |
|
3367 // delay _setPrinting:NO until back to main loop as this method may get called repeatedly |
|
3368 [self performSelector:@selector(_delayedEndPrintMode:) withObject:currenPrintOperation afterDelay:0]; |
|
3369 else |
|
3370 // not sure if this is actually ever invoked, it probably shouldn't be |
|
3371 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; |
|
3372 } |
|
3373 } |
|
3374 |
|
3375 - (float)_availablePaperWidthForPrintOperation:(NSPrintOperation *)printOperation |
|
3376 { |
|
3377 NSPrintInfo *printInfo = [printOperation printInfo]; |
|
3378 return [printInfo paperSize].width - [printInfo leftMargin] - [printInfo rightMargin]; |
|
3379 } |
|
3380 |
|
3381 - (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation |
|
3382 { |
|
3383 float viewWidth = NSWidth([self bounds]); |
|
3384 if (viewWidth < 1) { |
|
3385 LOG_ERROR("%@ has no width when printing", self); |
|
3386 return 1.0f; |
|
3387 } |
|
3388 |
|
3389 float userScaleFactor = [printOperation _web_pageSetupScaleFactor]; |
|
3390 float maxShrinkToFitScaleFactor = 1.0f / PrintingMaximumShrinkFactor; |
|
3391 float shrinkToFitScaleFactor = [self _availablePaperWidthForPrintOperation:printOperation]/viewWidth; |
|
3392 float shrinkToAvoidOrphan = _private->avoidingPrintOrphan ? (1.0f / PrintingOrphanShrinkAdjustment) : 1.0f; |
|
3393 return userScaleFactor * MAX(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor) * shrinkToAvoidOrphan; |
|
3394 } |
|
3395 |
|
3396 // FIXME 3491344: This is a secret AppKit-internal method that we need to override in order |
|
3397 // to get our shrink-to-fit to work with a custom pagination scheme. We can do this better |
|
3398 // if AppKit makes it SPI/API. |
|
3399 - (float)_provideTotalScaleFactorForPrintOperation:(NSPrintOperation *)printOperation |
|
3400 { |
|
3401 return [self _scaleFactorForPrintOperation:printOperation]; |
|
3402 } |
|
3403 |
|
3404 // This is used for Carbon printing. At some point we might want to make this public API. |
|
3405 - (void)setPageWidthForPrinting:(float)pageWidth |
|
3406 { |
|
3407 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:NO]; |
|
3408 [self _setPrinting:YES minimumPageWidth:pageWidth maximumPageWidth:pageWidth adjustViewSize:YES]; |
|
3409 } |
|
3410 |
|
3411 - (void)_endPrintMode |
|
3412 { |
|
3413 [self _setPrinting:NO minimumPageWidth:0.0f maximumPageWidth:0.0f adjustViewSize:YES]; |
|
3414 [[self window] setAutodisplay:YES]; |
|
3415 } |
|
3416 |
|
3417 - (void)_delayedEndPrintMode:(NSPrintOperation *)initiatingOperation |
|
3418 { |
|
3419 ASSERT_ARG(initiatingOperation, initiatingOperation != nil); |
|
3420 NSPrintOperation *currentOperation = [NSPrintOperation currentOperation]; |
|
3421 if (initiatingOperation == currentOperation) { |
|
3422 // The print operation is still underway. We don't expect this to ever happen, hence the assert, but we're |
|
3423 // being extra paranoid here since the printing code is so fragile. Delay the cleanup |
|
3424 // further. |
|
3425 ASSERT_NOT_REACHED(); |
|
3426 [self performSelector:@selector(_delayedEndPrintMode:) withObject:initiatingOperation afterDelay:0]; |
|
3427 } else if ([currentOperation view] == self) { |
|
3428 // A new print job has started, but it is printing the same WebHTMLView again. We don't expect |
|
3429 // this to ever happen, hence the assert, but we're being extra paranoid here since the printing code is so |
|
3430 // fragile. Do nothing, because we don't want to break the print job currently in progress, and |
|
3431 // the print job currently in progress is responsible for its own cleanup. |
|
3432 ASSERT_NOT_REACHED(); |
|
3433 } else { |
|
3434 // The print job that kicked off this delayed call has finished, and this view is not being |
|
3435 // printed again. We expect that no other print job has started. Since this delayed call wasn't |
|
3436 // cancelled, beginDocument and endDocument must not have been called, and we need to clean up |
|
3437 // the print mode here. |
|
3438 ASSERT(currentOperation == nil); |
|
3439 [self _endPrintMode]; |
|
3440 } |
|
3441 } |
|
3442 |
|
3443 // Return the number of pages available for printing |
|
3444 - (BOOL)knowsPageRange:(NSRangePointer)range |
|
3445 { |
|
3446 // Must do this explicit display here, because otherwise the view might redisplay while the print |
|
3447 // sheet was up, using printer fonts (and looking different). |
|
3448 [self displayIfNeeded]; |
|
3449 [[self window] setAutodisplay:NO]; |
|
3450 |
|
3451 // If we are a frameset just print with the layout we have onscreen, otherwise relayout |
|
3452 // according to the paper size |
|
3453 float minLayoutWidth = 0.0f; |
|
3454 float maxLayoutWidth = 0.0f; |
|
3455 Frame* frame = core([self _frame]); |
|
3456 if (!frame) |
|
3457 return NO; |
|
3458 if (!frame->isFrameSet()) { |
|
3459 float paperWidth = [self _availablePaperWidthForPrintOperation:[NSPrintOperation currentOperation]]; |
|
3460 minLayoutWidth = paperWidth * PrintingMinimumShrinkFactor; |
|
3461 maxLayoutWidth = paperWidth * PrintingMaximumShrinkFactor; |
|
3462 } |
|
3463 [self _setPrinting:YES minimumPageWidth:minLayoutWidth maximumPageWidth:maxLayoutWidth adjustViewSize:YES]; // will relayout |
|
3464 NSPrintOperation *printOperation = [NSPrintOperation currentOperation]; |
|
3465 // Certain types of errors, including invalid page ranges, can cause beginDocument and |
|
3466 // endDocument to be skipped after we've put ourselves in print mode (see 4145905). In those cases |
|
3467 // we need to get out of print mode without relying on any more callbacks from the printing mechanism. |
|
3468 // If we get as far as beginDocument without trouble, then this delayed request will be cancelled. |
|
3469 // If not cancelled, this delayed call will be invoked in the next pass through the main event loop, |
|
3470 // which is after beginDocument and endDocument would be called. |
|
3471 [self performSelector:@selector(_delayedEndPrintMode:) withObject:printOperation afterDelay:0]; |
|
3472 [[self _webView] _adjustPrintingMarginsForHeaderAndFooter]; |
|
3473 |
|
3474 // There is a theoretical chance that someone could do some drawing between here and endDocument, |
|
3475 // if something caused setNeedsDisplay after this point. If so, it's not a big tragedy, because |
|
3476 // you'd simply see the printer fonts on screen. As of this writing, this does not happen with Safari. |
|
3477 |
|
3478 range->location = 1; |
|
3479 float totalScaleFactor = [self _scaleFactorForPrintOperation:printOperation]; |
|
3480 float userScaleFactor = [printOperation _web_pageSetupScaleFactor]; |
|
3481 [_private->pageRects release]; |
|
3482 float fullPageHeight = floorf([self _calculatePrintHeight]/totalScaleFactor); |
|
3483 NSArray *newPageRects = [[self _bridge] computePageRectsWithPrintWidthScaleFactor:userScaleFactor |
|
3484 printHeight:fullPageHeight]; |
|
3485 |
|
3486 // AppKit gets all messed up if you give it a zero-length page count (see 3576334), so if we |
|
3487 // hit that case we'll pass along a degenerate 1 pixel square to print. This will print |
|
3488 // a blank page (with correct-looking header and footer if that option is on), which matches |
|
3489 // the behavior of IE and Camino at least. |
|
3490 if ([newPageRects count] == 0) |
|
3491 newPageRects = [NSArray arrayWithObject:[NSValue valueWithRect:NSMakeRect(0, 0, 1, 1)]]; |
|
3492 else if ([newPageRects count] > 1) { |
|
3493 // If the last page is a short orphan, try adjusting the print height slightly to see if this will squeeze the |
|
3494 // content onto one fewer page. If it does, use the adjusted scale. If not, use the original scale. |
|
3495 float lastPageHeight = NSHeight([[newPageRects lastObject] rectValue]); |
|
3496 if (lastPageHeight/fullPageHeight < LastPrintedPageOrphanRatio) { |
|
3497 NSArray *adjustedPageRects = [[self _bridge] computePageRectsWithPrintWidthScaleFactor:userScaleFactor |
|
3498 printHeight:fullPageHeight*PrintingOrphanShrinkAdjustment]; |
|
3499 // Use the adjusted rects only if the page count went down |
|
3500 if ([adjustedPageRects count] < [newPageRects count]) { |
|
3501 newPageRects = adjustedPageRects; |
|
3502 _private->avoidingPrintOrphan = YES; |
|
3503 } |
|
3504 } |
|
3505 } |
|
3506 |
|
3507 _private->pageRects = [newPageRects retain]; |
|
3508 |
|
3509 range->length = [_private->pageRects count]; |
|
3510 |
|
3511 return YES; |
|
3512 } |
|
3513 |
|
3514 // Return the drawing rectangle for a particular page number |
|
3515 - (NSRect)rectForPage:(int)page |
|
3516 { |
|
3517 return [[_private->pageRects objectAtIndex:page - 1] rectValue]; |
|
3518 } |
|
3519 |
|
3520 - (void)drawPageBorderWithSize:(NSSize)borderSize |
|
3521 { |
|
3522 ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize])); |
|
3523 [[self _webView] _drawHeaderAndFooter]; |
|
3524 } |
|
3525 |
|
3526 - (void)beginDocument |
|
3527 { |
|
3528 NS_DURING |
|
3529 // From now on we'll get a chance to call _endPrintMode in either beginDocument or |
|
3530 // endDocument, so we can cancel the "just in case" pending call. |
|
3531 [NSObject cancelPreviousPerformRequestsWithTarget:self |
|
3532 selector:@selector(_delayedEndPrintMode:) |
|
3533 object:[NSPrintOperation currentOperation]]; |
|
3534 [super beginDocument]; |
|
3535 NS_HANDLER |
|
3536 // Exception during [super beginDocument] means that endDocument will not get called, |
|
3537 // so we need to clean up our "print mode" here. |
|
3538 [self _endPrintMode]; |
|
3539 NS_ENDHANDLER |
|
3540 } |
|
3541 |
|
3542 - (void)endDocument |
|
3543 { |
|
3544 [super endDocument]; |
|
3545 // Note sadly at this point [NSGraphicsContext currentContextDrawingToScreen] is still NO |
|
3546 [self _endPrintMode]; |
|
3547 } |
|
3548 |
|
3549 - (void)keyDown:(NSEvent *)event |
|
3550 { |
|
3551 RetainPtr<WebHTMLView> selfProtector = self; |
|
3552 BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); |
|
3553 |
|
3554 BOOL callSuper = NO; |
|
3555 |
|
3556 [_private->keyDownEvent release]; |
|
3557 _private->keyDownEvent = [event retain]; |
|
3558 |
|
3559 BOOL completionPopupWasOpen = _private->compController && [_private->compController popupWindowIsOpen]; |
|
3560 if (!eventWasSentToWebCore && core([self _frame])->eventHandler()->keyEvent(event)) { |
|
3561 // WebCore processed a key event, bail on any preexisting complete: UI |
|
3562 if (completionPopupWasOpen) |
|
3563 [_private->compController endRevertingChange:YES moveLeft:NO]; |
|
3564 } else if (!_private->compController || ![_private->compController filterKeyDown:event]) { |
|
3565 // Not consumed by complete: popup window |
|
3566 [_private->compController endRevertingChange:YES moveLeft:NO]; |
|
3567 callSuper = YES; |
|
3568 } |
|
3569 if (callSuper) |
|
3570 [super keyDown:event]; |
|
3571 else |
|
3572 [NSCursor setHiddenUntilMouseMoves:YES]; |
|
3573 } |
|
3574 |
|
3575 - (void)keyUp:(NSEvent *)event |
|
3576 { |
|
3577 BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); |
|
3578 |
|
3579 [self retain]; |
|
3580 if (eventWasSentToWebCore || !core([self _frame])->eventHandler()->keyEvent(event)) |
|
3581 [super keyUp:event]; |
|
3582 [self release]; |
|
3583 } |
|
3584 |
|
3585 - (id)accessibilityAttributeValue:(NSString*)attributeName |
|
3586 { |
|
3587 if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) { |
|
3588 id accTree = [[self _bridge] accessibilityTree]; |
|
3589 if (accTree) |
|
3590 return [NSArray arrayWithObject:accTree]; |
|
3591 return nil; |
|
3592 } |
|
3593 return [super accessibilityAttributeValue:attributeName]; |
|
3594 } |
|
3595 |
|
3596 - (id)accessibilityFocusedUIElement |
|
3597 { |
|
3598 id accTree = [[self _bridge] accessibilityTree]; |
|
3599 if (accTree) |
|
3600 return [accTree accessibilityFocusedUIElement]; |
|
3601 return self; |
|
3602 } |
|
3603 |
|
3604 - (id)accessibilityHitTest:(NSPoint)point |
|
3605 { |
|
3606 id accTree = [[self _bridge] accessibilityTree]; |
|
3607 if (accTree) { |
|
3608 NSPoint windowCoord = [[self window] convertScreenToBase:point]; |
|
3609 return [accTree accessibilityHitTest:[self convertPoint:windowCoord fromView:nil]]; |
|
3610 } |
|
3611 return self; |
|
3612 } |
|
3613 |
|
3614 - (id)_accessibilityParentForSubview:(NSView *)subview |
|
3615 { |
|
3616 id accTree = [[self _bridge] accessibilityTree]; |
|
3617 if (!accTree) |
|
3618 return self; |
|
3619 id parent = [accTree _accessibilityParentForSubview:subview]; |
|
3620 if (!parent) |
|
3621 return self; |
|
3622 return parent; |
|
3623 } |
|
3624 |
|
3625 - (void)centerSelectionInVisibleArea:(id)sender |
|
3626 { |
|
3627 COMMAND_PROLOGUE |
|
3628 |
|
3629 if (Frame* coreFrame = core([self _frame])) |
|
3630 coreFrame->revealSelection(RenderLayer::gAlignCenterAlways); |
|
3631 } |
|
3632 |
|
3633 - (void)pageUp:(id)sender |
|
3634 { |
|
3635 COMMAND_PROLOGUE |
|
3636 |
|
3637 WebFrameView *frameView = [self _frameView]; |
|
3638 if (!frameView) |
|
3639 return; |
|
3640 if ([self _canAlterCurrentSelection]) |
|
3641 [[self _bridge] alterCurrentSelection:SelectionController::MOVE verticalDistance:-[frameView _verticalPageScrollDistance]]; |
|
3642 } |
|
3643 |
|
3644 - (void)pageDown:(id)sender |
|
3645 { |
|
3646 COMMAND_PROLOGUE |
|
3647 |
|
3648 WebFrameView *frameView = [self _frameView]; |
|
3649 if (!frameView) |
|
3650 return; |
|
3651 if ([self _canAlterCurrentSelection]) |
|
3652 [[self _bridge] alterCurrentSelection:SelectionController::MOVE verticalDistance:[frameView _verticalPageScrollDistance]]; |
|
3653 } |
|
3654 |
|
3655 - (void)pageUpAndModifySelection:(id)sender |
|
3656 { |
|
3657 COMMAND_PROLOGUE |
|
3658 |
|
3659 WebFrameView *frameView = [self _frameView]; |
|
3660 if (!frameView) |
|
3661 return; |
|
3662 if ([self _canAlterCurrentSelection]) |
|
3663 [[self _bridge] alterCurrentSelection:SelectionController::EXTEND verticalDistance:-[frameView _verticalPageScrollDistance]]; |
|
3664 } |
|
3665 |
|
3666 - (void)pageDownAndModifySelection:(id)sender |
|
3667 { |
|
3668 COMMAND_PROLOGUE |
|
3669 |
|
3670 WebFrameView *frameView = [self _frameView]; |
|
3671 if (frameView == nil) |
|
3672 return; |
|
3673 if ([self _canAlterCurrentSelection]) |
|
3674 [[self _bridge] alterCurrentSelection:SelectionController::EXTEND verticalDistance:[frameView _verticalPageScrollDistance]]; |
|
3675 } |
|
3676 |
|
3677 - (void)_expandSelectionToGranularity:(TextGranularity)granularity |
|
3678 { |
|
3679 if (![self _canAlterCurrentSelection]) |
|
3680 return; |
|
3681 |
|
3682 Frame* coreFrame = core([self _frame]); |
|
3683 if (!coreFrame || !coreFrame->selectionController()->isCaretOrRange()) |
|
3684 return; |
|
3685 |
|
3686 // NOTE: The enums *must* match the very similar ones declared in SelectionController.h |
|
3687 Selection selection(coreFrame->selectionController()->selection()); |
|
3688 selection.expandUsingGranularity(static_cast<TextGranularity>(granularity)); |
|
3689 |
|
3690 RefPtr<Range> range = selection.toRange(); |
|
3691 if (!range) |
|
3692 return; |
|
3693 |
|
3694 DOMRange *domRange = kit(range.get()); |
|
3695 |
|
3696 if ([domRange collapsed]) |
|
3697 return; |
|
3698 |
|
3699 EAffinity affinity = coreFrame->selectionController()->affinity(); |
|
3700 WebView *webView = [self _webView]; |
|
3701 if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] toDOMRange:domRange affinity:kit(affinity) stillSelecting:NO]) { |
|
3702 ExceptionCode ec = 0; |
|
3703 coreFrame->selectionController()->setSelectedRange(range.get(), affinity, true, ec); |
|
3704 } |
|
3705 } |
|
3706 |
|
3707 - (void)selectParagraph:(id)sender |
|
3708 { |
|
3709 COMMAND_PROLOGUE |
|
3710 |
|
3711 [self _expandSelectionToGranularity:ParagraphGranularity]; |
|
3712 } |
|
3713 |
|
3714 - (void)selectLine:(id)sender |
|
3715 { |
|
3716 COMMAND_PROLOGUE |
|
3717 |
|
3718 [self _expandSelectionToGranularity:LineGranularity]; |
|
3719 } |
|
3720 |
|
3721 - (void)selectSentence:(id)sender |
|
3722 { |
|
3723 COMMAND_PROLOGUE |
|
3724 |
|
3725 [self _expandSelectionToGranularity:SentenceGranularity]; |
|
3726 } |
|
3727 |
|
3728 - (void)selectWord:(id)sender |
|
3729 { |
|
3730 COMMAND_PROLOGUE |
|
3731 |
|
3732 [self _expandSelectionToGranularity:WordGranularity]; |
|
3733 } |
|
3734 |
|
3735 - (void)delete:(id)sender |
|
3736 { |
|
3737 COMMAND_PROLOGUE |
|
3738 |
|
3739 if (Frame* coreFrame = core([self _frame])) |
|
3740 coreFrame->editor()->performDelete(); |
|
3741 } |
|
3742 |
|
3743 - (NSData *)_selectionStartFontAttributesAsRTF |
|
3744 { |
|
3745 NSAttributedString *string = [[NSAttributedString alloc] initWithString:@"x" |
|
3746 attributes:core([self _frame])->fontAttributesForSelectionStart()]; |
|
3747 NSData *data = [string RTFFromRange:NSMakeRange(0, [string length]) documentAttributes:nil]; |
|
3748 [string release]; |
|
3749 return data; |
|
3750 } |
|
3751 |
|
3752 - (NSDictionary *)_fontAttributesFromFontPasteboard |
|
3753 { |
|
3754 NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard]; |
|
3755 if (fontPasteboard == nil) |
|
3756 return nil; |
|
3757 NSData *data = [fontPasteboard dataForType:NSFontPboardType]; |
|
3758 if (data == nil || [data length] == 0) |
|
3759 return nil; |
|
3760 // NSTextView does something more efficient by parsing the attributes only, but that's not available in API. |
|
3761 NSAttributedString *string = [[[NSAttributedString alloc] initWithRTF:data documentAttributes:NULL] autorelease]; |
|
3762 if (string == nil || [string length] == 0) |
|
3763 return nil; |
|
3764 return [string fontAttributesInRange:NSMakeRange(0, 1)]; |
|
3765 } |
|
3766 |
|
3767 - (DOMCSSStyleDeclaration *)_emptyStyle |
|
3768 { |
|
3769 return [[[self _frame] DOMDocument] createCSSStyleDeclaration]; |
|
3770 } |
|
3771 |
|
3772 - (NSString *)_colorAsString:(NSColor *)color |
|
3773 { |
|
3774 NSColor *rgbColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; |
|
3775 // FIXME: If color is non-nil and rgbColor is nil, that means we got some kind |
|
3776 // of fancy color that can't be converted to RGB. Changing that to "transparent" |
|
3777 // might not be great, but it's probably OK. |
|
3778 if (rgbColor == nil) |
|
3779 return @"transparent"; |
|
3780 float r = [rgbColor redComponent]; |
|
3781 float g = [rgbColor greenComponent]; |
|
3782 float b = [rgbColor blueComponent]; |
|
3783 float a = [rgbColor alphaComponent]; |
|
3784 if (a == 0) |
|
3785 return @"transparent"; |
|
3786 if (r == 0 && g == 0 && b == 0 && a == 1) |
|
3787 return @"black"; |
|
3788 if (r == 1 && g == 1 && b == 1 && a == 1) |
|
3789 return @"white"; |
|
3790 // FIXME: Lots more named colors. Maybe we could use the table in WebCore? |
|
3791 if (a == 1) |
|
3792 return [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", r * 255, g * 255, b * 255]; |
|
3793 return [NSString stringWithFormat:@"rgba(%.0f,%.0f,%.0f,%f)", r * 255, g * 255, b * 255, a]; |
|
3794 } |
|
3795 |
|
3796 - (NSString *)_shadowAsString:(NSShadow *)shadow |
|
3797 { |
|
3798 if (shadow == nil) |
|
3799 return @"none"; |
|
3800 NSSize offset = [shadow shadowOffset]; |
|
3801 float blurRadius = [shadow shadowBlurRadius]; |
|
3802 if (offset.width == 0 && offset.height == 0 && blurRadius == 0) |
|
3803 return @"none"; |
|
3804 NSColor *color = [shadow shadowColor]; |
|
3805 if (color == nil) |
|
3806 return @"none"; |
|
3807 // FIXME: Handle non-integral values here? |
|
3808 if (blurRadius == 0) |
|
3809 return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height]; |
|
3810 return [NSString stringWithFormat:@"%@ %.0fpx %.0fpx %.0fpx", [self _colorAsString:color], offset.width, offset.height, blurRadius]; |
|
3811 } |
|
3812 |
|
3813 - (DOMCSSStyleDeclaration *)_styleFromFontAttributes:(NSDictionary *)dictionary |
|
3814 { |
|
3815 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
3816 |
|
3817 NSColor *color = [dictionary objectForKey:NSBackgroundColorAttributeName]; |
|
3818 [style setBackgroundColor:[self _colorAsString:color]]; |
|
3819 |
|
3820 NSFont *font = [dictionary objectForKey:NSFontAttributeName]; |
|
3821 if (font == nil) { |
|
3822 [style setFontFamily:@"Helvetica"]; |
|
3823 [style setFontSize:@"12px"]; |
|
3824 [style setFontWeight:@"normal"]; |
|
3825 [style setFontStyle:@"normal"]; |
|
3826 } else { |
|
3827 NSFontManager *fm = [NSFontManager sharedFontManager]; |
|
3828 // FIXME: Need more sophisticated escaping code if we want to handle family names |
|
3829 // with characters like single quote or backslash in their names. |
|
3830 [style setFontFamily:[NSString stringWithFormat:@"'%@'", [font familyName]]]; |
|
3831 [style setFontSize:[NSString stringWithFormat:@"%0.fpx", [font pointSize]]]; |
|
3832 if ([fm weightOfFont:font] >= MIN_BOLD_WEIGHT) |
|
3833 [style setFontWeight:@"bold"]; |
|
3834 else |
|
3835 [style setFontWeight:@"normal"]; |
|
3836 if (([fm traitsOfFont:font] & NSItalicFontMask) != 0) |
|
3837 [style setFontStyle:@"italic"]; |
|
3838 else |
|
3839 [style setFontStyle:@"normal"]; |
|
3840 } |
|
3841 |
|
3842 color = [dictionary objectForKey:NSForegroundColorAttributeName]; |
|
3843 [style setColor:color ? [self _colorAsString:color] : (NSString *)@"black"]; |
|
3844 |
|
3845 NSShadow *shadow = [dictionary objectForKey:NSShadowAttributeName]; |
|
3846 [style setTextShadow:[self _shadowAsString:shadow]]; |
|
3847 |
|
3848 int strikethroughInt = [[dictionary objectForKey:NSStrikethroughStyleAttributeName] intValue]; |
|
3849 |
|
3850 int superscriptInt = [[dictionary objectForKey:NSSuperscriptAttributeName] intValue]; |
|
3851 if (superscriptInt > 0) |
|
3852 [style setVerticalAlign:@"super"]; |
|
3853 else if (superscriptInt < 0) |
|
3854 [style setVerticalAlign:@"sub"]; |
|
3855 else |
|
3856 [style setVerticalAlign:@"baseline"]; |
|
3857 int underlineInt = [[dictionary objectForKey:NSUnderlineStyleAttributeName] intValue]; |
|
3858 // FIXME: Underline wins here if we have both (see bug 3790443). |
|
3859 if (strikethroughInt == NSUnderlineStyleNone && underlineInt == NSUnderlineStyleNone) |
|
3860 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; |
|
3861 else if (underlineInt == NSUnderlineStyleNone) |
|
3862 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"line-through" priority:@""]; |
|
3863 else |
|
3864 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""]; |
|
3865 |
|
3866 return style; |
|
3867 } |
|
3868 |
|
3869 - (void)_applyStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction |
|
3870 { |
|
3871 if (Frame* coreFrame = core([self _frame])) |
|
3872 coreFrame->editor()->applyStyleToSelection(core(style), undoAction); |
|
3873 } |
|
3874 |
|
3875 - (void)_applyParagraphStyleToSelection:(DOMCSSStyleDeclaration *)style withUndoAction:(EditAction)undoAction |
|
3876 { |
|
3877 if (Frame* coreFrame = core([self _frame])) |
|
3878 coreFrame->editor()->applyParagraphStyleToSelection(core(style), undoAction); |
|
3879 } |
|
3880 |
|
3881 - (void)_toggleBold |
|
3882 { |
|
3883 if (Frame* coreFrame = core([self _frame])) |
|
3884 coreFrame->editor()->execCommand("ToggleBold"); |
|
3885 } |
|
3886 |
|
3887 - (void)_toggleItalic |
|
3888 { |
|
3889 if (Frame* coreFrame = core([self _frame])) |
|
3890 coreFrame->editor()->execCommand("ToggleItalic"); |
|
3891 } |
|
3892 |
|
3893 - (BOOL)_handleStyleKeyEquivalent:(NSEvent *)event |
|
3894 { |
|
3895 ASSERT([self _webView]); |
|
3896 if (![[[self _webView] preferences] respectStandardStyleKeyEquivalents]) |
|
3897 return NO; |
|
3898 |
|
3899 if (![self _canEdit]) |
|
3900 return NO; |
|
3901 |
|
3902 if (([event modifierFlags] & NSDeviceIndependentModifierFlagsMask) != NSCommandKeyMask) |
|
3903 return NO; |
|
3904 |
|
3905 NSString *string = [event characters]; |
|
3906 if ([string caseInsensitiveCompare:@"b"] == NSOrderedSame) { |
|
3907 [self _toggleBold]; |
|
3908 return YES; |
|
3909 } |
|
3910 if ([string caseInsensitiveCompare:@"i"] == NSOrderedSame) { |
|
3911 [self _toggleItalic]; |
|
3912 return YES; |
|
3913 } |
|
3914 |
|
3915 return NO; |
|
3916 } |
|
3917 |
|
3918 - (BOOL)performKeyEquivalent:(NSEvent *)event |
|
3919 { |
|
3920 if ([self _handleStyleKeyEquivalent:event]) |
|
3921 return YES; |
|
3922 |
|
3923 BOOL eventWasSentToWebCore = (_private->keyDownEvent == event); |
|
3924 BOOL ret = NO; |
|
3925 |
|
3926 [_private->keyDownEvent release]; |
|
3927 _private->keyDownEvent = [event retain]; |
|
3928 |
|
3929 [self retain]; |
|
3930 |
|
3931 // Pass command-key combos through WebCore if there is a key binding available for |
|
3932 // this event. This lets web pages have a crack at intercepting command-modified keypresses. |
|
3933 // But don't do it if we have already handled the event. |
|
3934 // Pressing Esc results in a fake event being sent - don't pass it to WebCore. |
|
3935 if (!eventWasSentToWebCore && event == [NSApp currentEvent] && self == [[self window] firstResponder]) |
|
3936 if (Frame* frame = core([self _frame])) |
|
3937 ret = frame->eventHandler()->keyEvent(event); |
|
3938 |
|
3939 if (!ret) |
|
3940 ret = [super performKeyEquivalent:event]; |
|
3941 |
|
3942 [self release]; |
|
3943 |
|
3944 return ret; |
|
3945 } |
|
3946 |
|
3947 - (void)copyFont:(id)sender |
|
3948 { |
|
3949 COMMAND_PROLOGUE |
|
3950 |
|
3951 // Put RTF with font attributes on the pasteboard. |
|
3952 // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font. |
|
3953 NSPasteboard *fontPasteboard = [NSPasteboard pasteboardWithName:NSFontPboard]; |
|
3954 [fontPasteboard declareTypes:[NSArray arrayWithObject:NSFontPboardType] owner:nil]; |
|
3955 [fontPasteboard setData:[self _selectionStartFontAttributesAsRTF] forType:NSFontPboardType]; |
|
3956 } |
|
3957 |
|
3958 - (void)pasteFont:(id)sender |
|
3959 { |
|
3960 COMMAND_PROLOGUE |
|
3961 |
|
3962 // Read RTF with font attributes from the pasteboard. |
|
3963 // Maybe later we should add a pasteboard type that contains CSS text for "native" copy and paste font. |
|
3964 [self _applyStyleToSelection:[self _styleFromFontAttributes:[self _fontAttributesFromFontPasteboard]] withUndoAction:EditActionPasteFont]; |
|
3965 } |
|
3966 |
|
3967 - (void)pasteAsRichText:(id)sender |
|
3968 { |
|
3969 COMMAND_PROLOGUE |
|
3970 |
|
3971 // Since rich text always beats plain text when both are on the pasteboard, it's not |
|
3972 // clear how this is different from plain old paste. |
|
3973 [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:NO]; |
|
3974 } |
|
3975 |
|
3976 - (NSFont *)_originalFontA |
|
3977 { |
|
3978 return [[NSFontManager sharedFontManager] fontWithFamily:@"Helvetica" traits:0 weight:STANDARD_WEIGHT size:10.0f]; |
|
3979 } |
|
3980 |
|
3981 - (NSFont *)_originalFontB |
|
3982 { |
|
3983 return [[NSFontManager sharedFontManager] fontWithFamily:@"Times" traits:(NSBoldFontMask | NSItalicFontMask) weight:STANDARD_BOLD_WEIGHT size:12.0f]; |
|
3984 } |
|
3985 |
|
3986 - (void)_addToStyle:(DOMCSSStyleDeclaration *)style fontA:(NSFont *)a fontB:(NSFont *)b |
|
3987 { |
|
3988 // Since there's no way to directly ask NSFontManager what style change it's going to do |
|
3989 // we instead pass two "specimen" fonts to it and let it change them. We then deduce what |
|
3990 // style change it was doing by looking at what happened to each of the two fonts. |
|
3991 // So if it was making the text bold, both fonts will be bold after the fact. |
|
3992 |
|
3993 if (a == nil || b == nil) |
|
3994 return; |
|
3995 |
|
3996 NSFontManager *fm = [NSFontManager sharedFontManager]; |
|
3997 |
|
3998 NSFont *oa = [self _originalFontA]; |
|
3999 |
|
4000 NSString *aFamilyName = [a familyName]; |
|
4001 NSString *bFamilyName = [b familyName]; |
|
4002 |
|
4003 int aPointSize = (int)[a pointSize]; |
|
4004 int bPointSize = (int)[b pointSize]; |
|
4005 |
|
4006 int aWeight = [fm weightOfFont:a]; |
|
4007 int bWeight = [fm weightOfFont:b]; |
|
4008 |
|
4009 BOOL aIsBold = aWeight >= MIN_BOLD_WEIGHT; |
|
4010 |
|
4011 BOOL aIsItalic = ([fm traitsOfFont:a] & NSItalicFontMask) != 0; |
|
4012 BOOL bIsItalic = ([fm traitsOfFont:b] & NSItalicFontMask) != 0; |
|
4013 |
|
4014 if ([aFamilyName isEqualToString:bFamilyName]) { |
|
4015 NSString *familyNameForCSS = aFamilyName; |
|
4016 |
|
4017 // The family name may not be specific enough to get us the font specified. |
|
4018 // In some cases, the only way to get exactly what we are looking for is to use |
|
4019 // the Postscript name. |
|
4020 |
|
4021 // Find the font the same way the rendering code would later if it encountered this CSS. |
|
4022 NSFontTraitMask traits = 0; |
|
4023 if (aIsBold) |
|
4024 traits |= NSBoldFontMask; |
|
4025 if (aIsItalic) |
|
4026 traits |= NSItalicFontMask; |
|
4027 NSFont *foundFont = WebCoreFindFont(aFamilyName, traits, aPointSize); |
|
4028 |
|
4029 // If we don't find a font with the same Postscript name, then we'll have to use the |
|
4030 // Postscript name to make the CSS specific enough. |
|
4031 if (![[foundFont fontName] isEqualToString:[a fontName]]) { |
|
4032 familyNameForCSS = [a fontName]; |
|
4033 } |
|
4034 |
|
4035 // FIXME: Need more sophisticated escaping code if we want to handle family names |
|
4036 // with characters like single quote or backslash in their names. |
|
4037 [style setFontFamily:[NSString stringWithFormat:@"'%@'", familyNameForCSS]]; |
|
4038 } |
|
4039 |
|
4040 int soa = (int)[oa pointSize]; |
|
4041 if (aPointSize == bPointSize) |
|
4042 [style setFontSize:[NSString stringWithFormat:@"%dpx", aPointSize]]; |
|
4043 else if (aPointSize < soa) |
|
4044 [style _setFontSizeDelta:@"-1px"]; |
|
4045 else if (aPointSize > soa) |
|
4046 [style _setFontSizeDelta:@"1px"]; |
|
4047 |
|
4048 if (aWeight == bWeight) |
|
4049 [style setFontWeight:aIsBold ? @"bold" : @"normal"]; |
|
4050 |
|
4051 if (aIsItalic == bIsItalic) |
|
4052 [style setFontStyle:aIsItalic ? @"italic" : @"normal"]; |
|
4053 } |
|
4054 |
|
4055 - (DOMCSSStyleDeclaration *)_styleFromFontManagerOperation |
|
4056 { |
|
4057 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
4058 |
|
4059 NSFontManager *fm = [NSFontManager sharedFontManager]; |
|
4060 |
|
4061 NSFont *oa = [self _originalFontA]; |
|
4062 NSFont *ob = [self _originalFontB]; |
|
4063 [self _addToStyle:style fontA:[fm convertFont:oa] fontB:[fm convertFont:ob]]; |
|
4064 |
|
4065 return style; |
|
4066 } |
|
4067 |
|
4068 - (void)changeFont:(id)sender |
|
4069 { |
|
4070 COMMAND_PROLOGUE |
|
4071 |
|
4072 [self _applyStyleToSelection:[self _styleFromFontManagerOperation] withUndoAction:EditActionSetFont]; |
|
4073 } |
|
4074 |
|
4075 - (DOMCSSStyleDeclaration *)_styleForAttributeChange:(id)sender |
|
4076 { |
|
4077 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
4078 |
|
4079 NSShadow *shadow = [[NSShadow alloc] init]; |
|
4080 [shadow setShadowOffset:NSMakeSize(1, 1)]; |
|
4081 |
|
4082 NSDictionary *oa = [NSDictionary dictionaryWithObjectsAndKeys: |
|
4083 [self _originalFontA], NSFontAttributeName, |
|
4084 nil]; |
|
4085 NSDictionary *ob = [NSDictionary dictionaryWithObjectsAndKeys: |
|
4086 [NSColor blackColor], NSBackgroundColorAttributeName, |
|
4087 [self _originalFontB], NSFontAttributeName, |
|
4088 [NSColor whiteColor], NSForegroundColorAttributeName, |
|
4089 shadow, NSShadowAttributeName, |
|
4090 [NSNumber numberWithInt:NSUnderlineStyleSingle], NSStrikethroughStyleAttributeName, |
|
4091 [NSNumber numberWithInt:1], NSSuperscriptAttributeName, |
|
4092 [NSNumber numberWithInt:NSUnderlineStyleSingle], NSUnderlineStyleAttributeName, |
|
4093 nil]; |
|
4094 |
|
4095 [shadow release]; |
|
4096 |
|
4097 #if 0 |
|
4098 |
|
4099 NSObliquenessAttributeName /* float; skew to be applied to glyphs, default 0: no skew */ |
|
4100 // font-style, but that is just an on-off switch |
|
4101 |
|
4102 NSExpansionAttributeName /* float; log of expansion factor to be applied to glyphs, default 0: no expansion */ |
|
4103 // font-stretch? |
|
4104 |
|
4105 NSKernAttributeName /* float, amount to modify default kerning, if 0, kerning off */ |
|
4106 // letter-spacing? probably not good enough |
|
4107 |
|
4108 NSUnderlineColorAttributeName /* NSColor, default nil: same as foreground color */ |
|
4109 NSStrikethroughColorAttributeName /* NSColor, default nil: same as foreground color */ |
|
4110 // text-decoration-color? |
|
4111 |
|
4112 NSLigatureAttributeName /* int, default 1: default ligatures, 0: no ligatures, 2: all ligatures */ |
|
4113 NSBaselineOffsetAttributeName /* float, in points; offset from baseline, default 0 */ |
|
4114 NSStrokeWidthAttributeName /* float, in percent of font point size, default 0: no stroke; positive for stroke alone, negative for stroke and fill (a typical value for outlined text would be 3.0) */ |
|
4115 NSStrokeColorAttributeName /* NSColor, default nil: same as foreground color */ |
|
4116 // need extensions? |
|
4117 |
|
4118 #endif |
|
4119 |
|
4120 NSDictionary *a = [sender convertAttributes:oa]; |
|
4121 NSDictionary *b = [sender convertAttributes:ob]; |
|
4122 |
|
4123 NSColor *ca = [a objectForKey:NSBackgroundColorAttributeName]; |
|
4124 NSColor *cb = [b objectForKey:NSBackgroundColorAttributeName]; |
|
4125 if (ca == cb) { |
|
4126 [style setBackgroundColor:[self _colorAsString:ca]]; |
|
4127 } |
|
4128 |
|
4129 [self _addToStyle:style fontA:[a objectForKey:NSFontAttributeName] fontB:[b objectForKey:NSFontAttributeName]]; |
|
4130 |
|
4131 ca = [a objectForKey:NSForegroundColorAttributeName]; |
|
4132 cb = [b objectForKey:NSForegroundColorAttributeName]; |
|
4133 if (ca == cb) { |
|
4134 [style setColor:[self _colorAsString:ca]]; |
|
4135 } |
|
4136 |
|
4137 NSShadow *sha = [a objectForKey:NSShadowAttributeName]; |
|
4138 if (sha) |
|
4139 [style setTextShadow:[self _shadowAsString:sha]]; |
|
4140 else if ([b objectForKey:NSShadowAttributeName] == nil) |
|
4141 [style setTextShadow:@"none"]; |
|
4142 |
|
4143 int sa = [[a objectForKey:NSStrikethroughStyleAttributeName] intValue]; |
|
4144 int sb = [[b objectForKey:NSStrikethroughStyleAttributeName] intValue]; |
|
4145 if (sa == sb) { |
|
4146 if (sa == NSUnderlineStyleNone) |
|
4147 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; |
|
4148 // we really mean "no line-through" rather than "none" |
|
4149 else |
|
4150 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"line-through" priority:@""]; |
|
4151 // we really mean "add line-through" rather than "line-through" |
|
4152 } |
|
4153 |
|
4154 sa = [[a objectForKey:NSSuperscriptAttributeName] intValue]; |
|
4155 sb = [[b objectForKey:NSSuperscriptAttributeName] intValue]; |
|
4156 if (sa == sb) { |
|
4157 if (sa > 0) |
|
4158 [style setVerticalAlign:@"super"]; |
|
4159 else if (sa < 0) |
|
4160 [style setVerticalAlign:@"sub"]; |
|
4161 else |
|
4162 [style setVerticalAlign:@"baseline"]; |
|
4163 } |
|
4164 |
|
4165 int ua = [[a objectForKey:NSUnderlineStyleAttributeName] intValue]; |
|
4166 int ub = [[b objectForKey:NSUnderlineStyleAttributeName] intValue]; |
|
4167 if (ua == ub) { |
|
4168 if (ua == NSUnderlineStyleNone) |
|
4169 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; |
|
4170 // we really mean "no underline" rather than "none" |
|
4171 else |
|
4172 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""]; |
|
4173 // we really mean "add underline" rather than "underline" |
|
4174 } |
|
4175 |
|
4176 return style; |
|
4177 } |
|
4178 |
|
4179 - (void)changeAttributes:(id)sender |
|
4180 { |
|
4181 COMMAND_PROLOGUE |
|
4182 |
|
4183 [self _applyStyleToSelection:[self _styleForAttributeChange:sender] withUndoAction:EditActionChangeAttributes]; |
|
4184 } |
|
4185 |
|
4186 - (DOMCSSStyleDeclaration *)_styleFromColorPanelWithSelector:(SEL)selector |
|
4187 { |
|
4188 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
4189 |
|
4190 ASSERT([style respondsToSelector:selector]); |
|
4191 [style performSelector:selector withObject:[self _colorAsString:[[NSColorPanel sharedColorPanel] color]]]; |
|
4192 |
|
4193 return style; |
|
4194 } |
|
4195 |
|
4196 - (EditAction)_undoActionFromColorPanelWithSelector:(SEL)selector |
|
4197 { |
|
4198 if (selector == @selector(setBackgroundColor:)) |
|
4199 return EditActionSetBackgroundColor; |
|
4200 return EditActionSetColor; |
|
4201 } |
|
4202 |
|
4203 - (void)_changeCSSColorUsingSelector:(SEL)selector inRange:(DOMRange *)range |
|
4204 { |
|
4205 DOMCSSStyleDeclaration *style = [self _styleFromColorPanelWithSelector:selector]; |
|
4206 WebView *webView = [self _webView]; |
|
4207 if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:range]) |
|
4208 core([self _frame])->editor()->applyStyle(core(style), [self _undoActionFromColorPanelWithSelector:selector]); |
|
4209 } |
|
4210 |
|
4211 - (void)changeDocumentBackgroundColor:(id)sender |
|
4212 { |
|
4213 COMMAND_PROLOGUE |
|
4214 |
|
4215 // Mimicking NSTextView, this method sets the background color for the |
|
4216 // entire document. There is no NSTextView API for setting the background |
|
4217 // color on the selected range only. Note that this method is currently |
|
4218 // never called from the UI (see comment in changeColor:). |
|
4219 // FIXME: this actually has no effect when called, probably due to 3654850. _documentRange seems |
|
4220 // to do the right thing because it works in startSpeaking:, and I know setBackgroundColor: does the |
|
4221 // right thing because I tested it with [self _selectedRange]. |
|
4222 // FIXME: This won't actually apply the style to the entire range here, because it ends up calling |
|
4223 // [bridge applyStyle:], which operates on the current selection. To make this work right, we'll |
|
4224 // need to save off the selection, temporarily set it to the entire range, make the change, then |
|
4225 // restore the old selection. |
|
4226 [self _changeCSSColorUsingSelector:@selector(setBackgroundColor:) inRange:[self _documentRange]]; |
|
4227 } |
|
4228 |
|
4229 - (void)changeColor:(id)sender |
|
4230 { |
|
4231 COMMAND_PROLOGUE |
|
4232 |
|
4233 // FIXME: in NSTextView, this method calls changeDocumentBackgroundColor: when a |
|
4234 // private call has earlier been made by [NSFontFontEffectsBox changeColor:], see 3674493. |
|
4235 // AppKit will have to be revised to allow this to work with anything that isn't an |
|
4236 // NSTextView. However, this might not be required for Tiger, since the background-color |
|
4237 // changing box in the font panel doesn't work in Mail (3674481), though it does in TextEdit. |
|
4238 [self _applyStyleToSelection:[self _styleFromColorPanelWithSelector:@selector(setColor:)] |
|
4239 withUndoAction:EditActionSetColor]; |
|
4240 } |
|
4241 |
|
4242 - (void)_alignSelectionUsingCSSValue:(NSString *)CSSAlignmentValue withUndoAction:(EditAction)undoAction |
|
4243 { |
|
4244 if (![self _canEditRichly]) |
|
4245 return; |
|
4246 |
|
4247 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
4248 [style setTextAlign:CSSAlignmentValue]; |
|
4249 [self _applyStyleToSelection:style withUndoAction:undoAction]; |
|
4250 } |
|
4251 |
|
4252 - (void)alignCenter:(id)sender |
|
4253 { |
|
4254 COMMAND_PROLOGUE |
|
4255 |
|
4256 [self _alignSelectionUsingCSSValue:@"center" withUndoAction:EditActionCenter]; |
|
4257 } |
|
4258 |
|
4259 - (void)alignJustified:(id)sender |
|
4260 { |
|
4261 COMMAND_PROLOGUE |
|
4262 |
|
4263 [self _alignSelectionUsingCSSValue:@"justify" withUndoAction:EditActionJustify]; |
|
4264 } |
|
4265 |
|
4266 - (void)alignLeft:(id)sender |
|
4267 { |
|
4268 COMMAND_PROLOGUE |
|
4269 |
|
4270 [self _alignSelectionUsingCSSValue:@"left" withUndoAction:EditActionAlignLeft]; |
|
4271 } |
|
4272 |
|
4273 - (void)alignRight:(id)sender |
|
4274 { |
|
4275 COMMAND_PROLOGUE |
|
4276 |
|
4277 [self _alignSelectionUsingCSSValue:@"right" withUndoAction:EditActionAlignRight]; |
|
4278 } |
|
4279 |
|
4280 - (void)insertParagraphSeparator:(id)sender |
|
4281 { |
|
4282 COMMAND_PROLOGUE |
|
4283 |
|
4284 if (Frame* coreFrame = core([self _frame])) |
|
4285 coreFrame->editor()->execCommand("InsertNewline"); |
|
4286 } |
|
4287 |
|
4288 - (void)_changeWordCaseWithSelector:(SEL)selector |
|
4289 { |
|
4290 if (![self _canEdit]) |
|
4291 return; |
|
4292 |
|
4293 WebFrameBridge *bridge = [self _bridge]; |
|
4294 [self selectWord:nil]; |
|
4295 NSString *word = [[bridge selectedString] performSelector:selector]; |
|
4296 // FIXME: Does this need a different action context other than "typed"? |
|
4297 if ([self _shouldReplaceSelectionWithText:word givenAction:WebViewInsertActionTyped]) |
|
4298 [bridge replaceSelectionWithText:word selectReplacement:NO smartReplace:NO]; |
|
4299 } |
|
4300 |
|
4301 - (void)uppercaseWord:(id)sender |
|
4302 { |
|
4303 COMMAND_PROLOGUE |
|
4304 |
|
4305 [self _changeWordCaseWithSelector:@selector(uppercaseString)]; |
|
4306 } |
|
4307 |
|
4308 - (void)lowercaseWord:(id)sender |
|
4309 { |
|
4310 COMMAND_PROLOGUE |
|
4311 |
|
4312 [self _changeWordCaseWithSelector:@selector(lowercaseString)]; |
|
4313 } |
|
4314 |
|
4315 - (void)capitalizeWord:(id)sender |
|
4316 { |
|
4317 COMMAND_PROLOGUE |
|
4318 |
|
4319 [self _changeWordCaseWithSelector:@selector(capitalizedString)]; |
|
4320 } |
|
4321 |
|
4322 - (void)deleteForward:(id)sender |
|
4323 { |
|
4324 COMMAND_PROLOGUE |
|
4325 |
|
4326 if (![self _isEditable]) |
|
4327 return; |
|
4328 Frame* coreFrame = core([self _frame]); |
|
4329 if (coreFrame) |
|
4330 coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, CharacterGranularity, false, true); |
|
4331 } |
|
4332 |
|
4333 - (void)deleteBackward:(id)sender |
|
4334 { |
|
4335 COMMAND_PROLOGUE |
|
4336 |
|
4337 if (![self _isEditable]) |
|
4338 return; |
|
4339 Frame* coreFrame = core([self _frame]); |
|
4340 if (coreFrame) |
|
4341 coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, CharacterGranularity, false, true); |
|
4342 } |
|
4343 |
|
4344 - (void)deleteBackwardByDecomposingPreviousCharacter:(id)sender |
|
4345 { |
|
4346 COMMAND_PROLOGUE |
|
4347 |
|
4348 LOG_ERROR("unimplemented, doing deleteBackward instead"); |
|
4349 |
|
4350 if (![self _isEditable]) |
|
4351 return; |
|
4352 Frame* coreFrame = core([self _frame]); |
|
4353 if (coreFrame) |
|
4354 coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, CharacterGranularity, false, true); |
|
4355 } |
|
4356 |
|
4357 - (void)deleteToBeginningOfLine:(id)sender |
|
4358 { |
|
4359 COMMAND_PROLOGUE |
|
4360 |
|
4361 Frame* coreFrame = core([self _frame]); |
|
4362 if (coreFrame) |
|
4363 coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, LineBoundary, true, false); |
|
4364 } |
|
4365 |
|
4366 - (void)deleteToEndOfLine:(id)sender |
|
4367 { |
|
4368 COMMAND_PROLOGUE |
|
4369 |
|
4370 // To match NSTextView, this command should delete the newline at the end of |
|
4371 // a paragraph if you are at the end of a paragraph (like deleteToEndOfParagraph does below). |
|
4372 Frame* coreFrame = core([self _frame]); |
|
4373 if (coreFrame) { |
|
4374 if (!coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, LineBoundary, true, false)) |
|
4375 coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, CharacterGranularity, true, false); |
|
4376 } |
|
4377 |
|
4378 } |
|
4379 |
|
4380 - (void)deleteToBeginningOfParagraph:(id)sender |
|
4381 { |
|
4382 COMMAND_PROLOGUE |
|
4383 |
|
4384 Frame* coreFrame = core([self _frame]); |
|
4385 if (coreFrame) |
|
4386 coreFrame->editor()->deleteWithDirection(SelectionController::BACKWARD, ParagraphBoundary, true, false); |
|
4387 } |
|
4388 |
|
4389 - (void)deleteToEndOfParagraph:(id)sender |
|
4390 { |
|
4391 COMMAND_PROLOGUE |
|
4392 |
|
4393 // Despite the name of the method, this should delete the newline if the caret is at the end of a paragraph. |
|
4394 // If deletion to the end of the paragraph fails, we delete one character forward, which will delete the newline. |
|
4395 Frame* coreFrame = core([self _frame]); |
|
4396 if (coreFrame) { |
|
4397 if (!coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, ParagraphBoundary, true, false)) |
|
4398 coreFrame->editor()->deleteWithDirection(SelectionController::FORWARD, CharacterGranularity, true, false); |
|
4399 } |
|
4400 } |
|
4401 |
|
4402 - (void)complete:(id)sender |
|
4403 { |
|
4404 COMMAND_PROLOGUE |
|
4405 |
|
4406 if (![self _canEdit]) |
|
4407 return; |
|
4408 if (!_private->compController) |
|
4409 _private->compController = [[WebTextCompleteController alloc] initWithHTMLView:self]; |
|
4410 [_private->compController doCompletion]; |
|
4411 } |
|
4412 |
|
4413 - (void)checkSpelling:(id)sender |
|
4414 { |
|
4415 COMMAND_PROLOGUE |
|
4416 |
|
4417 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; |
|
4418 if (!checker) { |
|
4419 LOG_ERROR("No NSSpellChecker"); |
|
4420 return; |
|
4421 } |
|
4422 |
|
4423 core([self _frame])->editor()->advanceToNextMisspelling(); |
|
4424 } |
|
4425 |
|
4426 - (void)showGuessPanel:(id)sender |
|
4427 { |
|
4428 COMMAND_PROLOGUE |
|
4429 |
|
4430 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; |
|
4431 if (!checker) { |
|
4432 LOG_ERROR("No NSSpellChecker"); |
|
4433 return; |
|
4434 } |
|
4435 |
|
4436 NSPanel *spellingPanel = [checker spellingPanel]; |
|
4437 #ifndef BUILDING_ON_TIGER |
|
4438 // Post-Tiger, this menu item is a show/hide toggle, to match AppKit. Leave Tiger behavior alone |
|
4439 // to match rest of OS X. |
|
4440 if ([spellingPanel isVisible]) { |
|
4441 [spellingPanel orderOut:sender]; |
|
4442 return; |
|
4443 } |
|
4444 #endif |
|
4445 |
|
4446 core([self _frame])->editor()->advanceToNextMisspelling(true); |
|
4447 [spellingPanel orderFront:sender]; |
|
4448 } |
|
4449 |
|
4450 - (void)_changeSpellingToWord:(NSString *)newWord |
|
4451 { |
|
4452 if (![self _canEdit]) |
|
4453 return; |
|
4454 |
|
4455 // Don't correct to empty string. (AppKit checked this, we might as well too.) |
|
4456 if (![NSSpellChecker sharedSpellChecker]) { |
|
4457 LOG_ERROR("No NSSpellChecker"); |
|
4458 return; |
|
4459 } |
|
4460 |
|
4461 if ([newWord isEqualToString:@""]) |
|
4462 return; |
|
4463 |
|
4464 if ([self _shouldReplaceSelectionWithText:newWord givenAction:WebViewInsertActionPasted]) |
|
4465 [[self _bridge] replaceSelectionWithText:newWord selectReplacement:YES smartReplace:NO]; |
|
4466 } |
|
4467 |
|
4468 - (void)changeSpelling:(id)sender |
|
4469 { |
|
4470 COMMAND_PROLOGUE |
|
4471 |
|
4472 [self _changeSpellingToWord:[[sender selectedCell] stringValue]]; |
|
4473 } |
|
4474 |
|
4475 - (void)ignoreSpelling:(id)sender |
|
4476 { |
|
4477 COMMAND_PROLOGUE |
|
4478 |
|
4479 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; |
|
4480 if (!checker) { |
|
4481 LOG_ERROR("No NSSpellChecker"); |
|
4482 return; |
|
4483 } |
|
4484 |
|
4485 NSString *stringToIgnore = [sender stringValue]; |
|
4486 unsigned int length = [stringToIgnore length]; |
|
4487 if (stringToIgnore && length > 0) { |
|
4488 [checker ignoreWord:stringToIgnore inSpellDocumentWithTag:[[self _webView] spellCheckerDocumentTag]]; |
|
4489 // FIXME: Need to clear misspelling marker if the currently selected word is the one we are to ignore? |
|
4490 } |
|
4491 } |
|
4492 |
|
4493 - (void)performFindPanelAction:(id)sender |
|
4494 { |
|
4495 COMMAND_PROLOGUE |
|
4496 |
|
4497 // Implementing this will probably require copying all of NSFindPanel.h and .m. |
|
4498 // We need *almost* the same thing as AppKit, but not quite. |
|
4499 LOG_ERROR("unimplemented"); |
|
4500 } |
|
4501 |
|
4502 - (void)startSpeaking:(id)sender |
|
4503 { |
|
4504 COMMAND_PROLOGUE |
|
4505 |
|
4506 WebFrameBridge *bridge = [self _bridge]; |
|
4507 DOMRange *range = [self _selectedRange]; |
|
4508 if (!range || [range collapsed]) |
|
4509 range = [self _documentRange]; |
|
4510 [NSApp speakString:[bridge stringForRange:range]]; |
|
4511 } |
|
4512 |
|
4513 - (void)stopSpeaking:(id)sender |
|
4514 { |
|
4515 COMMAND_PROLOGUE |
|
4516 |
|
4517 [NSApp stopSpeaking:sender]; |
|
4518 } |
|
4519 |
|
4520 - (void)insertNewlineIgnoringFieldEditor:(id)sender |
|
4521 { |
|
4522 COMMAND_PROLOGUE |
|
4523 |
|
4524 if (Frame* coreFrame = core([self _frame])) |
|
4525 coreFrame->editor()->execCommand("InsertNewline"); |
|
4526 } |
|
4527 |
|
4528 - (void)insertTabIgnoringFieldEditor:(id)sender |
|
4529 { |
|
4530 COMMAND_PROLOGUE |
|
4531 |
|
4532 if (Frame* coreFrame = core([self _frame])) |
|
4533 coreFrame->editor()->execCommand("InsertTab"); |
|
4534 } |
|
4535 |
|
4536 - (void)subscript:(id)sender |
|
4537 { |
|
4538 COMMAND_PROLOGUE |
|
4539 |
|
4540 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
4541 [style setVerticalAlign:@"sub"]; |
|
4542 [self _applyStyleToSelection:style withUndoAction:EditActionSubscript]; |
|
4543 } |
|
4544 |
|
4545 - (void)superscript:(id)sender |
|
4546 { |
|
4547 COMMAND_PROLOGUE |
|
4548 |
|
4549 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
4550 [style setVerticalAlign:@"super"]; |
|
4551 [self _applyStyleToSelection:style withUndoAction:EditActionSuperscript]; |
|
4552 } |
|
4553 |
|
4554 - (void)unscript:(id)sender |
|
4555 { |
|
4556 COMMAND_PROLOGUE |
|
4557 |
|
4558 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
4559 [style setVerticalAlign:@"baseline"]; |
|
4560 [self _applyStyleToSelection:style withUndoAction:EditActionUnscript]; |
|
4561 } |
|
4562 |
|
4563 - (void)underline:(id)sender |
|
4564 { |
|
4565 COMMAND_PROLOGUE |
|
4566 |
|
4567 Frame* coreFrame = core([self _frame]); |
|
4568 if (!coreFrame) |
|
4569 return; |
|
4570 // Despite the name, this method is actually supposed to toggle underline. |
|
4571 // FIXME: This currently clears overline, line-through, and blink as an unwanted side effect. |
|
4572 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
4573 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"underline" priority:@""]; |
|
4574 if (coreFrame->editor()->selectionStartHasStyle(core(style))) |
|
4575 [style setProperty:@"-khtml-text-decorations-in-effect" value:@"none" priority:@""]; |
|
4576 [self _applyStyleToSelection:style withUndoAction:EditActionUnderline]; |
|
4577 } |
|
4578 |
|
4579 - (void)yank:(id)sender |
|
4580 { |
|
4581 COMMAND_PROLOGUE |
|
4582 |
|
4583 if (![self _canEdit]) |
|
4584 return; |
|
4585 |
|
4586 NSString* yankee = _NSYankFromKillRing(); |
|
4587 |
|
4588 if (Frame* coreFrame = core([self _frame])) |
|
4589 coreFrame->editor()->insertTextWithoutSendingTextEvent(yankee, false); |
|
4590 |
|
4591 _NSSetKillRingToYankedState(); |
|
4592 } |
|
4593 |
|
4594 - (void)yankAndSelect:(id)sender |
|
4595 { |
|
4596 COMMAND_PROLOGUE |
|
4597 |
|
4598 if (![self _canEdit]) |
|
4599 return; |
|
4600 |
|
4601 NSString* yankee = _NSYankFromKillRing(); |
|
4602 |
|
4603 if (Frame* coreFrame = core([self _frame])) |
|
4604 coreFrame->editor()->insertTextWithoutSendingTextEvent(yankee, true); |
|
4605 |
|
4606 _NSSetKillRingToYankedState(); |
|
4607 } |
|
4608 |
|
4609 - (void)setMark:(id)sender |
|
4610 { |
|
4611 COMMAND_PROLOGUE |
|
4612 |
|
4613 [[self _bridge] setMarkDOMRange:[self _selectedRange]]; |
|
4614 } |
|
4615 |
|
4616 static DOMRange *unionDOMRanges(DOMRange *a, DOMRange *b) |
|
4617 { |
|
4618 ASSERT(a); |
|
4619 ASSERT(b); |
|
4620 DOMRange *s = [a compareBoundaryPoints:DOM_START_TO_START sourceRange:b] <= 0 ? a : b; |
|
4621 DOMRange *e = [a compareBoundaryPoints:DOM_END_TO_END sourceRange:b] <= 0 ? b : a; |
|
4622 DOMRange *r = [[[a startContainer] ownerDocument] createRange]; |
|
4623 [r setStart:[s startContainer] offset:[s startOffset]]; |
|
4624 [r setEnd:[e endContainer] offset:[e endOffset]]; |
|
4625 return r; |
|
4626 } |
|
4627 |
|
4628 - (void)deleteToMark:(id)sender |
|
4629 { |
|
4630 COMMAND_PROLOGUE |
|
4631 |
|
4632 if (![self _canEdit]) |
|
4633 return; |
|
4634 |
|
4635 DOMRange *mark = [[self _bridge] markDOMRange]; |
|
4636 if (mark == nil) { |
|
4637 if (Frame* coreFrame = core([self _frame])) |
|
4638 coreFrame->editor()->performDelete(); |
|
4639 } else { |
|
4640 DOMRange *selection = [self _selectedRange]; |
|
4641 DOMRange *r; |
|
4642 NS_DURING |
|
4643 r = unionDOMRanges(mark, selection); |
|
4644 NS_HANDLER |
|
4645 r = selection; |
|
4646 NS_ENDHANDLER |
|
4647 Frame* coreFrame = core([self _frame]); |
|
4648 if (coreFrame) |
|
4649 coreFrame->editor()->deleteRange([r _range], true, true, false, deleteSelectionAction, CharacterGranularity); |
|
4650 |
|
4651 } |
|
4652 [[self _bridge] setMarkDOMRange:[self _selectedRange]]; |
|
4653 } |
|
4654 |
|
4655 - (void)selectToMark:(id)sender |
|
4656 { |
|
4657 COMMAND_PROLOGUE |
|
4658 |
|
4659 WebFrameBridge *bridge = [self _bridge]; |
|
4660 DOMRange *mark = [bridge markDOMRange]; |
|
4661 DOMRange *selection = [self _selectedRange]; |
|
4662 Frame* coreFrame = core([self _frame]); |
|
4663 if (!mark || !selection || !coreFrame) { |
|
4664 NSBeep(); |
|
4665 return; |
|
4666 } |
|
4667 ExceptionCode ec = 0; |
|
4668 coreFrame->selectionController()->setSelectedRange(core(unionDOMRanges(mark, [self _selectedRange])), DOWNSTREAM, true, ec); |
|
4669 } |
|
4670 |
|
4671 - (void)swapWithMark:(id)sender |
|
4672 { |
|
4673 COMMAND_PROLOGUE |
|
4674 |
|
4675 WebFrameBridge *bridge = [self _bridge]; |
|
4676 DOMRange *mark = [bridge markDOMRange]; |
|
4677 DOMRange *selection = [self _selectedRange]; |
|
4678 Frame* coreFrame = core([self _frame]); |
|
4679 if (!mark || !selection || !coreFrame) { |
|
4680 NSBeep(); |
|
4681 return; |
|
4682 } |
|
4683 |
|
4684 ExceptionCode ec = 0; |
|
4685 coreFrame->selectionController()->setSelectedRange(core(mark), DOWNSTREAM, true, ec); |
|
4686 if (ec == 0) |
|
4687 [bridge setMarkDOMRange:selection]; |
|
4688 } |
|
4689 |
|
4690 - (void)transpose:(id)sender |
|
4691 { |
|
4692 COMMAND_PROLOGUE |
|
4693 |
|
4694 if (![self _canEdit]) |
|
4695 return; |
|
4696 |
|
4697 WebFrameBridge *bridge = [self _bridge]; |
|
4698 DOMRange *r = [bridge rangeOfCharactersAroundCaret]; |
|
4699 if (!r) |
|
4700 return; |
|
4701 NSString *characters = [bridge stringForRange:r]; |
|
4702 if ([characters length] != 2) |
|
4703 return; |
|
4704 NSString *transposed = [[characters substringFromIndex:1] stringByAppendingString:[characters substringToIndex:1]]; |
|
4705 WebView *webView = [self _webView]; |
|
4706 if (![[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[self _selectedRange] |
|
4707 toDOMRange:r affinity:NSSelectionAffinityDownstream stillSelecting:NO]) |
|
4708 return; |
|
4709 |
|
4710 Frame* coreFrame = core([self _frame]); |
|
4711 if (!coreFrame) |
|
4712 return; |
|
4713 |
|
4714 ExceptionCode ec = 0; |
|
4715 coreFrame->selectionController()->setSelectedRange(core(r), DOWNSTREAM, true, ec); |
|
4716 if ([self _shouldReplaceSelectionWithText:transposed givenAction:WebViewInsertActionTyped]) |
|
4717 [bridge replaceSelectionWithText:transposed selectReplacement:NO smartReplace:NO]; |
|
4718 } |
|
4719 |
|
4720 - (void)toggleBaseWritingDirection:(id)sender |
|
4721 { |
|
4722 COMMAND_PROLOGUE |
|
4723 |
|
4724 if (![self _canEdit]) |
|
4725 return; |
|
4726 |
|
4727 NSString *direction = @"RTL"; |
|
4728 switch ([[self _bridge] baseWritingDirectionForSelectionStart]) { |
|
4729 case NSWritingDirectionLeftToRight: |
|
4730 break; |
|
4731 case NSWritingDirectionRightToLeft: |
|
4732 direction = @"LTR"; |
|
4733 break; |
|
4734 // The writingDirectionForSelectionStart method will never return "natural". It |
|
4735 // will always return a concrete direction. So, keep the compiler happy, and assert not reached. |
|
4736 case NSWritingDirectionNatural: |
|
4737 ASSERT_NOT_REACHED(); |
|
4738 break; |
|
4739 } |
|
4740 |
|
4741 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
4742 [style setDirection:direction]; |
|
4743 [self _applyParagraphStyleToSelection:style withUndoAction:EditActionSetWritingDirection]; |
|
4744 } |
|
4745 |
|
4746 - (void)changeBaseWritingDirection:(id)sender |
|
4747 { |
|
4748 COMMAND_PROLOGUE |
|
4749 |
|
4750 if (![self _canEdit]) |
|
4751 return; |
|
4752 |
|
4753 NSWritingDirection writingDirection = static_cast<NSWritingDirection>([sender tag]); |
|
4754 |
|
4755 // We disable the menu item that performs this action because we can't implement |
|
4756 // NSWritingDirectionNatural's behavior using CSS. |
|
4757 ASSERT(writingDirection != NSWritingDirectionNatural); |
|
4758 |
|
4759 DOMCSSStyleDeclaration *style = [self _emptyStyle]; |
|
4760 [style setDirection:writingDirection == NSWritingDirectionLeftToRight ? @"LTR" : @"RTL"]; |
|
4761 [self _applyParagraphStyleToSelection:style withUndoAction:EditActionSetWritingDirection]; |
|
4762 } |
|
4763 |
|
4764 - (void)indent:(id)sender |
|
4765 { |
|
4766 COMMAND_PROLOGUE |
|
4767 |
|
4768 core([self _frame])->editor()->indent(); |
|
4769 } |
|
4770 |
|
4771 - (void)outdent:(id)sender |
|
4772 { |
|
4773 COMMAND_PROLOGUE |
|
4774 |
|
4775 core([self _frame])->editor()->outdent(); |
|
4776 } |
|
4777 |
|
4778 #if 0 |
|
4779 |
|
4780 // CSS does not have a way to specify an outline font, which may make this difficult to implement. |
|
4781 // Maybe a special case of text-shadow? |
|
4782 - (void)outline:(id)sender; |
|
4783 |
|
4784 // This is part of table support, which may be in NSTextView for Tiger. |
|
4785 // It's probably simple to do the equivalent thing for WebKit. |
|
4786 - (void)insertTable:(id)sender; |
|
4787 |
|
4788 // This could be important. |
|
4789 - (void)toggleTraditionalCharacterShape:(id)sender; |
|
4790 |
|
4791 // I'm not sure what the equivalents of these in the web world are. |
|
4792 - (void)insertLineSeparator:(id)sender; |
|
4793 - (void)insertPageBreak:(id)sender; |
|
4794 |
|
4795 // These methods are not implemented in NSTextView yet at the time of this writing. |
|
4796 - (void)changeCaseOfLetter:(id)sender; |
|
4797 - (void)transposeWords:(id)sender; |
|
4798 |
|
4799 #endif |
|
4800 |
|
4801 // Super-hack alert. |
|
4802 // Workaround for bug 3789278. |
|
4803 |
|
4804 // Returns a selector only if called while: |
|
4805 // 1) first responder is self |
|
4806 // 2) handling a key down event |
|
4807 // 3) not yet inside keyDown: method |
|
4808 // 4) key is an arrow key |
|
4809 // The selector is the one that gets sent by -[NSWindow _processKeyboardUIKey] for this key. |
|
4810 - (SEL)_arrowKeyDownEventSelectorIfPreprocessing |
|
4811 { |
|
4812 NSWindow *w = [self window]; |
|
4813 if ([w firstResponder] != self) |
|
4814 return NULL; |
|
4815 NSEvent *e = [w currentEvent]; |
|
4816 if ([e type] != NSKeyDown) |
|
4817 return NULL; |
|
4818 if (e == _private->keyDownEvent) |
|
4819 return NULL; |
|
4820 NSString *s = [e charactersIgnoringModifiers]; |
|
4821 if ([s length] == 0) |
|
4822 return NULL; |
|
4823 switch ([s characterAtIndex:0]) { |
|
4824 case NSDownArrowFunctionKey: |
|
4825 return @selector(moveDown:); |
|
4826 case NSLeftArrowFunctionKey: |
|
4827 return @selector(moveLeft:); |
|
4828 case NSRightArrowFunctionKey: |
|
4829 return @selector(moveRight:); |
|
4830 case NSUpArrowFunctionKey: |
|
4831 return @selector(moveUp:); |
|
4832 default: |
|
4833 return NULL; |
|
4834 } |
|
4835 } |
|
4836 |
|
4837 // Returns NO instead of YES if called on the selector that the |
|
4838 // _arrowKeyDownEventSelectorIfPreprocessing method returns. |
|
4839 // This should only happen inside -[NSWindow _processKeyboardUIKey], |
|
4840 // and together with the change below should cause that method |
|
4841 // to return NO rather than handling the key. |
|
4842 // Also set a 1-shot flag for the nextResponder check below. |
|
4843 - (BOOL)respondsToSelector:(SEL)selector |
|
4844 { |
|
4845 if (![super respondsToSelector:selector]) |
|
4846 return NO; |
|
4847 SEL arrowKeySelector = [self _arrowKeyDownEventSelectorIfPreprocessing]; |
|
4848 if (selector != arrowKeySelector) |
|
4849 return YES; |
|
4850 _private->nextResponderDisabledOnce = YES; |
|
4851 return NO; |
|
4852 } |
|
4853 |
|
4854 // Returns nil instead of the next responder if called when the |
|
4855 // one-shot flag is set, and _arrowKeyDownEventSelectorIfPreprocessing |
|
4856 // returns something other than NULL. This should only happen inside |
|
4857 // -[NSWindow _processKeyboardUIKey] and together with the change above |
|
4858 // should cause that method to return NO rather than handling the key. |
|
4859 - (NSResponder *)nextResponder |
|
4860 { |
|
4861 BOOL disabled = _private->nextResponderDisabledOnce; |
|
4862 _private->nextResponderDisabledOnce = NO; |
|
4863 if (disabled && [self _arrowKeyDownEventSelectorIfPreprocessing] != NULL) |
|
4864 return nil; |
|
4865 return [super nextResponder]; |
|
4866 } |
|
4867 |
|
4868 // Despite its name, this is called at different times than windowDidBecomeKey is. |
|
4869 // It takes into account all the other factors that determine when NSCell draws |
|
4870 // with different tints, so it's the right call to use for control tints. We'd prefer |
|
4871 // to do this with API. <rdar://problem/5136760> |
|
4872 - (void)_windowChangedKeyState |
|
4873 { |
|
4874 if (Frame* frame = core([self _frame])) |
|
4875 if (FrameView* view = frame->view()) |
|
4876 view->updateControlTints(); |
|
4877 [super _windowChangedKeyState]; |
|
4878 } |
|
4879 |
|
4880 @end |
|
4881 |
|
4882 @implementation WebHTMLView (WebTextSizing) |
|
4883 |
|
4884 - (IBAction)_makeTextSmaller:(id)sender |
|
4885 { |
|
4886 [self _updateTextSizeMultiplier]; |
|
4887 } |
|
4888 |
|
4889 - (IBAction)_makeTextLarger:(id)sender |
|
4890 { |
|
4891 [self _updateTextSizeMultiplier]; |
|
4892 } |
|
4893 |
|
4894 - (IBAction)_makeTextStandardSize:(id)sender |
|
4895 { |
|
4896 [self _updateTextSizeMultiplier]; |
|
4897 } |
|
4898 |
|
4899 - (BOOL)_tracksCommonSizeFactor |
|
4900 { |
|
4901 return YES; |
|
4902 } |
|
4903 |
|
4904 - (void)_textSizeMultiplierChanged |
|
4905 { |
|
4906 [self _updateTextSizeMultiplier]; |
|
4907 } |
|
4908 |
|
4909 // never sent because we track the common size factor |
|
4910 - (BOOL)_canMakeTextSmaller |
|
4911 { |
|
4912 ASSERT_NOT_REACHED(); |
|
4913 return NO; |
|
4914 } |
|
4915 |
|
4916 - (BOOL)_canMakeTextLarger |
|
4917 { |
|
4918 ASSERT_NOT_REACHED(); |
|
4919 return NO; |
|
4920 } |
|
4921 |
|
4922 - (BOOL)_canMakeTextStandardSize |
|
4923 { |
|
4924 ASSERT_NOT_REACHED(); |
|
4925 return NO; |
|
4926 } |
|
4927 |
|
4928 @end |
|
4929 |
|
4930 @implementation NSArray (WebHTMLView) |
|
4931 |
|
4932 - (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object |
|
4933 { |
|
4934 #ifndef __LP64__ |
|
4935 NSEnumerator *enumerator = [self objectEnumerator]; |
|
4936 WebNetscapePluginEmbeddedView *view; |
|
4937 while ((view = [enumerator nextObject]) != nil) |
|
4938 if ([view isKindOfClass:[WebNetscapePluginEmbeddedView class]]) |
|
4939 [view performSelector:selector withObject:object]; |
|
4940 #endif |
|
4941 } |
|
4942 |
|
4943 @end |
|
4944 |
|
4945 @implementation WebHTMLView (WebInternal) |
|
4946 |
|
4947 - (void)_selectionChanged |
|
4948 { |
|
4949 [self _updateSelectionForInputManager]; |
|
4950 [self _updateFontPanel]; |
|
4951 if (Frame* coreFrame = core([self _frame])) |
|
4952 coreFrame->editor()->setStartNewKillRingSequence(true); |
|
4953 } |
|
4954 |
|
4955 - (void)_updateFontPanel |
|
4956 { |
|
4957 // FIXME: NSTextView bails out if becoming or resigning first responder, for which it has ivar flags. Not |
|
4958 // sure if we need to do something similar. |
|
4959 |
|
4960 if (![self _canEdit]) |
|
4961 return; |
|
4962 |
|
4963 NSWindow *window = [self window]; |
|
4964 // FIXME: is this first-responder check correct? What happens if a subframe is editable and is first responder? |
|
4965 if ([NSApp keyWindow] != window || [window firstResponder] != self) |
|
4966 return; |
|
4967 |
|
4968 BOOL multiple = NO; |
|
4969 NSFont *font = [[self _bridge] fontForSelection:&multiple]; |
|
4970 |
|
4971 // FIXME: for now, return a bogus font that distinguishes the empty selection from the non-empty |
|
4972 // selection. We should be able to remove this once the rest of this code works properly. |
|
4973 if (font == nil) |
|
4974 font = [self _hasSelection] ? [NSFont menuFontOfSize:23] : [NSFont toolTipsFontOfSize:17]; |
|
4975 ASSERT(font != nil); |
|
4976 |
|
4977 NSFontManager *fm = [NSFontManager sharedFontManager]; |
|
4978 [fm setSelectedFont:font isMultiple:multiple]; |
|
4979 |
|
4980 // FIXME: we don't keep track of selected attributes, or set them on the font panel. This |
|
4981 // appears to have no effect on the UI. E.g., underlined text in Mail or TextEdit is |
|
4982 // not reflected in the font panel. Maybe someday this will change. |
|
4983 } |
|
4984 |
|
4985 - (BOOL)_canSmartCopyOrDelete |
|
4986 { |
|
4987 return [[self _webView] smartInsertDeleteEnabled] && [[self _bridge] selectionGranularity] == WordGranularity; |
|
4988 } |
|
4989 |
|
4990 - (DOMRange *)_smartDeleteRangeForProposedRange:(DOMRange *)proposedRange |
|
4991 { |
|
4992 if (proposedRange == nil || [self _canSmartCopyOrDelete] == NO) |
|
4993 return nil; |
|
4994 |
|
4995 return [[self _bridge] smartDeleteRangeForProposedRange:proposedRange]; |
|
4996 } |
|
4997 |
|
4998 - (void)_smartInsertForString:(NSString *)pasteString replacingRange:(DOMRange *)rangeToReplace beforeString:(NSString **)beforeString afterString:(NSString **)afterString |
|
4999 { |
|
5000 if (!pasteString || !rangeToReplace || ![[self _webView] smartInsertDeleteEnabled]) { |
|
5001 if (beforeString) |
|
5002 *beforeString = nil; |
|
5003 if (afterString) |
|
5004 *afterString = nil; |
|
5005 return; |
|
5006 } |
|
5007 |
|
5008 [[self _bridge] smartInsertForString:pasteString replacingRange:rangeToReplace beforeString:beforeString afterString:afterString]; |
|
5009 } |
|
5010 |
|
5011 - (BOOL)_textViewWasFirstResponderAtMouseDownTime:(NSTextView *)textView |
|
5012 { |
|
5013 return textView == _private->firstResponderTextViewAtMouseDownTime; |
|
5014 } |
|
5015 |
|
5016 |
|
5017 - (NSEvent *)_mouseDownEvent |
|
5018 { |
|
5019 return _private->mouseDownEvent; |
|
5020 } |
|
5021 |
|
5022 #ifndef __LP64__ |
|
5023 - (void)_pauseNullEventsForAllNetscapePlugins |
|
5024 { |
|
5025 NSArray *subviews = [self subviews]; |
|
5026 unsigned int subviewCount = [subviews count]; |
|
5027 unsigned int subviewIndex; |
|
5028 |
|
5029 for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) { |
|
5030 NSView *subview = [subviews objectAtIndex:subviewIndex]; |
|
5031 if ([subview isKindOfClass:[WebBaseNetscapePluginView class]]) |
|
5032 [(WebBaseNetscapePluginView *)subview stopNullEvents]; |
|
5033 } |
|
5034 } |
|
5035 #endif |
|
5036 |
|
5037 #ifndef __LP64__ |
|
5038 - (void)_resumeNullEventsForAllNetscapePlugins |
|
5039 { |
|
5040 NSArray *subviews = [self subviews]; |
|
5041 unsigned int subviewCount = [subviews count]; |
|
5042 unsigned int subviewIndex; |
|
5043 |
|
5044 for (subviewIndex = 0; subviewIndex < subviewCount; subviewIndex++) { |
|
5045 NSView *subview = [subviews objectAtIndex:subviewIndex]; |
|
5046 if ([subview isKindOfClass:[WebBaseNetscapePluginView class]]) |
|
5047 [(WebBaseNetscapePluginView *)subview restartNullEvents]; |
|
5048 } |
|
5049 } |
|
5050 #endif |
|
5051 |
|
5052 - (void)_willMakeFirstResponderForNodeFocus |
|
5053 { |
|
5054 _private->willBecomeFirstResponderForNodeFocus = YES; |
|
5055 } |
|
5056 |
|
5057 - (id<WebHTMLHighlighter>)_highlighterForType:(NSString*)type |
|
5058 { |
|
5059 return [_private->highlighters objectForKey:type]; |
|
5060 } |
|
5061 |
|
5062 - (WebFrame *)_frame |
|
5063 { |
|
5064 return [_private->dataSource webFrame]; |
|
5065 } |
|
5066 |
|
5067 - (void)copy:(id)sender |
|
5068 { |
|
5069 COMMAND_PROLOGUE |
|
5070 |
|
5071 if (Frame* coreFrame = core([self _frame])) |
|
5072 coreFrame->editor()->copy(); |
|
5073 } |
|
5074 |
|
5075 - (void)cut:(id)sender |
|
5076 { |
|
5077 COMMAND_PROLOGUE |
|
5078 |
|
5079 if (Frame* coreFrame = core([self _frame])) |
|
5080 coreFrame->editor()->cut(); |
|
5081 } |
|
5082 |
|
5083 - (void)paste:(id)sender |
|
5084 { |
|
5085 COMMAND_PROLOGUE |
|
5086 |
|
5087 Frame* coreFrame = core([self _frame]); |
|
5088 if (coreFrame && coreFrame->editor()->tryDHTMLPaste()) |
|
5089 return; // DHTML did the whole operation |
|
5090 if (!coreFrame->editor()->canPaste()) |
|
5091 return; |
|
5092 if (coreFrame && coreFrame->selectionController()->isContentRichlyEditable()) |
|
5093 [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:YES]; |
|
5094 else |
|
5095 coreFrame->editor()->pasteAsPlainText(); |
|
5096 } |
|
5097 |
|
5098 - (void)pasteAsPlainText:(id)sender |
|
5099 { |
|
5100 COMMAND_PROLOGUE |
|
5101 |
|
5102 if (![self _canEdit]) |
|
5103 return; |
|
5104 [self _pasteAsPlainTextWithPasteboard:[NSPasteboard generalPasteboard]]; |
|
5105 } |
|
5106 |
|
5107 - (void)closeIfNotCurrentView |
|
5108 { |
|
5109 if ([[[self _frame] frameView] documentView] != self) |
|
5110 [self close]; |
|
5111 } |
|
5112 |
|
5113 - (DOMDocumentFragment*)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard |
|
5114 { |
|
5115 BOOL discard; |
|
5116 return [self _documentFragmentFromPasteboard:pasteboard inContext:nil allowPlainText:NO chosePlainText:&discard]; |
|
5117 } |
|
5118 |
|
5119 #ifndef BUILDING_ON_TIGER |
|
5120 |
|
5121 - (BOOL)isGrammarCheckingEnabled |
|
5122 { |
|
5123 // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because |
|
5124 // the AppKit code checks the first responder. |
|
5125 return [[self _webView] isGrammarCheckingEnabled]; |
|
5126 } |
|
5127 |
|
5128 - (void)setGrammarCheckingEnabled:(BOOL)flag |
|
5129 { |
|
5130 // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because |
|
5131 // the AppKit code checks the first responder. |
|
5132 [[self _webView] setGrammarCheckingEnabled:flag]; |
|
5133 } |
|
5134 |
|
5135 - (void)toggleGrammarChecking:(id)sender |
|
5136 { |
|
5137 // FIXME 4799134: WebView is the bottleneck for this grammar-checking logic, but we must implement the method here because |
|
5138 // the AppKit code checks the first responder. |
|
5139 [[self _webView] toggleGrammarChecking:sender]; |
|
5140 } |
|
5141 |
|
5142 |
|
5143 static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point) |
|
5144 { |
|
5145 NSArray *screens = [NSScreen screens]; |
|
5146 |
|
5147 if ([screens count] == 0) { |
|
5148 // You could theoretically get here if running with no monitor, in which case it doesn't matter |
|
5149 // much where the "on-screen" point is. |
|
5150 return CGPointMake(point.x, point.y); |
|
5151 } |
|
5152 |
|
5153 // Flip the y coordinate from the top of the menu bar screen -- see 4636390 |
|
5154 return CGPointMake(point.x, NSMaxY([[screens objectAtIndex:0] frame]) - point.y); |
|
5155 } |
|
5156 |
|
5157 #endif |
|
5158 |
|
5159 - (void)_lookUpInDictionaryFromMenu:(id)sender |
|
5160 { |
|
5161 // Dictionary API will accept a whitespace-only string and display UI as if it were real text, |
|
5162 // so bail out early to avoid that. |
|
5163 if ([[[self selectedString] _webkit_stringByTrimmingWhitespace] length] == 0) |
|
5164 return; |
|
5165 |
|
5166 // We soft link to get the function that displays the dictionary (either pop-up window or app) to avoid the performance |
|
5167 // penalty of linking to another framework. This function changed signature as well as framework between Tiger and Leopard, |
|
5168 // so the two cases are handled separately. |
|
5169 |
|
5170 #ifdef BUILDING_ON_TIGER |
|
5171 typedef OSStatus (*ServiceWindowShowFunction)(id inWordString, NSRect inWordBoundary, UInt16 inLineDirection); |
|
5172 const char *frameworkPath = "/System/Library/Frameworks/ApplicationServices.framework/Frameworks/LangAnalysis.framework/LangAnalysis"; |
|
5173 const char *functionName = "DCMDictionaryServiceWindowShow"; |
|
5174 #else |
|
5175 typedef void (*ServiceWindowShowFunction)(id unusedDictionaryRef, id inWordString, CFRange selectionRange, id unusedFont, CGPoint textOrigin, Boolean verticalText, id unusedTransform); |
|
5176 const char *frameworkPath = "/System/Library/Frameworks/Carbon.framework/Frameworks/HIToolbox.framework/HIToolbox"; |
|
5177 const char *functionName = "HIDictionaryWindowShow"; |
|
5178 #endif |
|
5179 |
|
5180 static bool lookedForFunction = false; |
|
5181 static ServiceWindowShowFunction dictionaryServiceWindowShow = NULL; |
|
5182 |
|
5183 if (!lookedForFunction) { |
|
5184 void* langAnalysisFramework = dlopen(frameworkPath, RTLD_LAZY); |
|
5185 ASSERT(langAnalysisFramework); |
|
5186 if (langAnalysisFramework) |
|
5187 dictionaryServiceWindowShow = (ServiceWindowShowFunction)dlsym(langAnalysisFramework, functionName); |
|
5188 lookedForFunction = true; |
|
5189 } |
|
5190 |
|
5191 ASSERT(dictionaryServiceWindowShow); |
|
5192 if (!dictionaryServiceWindowShow) { |
|
5193 NSLog(@"Couldn't find the %s function in %s", functionName, frameworkPath); |
|
5194 return; |
|
5195 } |
|
5196 |
|
5197 NSAttributedString *attrString = [self selectedAttributedString]; |
|
5198 |
|
5199 #ifdef BUILDING_ON_TIGER |
|
5200 // FIXME: must check for right-to-left here |
|
5201 NSWritingDirection writingDirection = NSWritingDirectionLeftToRight; |
|
5202 |
|
5203 // FIXME: the dictionary API expects the rect for the first line of selection. Passing |
|
5204 // the rect for the entire selection, as we do here, positions the pop-up window near |
|
5205 // the bottom of the selection rather than at the selected word. |
|
5206 NSRect rect = [self convertRect:core([self _frame])->selectionRect() toView:nil]; |
|
5207 rect.origin = [[self window] convertBaseToScreen:rect.origin]; |
|
5208 NSData *data = [attrString RTFFromRange:NSMakeRange(0, [attrString length]) documentAttributes:nil]; |
|
5209 dictionaryServiceWindowShow(data, rect, (writingDirection == NSWritingDirectionRightToLeft) ? 1 : 0); |
|
5210 #else |
|
5211 // The HIDictionaryWindowShow function requires the origin, in CG screen coordinates, of the first character of text in the selection. |
|
5212 // FIXME 4945808: We approximate this in a way that works well when a single word is selected, and less well in some other cases |
|
5213 // (but no worse than we did in Tiger) |
|
5214 NSRect rect = core([self _frame])->selectionRect(); |
|
5215 |
|
5216 NSDictionary *attributes = [attrString fontAttributesInRange:NSMakeRange(0,1)]; |
|
5217 NSFont *font = [attributes objectForKey:NSFontAttributeName]; |
|
5218 if (font) |
|
5219 rect.origin.y += [font ascender]; |
|
5220 |
|
5221 NSPoint windowPoint = [self convertPoint:rect.origin toView:nil]; |
|
5222 NSPoint screenPoint = [[self window] convertBaseToScreen:windowPoint]; |
|
5223 |
|
5224 dictionaryServiceWindowShow(nil, attrString, CFRangeMake(0, [attrString length]), nil, |
|
5225 coreGraphicsScreenPointForAppKitScreenPoint(screenPoint), false, nil); |
|
5226 #endif |
|
5227 } |
|
5228 |
|
5229 - (void)_hoverFeedbackSuspendedChanged |
|
5230 { |
|
5231 [self _updateMouseoverWithFakeEvent]; |
|
5232 } |
|
5233 |
|
5234 - (BOOL)_interceptEditingKeyEvent:(KeyboardEvent*)event shouldSaveCommand:(BOOL)shouldSave |
|
5235 { |
|
5236 // Ask AppKit to process the key event -- it will call back with either insertText or doCommandBySelector. |
|
5237 WebHTMLViewInterpretKeyEventsParameters parameters; |
|
5238 parameters.eventWasHandled = false; |
|
5239 parameters.shouldSaveCommand = shouldSave; |
|
5240 // If we're intercepting the initial IM call we assume that the IM has consumed the event, |
|
5241 // and only change this assumption if one of the NSTextInput/Responder callbacks is used. |
|
5242 // We assume the IM will *not* consume hotkey sequences |
|
5243 parameters.consumedByIM = !event->metaKey() && shouldSave; |
|
5244 |
|
5245 if (const PlatformKeyboardEvent* platformEvent = event->keyEvent()) { |
|
5246 NSEvent *macEvent = platformEvent->macEvent(); |
|
5247 if ([macEvent type] == NSKeyDown && [_private->compController filterKeyDown:macEvent]) |
|
5248 return true; |
|
5249 parameters.event = event; |
|
5250 _private->interpretKeyEventsParameters = ¶meters; |
|
5251 _private->receivedNOOP = NO; |
|
5252 KeypressCommand command = event->keypressCommand(); |
|
5253 bool hasKeypressCommand = !command.commandNames.isEmpty() || !command.text.isEmpty(); |
|
5254 |
|
5255 if (parameters.shouldSaveCommand || !hasKeypressCommand) |
|
5256 [self interpretKeyEvents:[NSArray arrayWithObject:macEvent]]; |
|
5257 else { |
|
5258 if (!command.text.isEmpty()) |
|
5259 [self insertText:command.text]; |
|
5260 else { |
|
5261 size_t size = command.commandNames.size(); |
|
5262 for (size_t i = 0; i < size; ++i) |
|
5263 [self doCommandBySelector:NSSelectorFromString(command.commandNames[i])]; |
|
5264 } |
|
5265 } |
|
5266 _private->interpretKeyEventsParameters = 0; |
|
5267 } |
|
5268 return (!_private->receivedNOOP && parameters.eventWasHandled) || parameters.consumedByIM; |
|
5269 } |
|
5270 |
|
5271 - (WebCore::CachedImage*)promisedDragTIFFDataSource |
|
5272 { |
|
5273 return _private->promisedDragTIFFDataSource; |
|
5274 } |
|
5275 |
|
5276 - (void)setPromisedDragTIFFDataSource:(WebCore::CachedImage*)source |
|
5277 { |
|
5278 if (source) |
|
5279 source->ref(promisedDataClient()); |
|
5280 |
|
5281 if (_private->promisedDragTIFFDataSource) |
|
5282 _private->promisedDragTIFFDataSource->deref(promisedDataClient()); |
|
5283 _private->promisedDragTIFFDataSource = source; |
|
5284 } |
|
5285 |
|
5286 #undef COMMAND_PROLOGUE |
|
5287 |
|
5288 - (void)_layoutIfNeeded |
|
5289 { |
|
5290 ASSERT(!_private->subviewsSetAside); |
|
5291 |
|
5292 if ([[self _bridge] needsLayout]) |
|
5293 _private->needsLayout = YES; |
|
5294 if (_private->needsToApplyStyles || _private->needsLayout) |
|
5295 [self layout]; |
|
5296 } |
|
5297 |
|
5298 - (void)_web_layoutIfNeededRecursive |
|
5299 { |
|
5300 [self _layoutIfNeeded]; |
|
5301 |
|
5302 #ifndef NDEBUG |
|
5303 _private->enumeratingSubviews = YES; |
|
5304 #endif |
|
5305 |
|
5306 NSMutableArray *descendantWebHTMLViews = [[NSMutableArray alloc] init]; |
|
5307 |
|
5308 [self _web_addDescendantWebHTMLViewsToArray:descendantWebHTMLViews]; |
|
5309 |
|
5310 unsigned count = [descendantWebHTMLViews count]; |
|
5311 for (unsigned i = 0; i < count; ++i) |
|
5312 [[descendantWebHTMLViews objectAtIndex:i] _layoutIfNeeded]; |
|
5313 |
|
5314 [descendantWebHTMLViews release]; |
|
5315 |
|
5316 #ifndef NDEBUG |
|
5317 _private->enumeratingSubviews = NO; |
|
5318 #endif |
|
5319 } |
|
5320 |
|
5321 @end |
|
5322 |
|
5323 @implementation WebHTMLView (WebNSTextInputSupport) |
|
5324 |
|
5325 - (NSArray *)validAttributesForMarkedText |
|
5326 { |
|
5327 static NSArray *validAttributes; |
|
5328 if (!validAttributes) { |
|
5329 validAttributes = [[NSArray alloc] initWithObjects: |
|
5330 NSUnderlineStyleAttributeName, NSUnderlineColorAttributeName, |
|
5331 NSMarkedClauseSegmentAttributeName, NSTextInputReplacementRangeAttributeName, nil]; |
|
5332 // NSText also supports the following attributes, but it's |
|
5333 // hard to tell which are really required for text input to |
|
5334 // work well; I have not seen any input method make use of them yet. |
|
5335 // NSFontAttributeName, NSForegroundColorAttributeName, |
|
5336 // NSBackgroundColorAttributeName, NSLanguageAttributeName. |
|
5337 CFRetain(validAttributes); |
|
5338 } |
|
5339 LOG(TextInput, "validAttributesForMarkedText -> (...)"); |
|
5340 return validAttributes; |
|
5341 } |
|
5342 |
|
5343 // Utility function to make sure we don't return anything through the NSTextInput |
|
5344 // API when an editable region is not currently focused. |
|
5345 static BOOL isTextInput(Frame* coreFrame) |
|
5346 { |
|
5347 return coreFrame && !coreFrame->selectionController()->isNone() && coreFrame->selectionController()->isContentEditable(); |
|
5348 } |
|
5349 |
|
5350 - (NSAttributedString *)textStorage |
|
5351 { |
|
5352 if (!isTextInput(core([self _frame]))) { |
|
5353 LOG(TextInput, "textStorage -> nil"); |
|
5354 return nil; |
|
5355 } |
|
5356 NSAttributedString *result = [self attributedSubstringFromRange:NSMakeRange(0, UINT_MAX)]; |
|
5357 |
|
5358 LOG(TextInput, "textStorage -> \"%@\"", result ? [result string] : @""); |
|
5359 |
|
5360 // We have to return an empty string rather than null to prevent TSM from calling -string |
|
5361 return result ? result : [[[NSAttributedString alloc] initWithString:@""] autorelease]; |
|
5362 } |
|
5363 |
|
5364 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint |
|
5365 { |
|
5366 NSWindow *window = [self window]; |
|
5367 WebFrameBridge *bridge = [self _bridge]; |
|
5368 |
|
5369 if (window) |
|
5370 thePoint = [window convertScreenToBase:thePoint]; |
|
5371 thePoint = [self convertPoint:thePoint fromView:nil]; |
|
5372 |
|
5373 DOMRange *range = [bridge characterRangeAtPoint:thePoint]; |
|
5374 if (!range) { |
|
5375 LOG(TextInput, "characterIndexForPoint:(%f, %f) -> NSNotFound", thePoint.x, thePoint.y); |
|
5376 return NSNotFound; |
|
5377 } |
|
5378 |
|
5379 unsigned result = [bridge convertDOMRangeToNSRange:range].location; |
|
5380 LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", thePoint.x, thePoint.y, result); |
|
5381 return result; |
|
5382 } |
|
5383 |
|
5384 - (NSRect)firstRectForCharacterRange:(NSRange)theRange |
|
5385 { |
|
5386 WebFrameBridge *bridge = [self _bridge]; |
|
5387 |
|
5388 // Just to match NSTextView's behavior. Regression tests cannot detect this; |
|
5389 // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682 |
|
5390 // (type something; try ranges (1, -1) and (2, -1). |
|
5391 if ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0)) |
|
5392 theRange.length = 0; |
|
5393 |
|
5394 DOMRange *range = [bridge convertNSRangeToDOMRange:theRange]; |
|
5395 if (!range) { |
|
5396 LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (0, 0, 0, 0)", theRange.location, theRange.length); |
|
5397 return NSMakeRect(0, 0, 0, 0); |
|
5398 } |
|
5399 |
|
5400 ASSERT([range startContainer]); |
|
5401 ASSERT([range endContainer]); |
|
5402 |
|
5403 NSRect resultRect = [bridge firstRectForDOMRange:range]; |
|
5404 resultRect = [self convertRect:resultRect toView:nil]; |
|
5405 |
|
5406 NSWindow *window = [self window]; |
|
5407 if (window) |
|
5408 resultRect.origin = [window convertBaseToScreen:resultRect.origin]; |
|
5409 |
|
5410 LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (%f, %f, %f, %f)", theRange.location, theRange.length, resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height); |
|
5411 return resultRect; |
|
5412 } |
|
5413 |
|
5414 - (NSRange)selectedRange |
|
5415 { |
|
5416 if (!isTextInput(core([self _frame]))) { |
|
5417 LOG(TextInput, "selectedRange -> (NSNotFound, 0)"); |
|
5418 return NSMakeRange(NSNotFound, 0); |
|
5419 } |
|
5420 NSRange result = [[self _bridge] selectedNSRange]; |
|
5421 |
|
5422 LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length); |
|
5423 return result; |
|
5424 } |
|
5425 |
|
5426 - (NSRange)markedRange |
|
5427 { |
|
5428 NSRange result = [[self _bridge] markedTextNSRange]; |
|
5429 LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length); |
|
5430 return result; |
|
5431 } |
|
5432 |
|
5433 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange |
|
5434 { |
|
5435 if (!isTextInput(core([self _frame]))) { |
|
5436 LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); |
|
5437 return nil; |
|
5438 } |
|
5439 WebFrameBridge *bridge = [self _bridge]; |
|
5440 DOMRange *domRange = [bridge convertNSRangeToDOMRange:nsRange]; |
|
5441 if (!domRange) { |
|
5442 LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length); |
|
5443 return nil; |
|
5444 } |
|
5445 |
|
5446 NSAttributedString *result = [NSAttributedString _web_attributedStringFromRange:core(domRange)]; |
|
5447 |
|
5448 // [NSAttributedString(WebKitExtras) _web_attributedStringFromRange:] insists on inserting a trailing |
|
5449 // whitespace at the end of the string which breaks the ATOK input method. <rdar://problem/5400551> |
|
5450 // To work around this we truncate the resultant string to the correct length. |
|
5451 if ([result length] > nsRange.length) { |
|
5452 ASSERT([result length] == nsRange.length + 1); |
|
5453 ASSERT([[result string] characterAtIndex:nsRange.length] == '\n' || [[result string] characterAtIndex:nsRange.length] == ' '); |
|
5454 result = [result attributedSubstringFromRange:NSMakeRange(0, nsRange.length)]; |
|
5455 } |
|
5456 LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%@\"", nsRange.location, nsRange.length, [result string]); |
|
5457 return result; |
|
5458 } |
|
5459 |
|
5460 // test for 10.4 because of <rdar://problem/4243463> |
|
5461 #ifdef BUILDING_ON_TIGER |
|
5462 - (long)conversationIdentifier |
|
5463 { |
|
5464 return (long)self; |
|
5465 } |
|
5466 #else |
|
5467 - (NSInteger)conversationIdentifier |
|
5468 { |
|
5469 return (NSInteger)self; |
|
5470 } |
|
5471 #endif |
|
5472 |
|
5473 - (BOOL)hasMarkedText |
|
5474 { |
|
5475 Frame* coreFrame = core([self _frame]); |
|
5476 BOOL result = coreFrame && coreFrame->editor()->hasComposition(); |
|
5477 LOG(TextInput, "hasMarkedText -> %u", result); |
|
5478 return result; |
|
5479 } |
|
5480 |
|
5481 - (void)unmarkText |
|
5482 { |
|
5483 LOG(TextInput, "unmarkText"); |
|
5484 |
|
5485 // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. |
|
5486 WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; |
|
5487 _private->interpretKeyEventsParameters = 0; |
|
5488 |
|
5489 if (parameters) { |
|
5490 parameters->eventWasHandled = YES; |
|
5491 parameters->consumedByIM = NO; |
|
5492 } |
|
5493 |
|
5494 if (Frame* coreFrame = core([self _frame])) |
|
5495 coreFrame->editor()->confirmComposition(); |
|
5496 } |
|
5497 |
|
5498 static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnderline>& result) |
|
5499 { |
|
5500 int length = [[string string] length]; |
|
5501 |
|
5502 int i = 0; |
|
5503 while (i < length) { |
|
5504 NSRange range; |
|
5505 NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&range inRange:NSMakeRange(i, length - i)]; |
|
5506 |
|
5507 if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) { |
|
5508 Color color = Color::black; |
|
5509 if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName]) |
|
5510 color = colorFromNSColor([colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]); |
|
5511 result.append(CompositionUnderline(range.location, NSMaxRange(range), color, [style intValue] > 1)); |
|
5512 } |
|
5513 |
|
5514 i = range.location + range.length; |
|
5515 } |
|
5516 } |
|
5517 |
|
5518 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange |
|
5519 { |
|
5520 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString |
|
5521 |
|
5522 LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelRange.location, newSelRange.length); |
|
5523 |
|
5524 // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. |
|
5525 WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; |
|
5526 _private->interpretKeyEventsParameters = 0; |
|
5527 |
|
5528 if (parameters) { |
|
5529 parameters->eventWasHandled = YES; |
|
5530 parameters->consumedByIM = NO; |
|
5531 } |
|
5532 |
|
5533 Frame* coreFrame = core([self _frame]); |
|
5534 if (!coreFrame) |
|
5535 return; |
|
5536 |
|
5537 if (![self _isEditable]) |
|
5538 return; |
|
5539 |
|
5540 Vector<CompositionUnderline> underlines; |
|
5541 NSString *text = string; |
|
5542 |
|
5543 if (isAttributedString) { |
|
5544 unsigned markedTextLength = [(NSString *)string length]; |
|
5545 NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, markedTextLength)]; |
|
5546 LOG(TextInput, " ReplacementRange: %@", rangeString); |
|
5547 // The AppKit adds a 'secret' property to the string that contains the replacement range. |
|
5548 // The replacement range is the range of the the text that should be replaced with the new string. |
|
5549 if (rangeString) |
|
5550 [[self _bridge] selectNSRange:NSRangeFromString(rangeString)]; |
|
5551 |
|
5552 text = [string string]; |
|
5553 extractUnderlines(string, underlines); |
|
5554 } |
|
5555 |
|
5556 coreFrame->editor()->setComposition(text, underlines, newSelRange.location, NSMaxRange(newSelRange)); |
|
5557 } |
|
5558 |
|
5559 - (void)doCommandBySelector:(SEL)selector |
|
5560 { |
|
5561 LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector)); |
|
5562 |
|
5563 // Use pointer to get parameters passed to us by the caller of interpretKeyEvents. |
|
5564 // The same call to interpretKeyEvents can do more than one command. |
|
5565 WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; |
|
5566 if (parameters) |
|
5567 parameters->consumedByIM = NO; |
|
5568 |
|
5569 if (selector == @selector(noop:)) { |
|
5570 _private->receivedNOOP = YES; |
|
5571 return; |
|
5572 } |
|
5573 |
|
5574 KeyboardEvent* event = parameters ? parameters->event : 0; |
|
5575 bool shouldSaveCommand = parameters && parameters->shouldSaveCommand; |
|
5576 |
|
5577 if (event && shouldSaveCommand) { |
|
5578 KeypressCommand command = event->keypressCommand(); |
|
5579 command.commandNames.append(NSStringFromSelector(selector)); |
|
5580 event->setKeypressCommand(command); |
|
5581 } else { |
|
5582 // Make sure that only direct calls to doCommandBySelector: see the parameters by setting to 0. |
|
5583 _private->interpretKeyEventsParameters = 0; |
|
5584 |
|
5585 bool eventWasHandled = true; |
|
5586 |
|
5587 WebView *webView = [self _webView]; |
|
5588 Frame* coreFrame = core([self _frame]); |
|
5589 if (![[webView _editingDelegateForwarder] webView:webView doCommandBySelector:selector] && coreFrame) { |
|
5590 if (selector == @selector(insertNewline:) || selector == @selector(insertParagraphSeparator:) || selector == @selector(insertNewlineIgnoringFieldEditor:)) |
|
5591 eventWasHandled = coreFrame->editor()->execCommand("InsertNewline", event); |
|
5592 else if (selector == @selector(insertLineBreak:)) |
|
5593 eventWasHandled = coreFrame->editor()->execCommand("InsertLineBreak", event); |
|
5594 else if (selector == @selector(insertTab:) || selector == @selector(insertTabIgnoringFieldEditor:)) |
|
5595 eventWasHandled = coreFrame->editor()->execCommand("InsertTab", event); |
|
5596 else if (selector == @selector(insertBacktab:)) |
|
5597 eventWasHandled = coreFrame->editor()->execCommand("InsertBacktab", event); |
|
5598 else { |
|
5599 _private->selectorForDoCommandBySelector = selector; |
|
5600 [super doCommandBySelector:selector]; |
|
5601 _private->selectorForDoCommandBySelector = 0; |
|
5602 } |
|
5603 } |
|
5604 |
|
5605 if (parameters) |
|
5606 parameters->eventWasHandled = eventWasHandled; |
|
5607 |
|
5608 // Restore the parameters so that other calls to doCommandBySelector: see them, |
|
5609 // and other commands can participate in setting the "eventWasHandled" flag. |
|
5610 _private->interpretKeyEventsParameters = parameters; |
|
5611 } |
|
5612 } |
|
5613 |
|
5614 - (void)insertText:(id)string |
|
5615 { |
|
5616 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString |
|
5617 |
|
5618 LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string); |
|
5619 |
|
5620 WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters; |
|
5621 _private->interpretKeyEventsParameters = 0; |
|
5622 if (parameters) |
|
5623 parameters->consumedByIM = NO; |
|
5624 |
|
5625 // We don't support inserting an attributed string but input methods don't appear to require this. |
|
5626 Frame* coreFrame = core([self _frame]); |
|
5627 NSString *text; |
|
5628 bool isFromInputMethod = coreFrame->editor()->hasComposition(); |
|
5629 if (isAttributedString) { |
|
5630 text = [string string]; |
|
5631 // We deal with the NSTextInputReplacementRangeAttributeName attribute from NSAttributedString here |
|
5632 // simply because it is used by at least one Input Method -- it corresonds to the kEventParamTextInputSendReplaceRange |
|
5633 // event in TSM. This behaviour matches that of -[WebHTMLView setMarkedText:selectedRange:] when it receives an |
|
5634 // NSAttributedString |
|
5635 NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, [text length])]; |
|
5636 LOG(TextInput, " ReplacementRange: %@", rangeString); |
|
5637 if (rangeString) { |
|
5638 [[self _bridge] selectNSRange:NSRangeFromString(rangeString)]; |
|
5639 isFromInputMethod = YES; |
|
5640 } |
|
5641 } else |
|
5642 text = string; |
|
5643 |
|
5644 bool eventHandled = false; |
|
5645 if ([text length]) { |
|
5646 KeyboardEvent* event = parameters ? parameters->event : 0; |
|
5647 |
|
5648 // insertText can be called from an input method or from normal key event processing |
|
5649 // If its from normal key event processing, we may need to save the action to perform it later. |
|
5650 // If its from an input method, then we should go ahead and insert the text now. |
|
5651 // We assume it's from the input method if we have marked text. |
|
5652 // FIXME: In theory, this could be wrong for some input methods, so we should try to find |
|
5653 // another way to determine if the call is from the input method |
|
5654 bool shouldSaveCommand = parameters && parameters->shouldSaveCommand; |
|
5655 if (event && shouldSaveCommand && !isFromInputMethod) { |
|
5656 KeypressCommand command; |
|
5657 command.text = text; |
|
5658 event->setKeypressCommand(command); |
|
5659 return; |
|
5660 } |
|
5661 |
|
5662 String eventText = text; |
|
5663 eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore |
|
5664 if (coreFrame) { |
|
5665 if (!coreFrame->editor()->hasComposition()) |
|
5666 eventHandled = coreFrame->editor()->insertText(eventText, event); |
|
5667 else { |
|
5668 eventHandled = true; |
|
5669 coreFrame->editor()->confirmComposition(eventText); |
|
5670 } |
|
5671 } |
|
5672 } |
|
5673 |
|
5674 if (!parameters) |
|
5675 return; |
|
5676 |
|
5677 if (isFromInputMethod) { |
|
5678 // Allow doCommandBySelector: to be called after insertText: by resetting interpretKeyEventsParameters |
|
5679 _private->interpretKeyEventsParameters = parameters; |
|
5680 parameters->consumedByIM = YES; |
|
5681 return; |
|
5682 } |
|
5683 |
|
5684 parameters->eventWasHandled = eventHandled; |
|
5685 } |
|
5686 |
|
5687 - (void)_updateSelectionForInputManager |
|
5688 { |
|
5689 Frame* coreFrame = core([self _frame]); |
|
5690 if (!coreFrame) |
|
5691 return; |
|
5692 |
|
5693 if (!coreFrame->editor()->hasComposition()) |
|
5694 return; |
|
5695 |
|
5696 if (coreFrame->editor()->ignoreCompositionSelectionChange()) |
|
5697 return; |
|
5698 |
|
5699 unsigned start; |
|
5700 unsigned end; |
|
5701 if (coreFrame->editor()->getCompositionSelection(start, end)) |
|
5702 [[NSInputManager currentInputManager] markedTextSelectionChanged:NSMakeRange(start, end - start) client:self]; |
|
5703 else { |
|
5704 coreFrame->editor()->confirmCompositionWithoutDisturbingSelection(); |
|
5705 [[NSInputManager currentInputManager] markedTextAbandoned:self]; |
|
5706 } |
|
5707 } |
|
5708 |
|
5709 @end |
|
5710 |
|
5711 /* |
|
5712 This class runs the show for handing the complete: NSTextView operation. It counts on its HTML view |
|
5713 to call endRevertingChange: whenever the current completion needs to be aborted. |
|
5714 |
|
5715 The class is in one of two modes: PopupWindow showing, or not. It is shown when a completion yields |
|
5716 more than one match. If a completion yields one or zero matches, it is not shown, and **there is no |
|
5717 state carried across to the next completion**. |
|
5718 */ |
|
5719 |
|
5720 @implementation WebTextCompleteController |
|
5721 |
|
5722 - (id)initWithHTMLView:(WebHTMLView *)view |
|
5723 { |
|
5724 self = [super init]; |
|
5725 if (!self) |
|
5726 return nil; |
|
5727 _view = view; |
|
5728 return self; |
|
5729 } |
|
5730 |
|
5731 - (void)dealloc |
|
5732 { |
|
5733 [_popupWindow release]; |
|
5734 [_completions release]; |
|
5735 [_originalString release]; |
|
5736 |
|
5737 [super dealloc]; |
|
5738 } |
|
5739 |
|
5740 - (void)_insertMatch:(NSString *)match |
|
5741 { |
|
5742 // FIXME: 3769654 - We should preserve case of string being inserted, even in prefix (but then also be |
|
5743 // able to revert that). Mimic NSText. |
|
5744 WebFrameBridge *bridge = [_view _bridge]; |
|
5745 NSString *newText = [match substringFromIndex:prefixLength]; |
|
5746 [bridge replaceSelectionWithText:newText selectReplacement:YES smartReplace:NO]; |
|
5747 } |
|
5748 |
|
5749 // mostly lifted from NSTextView_KeyBinding.m |
|
5750 - (void)_buildUI |
|
5751 { |
|
5752 NSRect scrollFrame = NSMakeRect(0, 0, 100, 100); |
|
5753 NSRect tableFrame = NSZeroRect; |
|
5754 tableFrame.size = [NSScrollView contentSizeForFrameSize:scrollFrame.size hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSNoBorder]; |
|
5755 // Added cast to work around problem with multiple Foundation initWithIdentifier: methods with different parameter types. |
|
5756 NSTableColumn *column = [(NSTableColumn *)[NSTableColumn alloc] initWithIdentifier:[NSNumber numberWithInt:0]]; |
|
5757 [column setWidth:tableFrame.size.width]; |
|
5758 [column setEditable:NO]; |
|
5759 |
|
5760 _tableView = [[NSTableView alloc] initWithFrame:tableFrame]; |
|
5761 [_tableView setAutoresizingMask:NSViewWidthSizable]; |
|
5762 [_tableView addTableColumn:column]; |
|
5763 [column release]; |
|
5764 [_tableView setDrawsGrid:NO]; |
|
5765 [_tableView setCornerView:nil]; |
|
5766 [_tableView setHeaderView:nil]; |
|
5767 [_tableView setColumnAutoresizingStyle:NSTableViewUniformColumnAutoresizingStyle]; |
|
5768 [_tableView setDelegate:self]; |
|
5769 [_tableView setDataSource:self]; |
|
5770 [_tableView setTarget:self]; |
|
5771 [_tableView setDoubleAction:@selector(tableAction:)]; |
|
5772 |
|
5773 NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:scrollFrame]; |
|
5774 [scrollView setBorderType:NSNoBorder]; |
|
5775 [scrollView setHasVerticalScroller:YES]; |
|
5776 [scrollView setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; |
|
5777 [scrollView setDocumentView:_tableView]; |
|
5778 [_tableView release]; |
|
5779 |
|
5780 _popupWindow = [[NSWindow alloc] initWithContentRect:scrollFrame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]; |
|
5781 [_popupWindow setAlphaValue:0.88f]; |
|
5782 [_popupWindow setContentView:scrollView]; |
|
5783 [scrollView release]; |
|
5784 [_popupWindow setHasShadow:YES]; |
|
5785 [_popupWindow setOneShot:YES]; |
|
5786 [_popupWindow _setForceActiveControls:YES]; |
|
5787 [_popupWindow setReleasedWhenClosed:NO]; |
|
5788 } |
|
5789 |
|
5790 // mostly lifted from NSTextView_KeyBinding.m |
|
5791 - (void)_placePopupWindow:(NSPoint)topLeft |
|
5792 { |
|
5793 int numberToShow = [_completions count]; |
|
5794 if (numberToShow > 20) { |
|
5795 numberToShow = 20; |
|
5796 } |
|
5797 |
|
5798 NSRect windowFrame; |
|
5799 NSPoint wordStart = topLeft; |
|
5800 windowFrame.origin = [[_view window] convertBaseToScreen:[_view convertPoint:wordStart toView:nil]]; |
|
5801 windowFrame.size.height = numberToShow * [_tableView rowHeight] + (numberToShow + 1) * [_tableView intercellSpacing].height; |
|
5802 windowFrame.origin.y -= windowFrame.size.height; |
|
5803 NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:[NSFont systemFontOfSize:12.0f], NSFontAttributeName, nil]; |
|
5804 float maxWidth = 0.0f; |
|
5805 int maxIndex = -1; |
|
5806 int i; |
|
5807 for (i = 0; i < numberToShow; i++) { |
|
5808 float width = ceilf([[_completions objectAtIndex:i] sizeWithAttributes:attributes].width); |
|
5809 if (width > maxWidth) { |
|
5810 maxWidth = width; |
|
5811 maxIndex = i; |
|
5812 } |
|
5813 } |
|
5814 windowFrame.size.width = 100; |
|
5815 if (maxIndex >= 0) { |
|
5816 maxWidth = ceilf([NSScrollView frameSizeForContentSize:NSMakeSize(maxWidth, 100.0f) hasHorizontalScroller:NO hasVerticalScroller:YES borderType:NSNoBorder].width); |
|
5817 maxWidth = ceilf([NSWindow frameRectForContentRect:NSMakeRect(0.0f, 0.0f, maxWidth, 100.0f) styleMask:NSBorderlessWindowMask].size.width); |
|
5818 maxWidth += 5.0f; |
|
5819 windowFrame.size.width = MAX(maxWidth, windowFrame.size.width); |
|
5820 maxWidth = MIN(400.0f, windowFrame.size.width); |
|
5821 } |
|
5822 [_popupWindow setFrame:windowFrame display:NO]; |
|
5823 |
|
5824 [_tableView reloadData]; |
|
5825 [_tableView selectRow:0 byExtendingSelection:NO]; |
|
5826 [_tableView scrollRowToVisible:0]; |
|
5827 [self _reflectSelection]; |
|
5828 [_popupWindow setLevel:NSPopUpMenuWindowLevel]; |
|
5829 [_popupWindow orderFront:nil]; |
|
5830 [[_view window] addChildWindow:_popupWindow ordered:NSWindowAbove]; |
|
5831 } |
|
5832 |
|
5833 - (void)doCompletion |
|
5834 { |
|
5835 if (!_popupWindow) { |
|
5836 NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker]; |
|
5837 if (!checker) { |
|
5838 LOG_ERROR("No NSSpellChecker"); |
|
5839 return; |
|
5840 } |
|
5841 |
|
5842 // Get preceeding word stem |
|
5843 WebFrameBridge *bridge = [_view _bridge]; |
|
5844 DOMRange *selection = kit(core([_view _frame])->selectionController()->toRange().get()); |
|
5845 DOMRange *wholeWord = [bridge rangeByAlteringCurrentSelection:SelectionController::EXTEND |
|
5846 direction:SelectionController::BACKWARD granularity:WordGranularity]; |
|
5847 DOMRange *prefix = [wholeWord cloneRange]; |
|
5848 [prefix setEnd:[selection startContainer] offset:[selection startOffset]]; |
|
5849 |
|
5850 // Reject some NOP cases |
|
5851 if ([prefix collapsed]) { |
|
5852 NSBeep(); |
|
5853 return; |
|
5854 } |
|
5855 NSString *prefixStr = [bridge stringForRange:prefix]; |
|
5856 NSString *trimmedPrefix = [prefixStr stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
|
5857 if ([trimmedPrefix length] == 0) { |
|
5858 NSBeep(); |
|
5859 return; |
|
5860 } |
|
5861 prefixLength = [prefixStr length]; |
|
5862 |
|
5863 // Lookup matches |
|
5864 [_completions release]; |
|
5865 _completions = [checker completionsForPartialWordRange:NSMakeRange(0, [prefixStr length]) inString:prefixStr language:nil inSpellDocumentWithTag:[[_view _webView] spellCheckerDocumentTag]]; |
|
5866 [_completions retain]; |
|
5867 |
|
5868 if (!_completions || [_completions count] == 0) { |
|
5869 NSBeep(); |
|
5870 } else if ([_completions count] == 1) { |
|
5871 [self _insertMatch:[_completions objectAtIndex:0]]; |
|
5872 } else { |
|
5873 ASSERT(!_originalString); // this should only be set IFF we have a popup window |
|
5874 _originalString = [[bridge stringForRange:selection] retain]; |
|
5875 [self _buildUI]; |
|
5876 NSRect wordRect = [bridge caretRectAtNode:[wholeWord startContainer] offset:[wholeWord startOffset] affinity:NSSelectionAffinityDownstream]; |
|
5877 // +1 to be under the word, not the caret |
|
5878 // FIXME - 3769652 - Wrong positioning for right to left languages. We should line up the upper |
|
5879 // right corner with the caret instead of upper left, and the +1 would be a -1. |
|
5880 NSPoint wordLowerLeft = { NSMinX(wordRect)+1, NSMaxY(wordRect) }; |
|
5881 [self _placePopupWindow:wordLowerLeft]; |
|
5882 } |
|
5883 } else { |
|
5884 [self endRevertingChange:YES moveLeft:NO]; |
|
5885 } |
|
5886 } |
|
5887 |
|
5888 - (void)endRevertingChange:(BOOL)revertChange moveLeft:(BOOL)goLeft |
|
5889 { |
|
5890 if (_popupWindow) { |
|
5891 // tear down UI |
|
5892 [[_view window] removeChildWindow:_popupWindow]; |
|
5893 [_popupWindow orderOut:self]; |
|
5894 // Must autorelease because event tracking code may be on the stack touching UI |
|
5895 [_popupWindow autorelease]; |
|
5896 _popupWindow = nil; |
|
5897 |
|
5898 if (revertChange) { |
|
5899 WebFrameBridge *bridge = [_view _bridge]; |
|
5900 [bridge replaceSelectionWithText:_originalString selectReplacement:YES smartReplace:NO]; |
|
5901 } else if ([_view _hasSelection]) { |
|
5902 if (goLeft) |
|
5903 [_view moveBackward:nil]; |
|
5904 else |
|
5905 [_view moveForward:nil]; |
|
5906 } |
|
5907 [_originalString release]; |
|
5908 _originalString = nil; |
|
5909 } |
|
5910 // else there is no state to abort if the window was not up |
|
5911 } |
|
5912 |
|
5913 - (BOOL)popupWindowIsOpen |
|
5914 { |
|
5915 return _popupWindow != nil; |
|
5916 } |
|
5917 |
|
5918 // WebHTMLView gives us a crack at key events it sees. Return whether we consumed the event. |
|
5919 // The features for the various keys mimic NSTextView. |
|
5920 - (BOOL)filterKeyDown:(NSEvent *)event |
|
5921 { |
|
5922 if (_popupWindow) { |
|
5923 NSString *string = [event charactersIgnoringModifiers]; |
|
5924 unichar c = [string characterAtIndex:0]; |
|
5925 if (c == NSUpArrowFunctionKey) { |
|
5926 int selectedRow = [_tableView selectedRow]; |
|
5927 if (0 < selectedRow) { |
|
5928 [_tableView selectRow:selectedRow-1 byExtendingSelection:NO]; |
|
5929 [_tableView scrollRowToVisible:selectedRow-1]; |
|
5930 } |
|
5931 return YES; |
|
5932 } else if (c == NSDownArrowFunctionKey) { |
|
5933 int selectedRow = [_tableView selectedRow]; |
|
5934 if (selectedRow < (int)[_completions count]-1) { |
|
5935 [_tableView selectRow:selectedRow+1 byExtendingSelection:NO]; |
|
5936 [_tableView scrollRowToVisible:selectedRow+1]; |
|
5937 } |
|
5938 return YES; |
|
5939 } else if (c == NSRightArrowFunctionKey || c == '\n' || c == '\r' || c == '\t') { |
|
5940 [self endRevertingChange:NO moveLeft:NO]; |
|
5941 return YES; |
|
5942 } else if (c == NSLeftArrowFunctionKey) { |
|
5943 [self endRevertingChange:NO moveLeft:YES]; |
|
5944 return YES; |
|
5945 } else if (c == 0x1b || c == NSF5FunctionKey) { |
|
5946 [self endRevertingChange:YES moveLeft:NO]; |
|
5947 return YES; |
|
5948 } else if (c == ' ' || ispunct(c)) { |
|
5949 [self endRevertingChange:NO moveLeft:NO]; |
|
5950 return NO; // let the char get inserted |
|
5951 } |
|
5952 } |
|
5953 return NO; |
|
5954 } |
|
5955 |
|
5956 - (void)_reflectSelection |
|
5957 { |
|
5958 int selectedRow = [_tableView selectedRow]; |
|
5959 ASSERT(selectedRow >= 0 && selectedRow < (int)[_completions count]); |
|
5960 [self _insertMatch:[_completions objectAtIndex:selectedRow]]; |
|
5961 } |
|
5962 |
|
5963 - (void)tableAction:(id)sender |
|
5964 { |
|
5965 [self _reflectSelection]; |
|
5966 [self endRevertingChange:NO moveLeft:NO]; |
|
5967 } |
|
5968 |
|
5969 - (int)numberOfRowsInTableView:(NSTableView *)tableView |
|
5970 { |
|
5971 return [_completions count]; |
|
5972 } |
|
5973 |
|
5974 - (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row |
|
5975 { |
|
5976 return [_completions objectAtIndex:row]; |
|
5977 } |
|
5978 |
|
5979 - (void)tableViewSelectionDidChange:(NSNotification *)notification |
|
5980 { |
|
5981 [self _reflectSelection]; |
|
5982 } |
|
5983 |
|
5984 @end |
|
5985 |
|
5986 @implementation WebHTMLView (WebDocumentPrivateProtocols) |
|
5987 |
|
5988 - (NSRect)selectionRect |
|
5989 { |
|
5990 if ([self _hasSelection]) |
|
5991 return core([self _frame])->selectionRect(); |
|
5992 return NSZeroRect; |
|
5993 } |
|
5994 |
|
5995 - (NSArray *)selectionTextRects |
|
5996 { |
|
5997 if (![self _hasSelection]) |
|
5998 return nil; |
|
5999 |
|
6000 Vector<FloatRect> list; |
|
6001 core([self _frame])->selectionTextRects(list); |
|
6002 |
|
6003 unsigned size = list.size(); |
|
6004 NSMutableArray *result = [[[NSMutableArray alloc] initWithCapacity:size] autorelease]; |
|
6005 for (unsigned i = 0; i < size; ++i) |
|
6006 [result addObject:[NSValue valueWithRect:list[i]]]; |
|
6007 |
|
6008 return result; |
|
6009 } |
|
6010 |
|
6011 - (NSView *)selectionView |
|
6012 { |
|
6013 return self; |
|
6014 } |
|
6015 |
|
6016 - (NSImage *)selectionImageForcingBlackText:(BOOL)forceBlackText |
|
6017 { |
|
6018 if ([self _hasSelection]) |
|
6019 return core([self _frame])->selectionImage(forceBlackText); |
|
6020 return nil; |
|
6021 } |
|
6022 |
|
6023 - (NSImage *)selectionImageForcingWhiteText:(BOOL)forceWhiteText |
|
6024 { |
|
6025 // NOTE: this method is obsolete and doesn't behave as its name suggests. |
|
6026 // See comment in WebDocumentPrivate.h. |
|
6027 return [self selectionImageForcingBlackText:forceWhiteText]; |
|
6028 } |
|
6029 |
|
6030 - (NSRect)selectionImageRect |
|
6031 { |
|
6032 if ([self _hasSelection]) |
|
6033 return core([self _frame])->selectionRect(); |
|
6034 return NSZeroRect; |
|
6035 } |
|
6036 |
|
6037 - (NSArray *)pasteboardTypesForSelection |
|
6038 { |
|
6039 if ([self _canSmartCopyOrDelete]) { |
|
6040 NSMutableArray *types = [[[[self class] _selectionPasteboardTypes] mutableCopy] autorelease]; |
|
6041 [types addObject:WebSmartPastePboardType]; |
|
6042 return types; |
|
6043 } else { |
|
6044 return [[self class] _selectionPasteboardTypes]; |
|
6045 } |
|
6046 } |
|
6047 |
|
6048 - (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard |
|
6049 { |
|
6050 [self _writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard cachedAttributedString:nil]; |
|
6051 } |
|
6052 |
|
6053 - (void)selectAll |
|
6054 { |
|
6055 Frame* coreFrame = core([self _frame]); |
|
6056 if (coreFrame) |
|
6057 coreFrame->selectionController()->selectAll(); |
|
6058 } |
|
6059 |
|
6060 - (void)deselectAll |
|
6061 { |
|
6062 Frame* coreFrame = core([self _frame]); |
|
6063 if (!coreFrame) |
|
6064 return; |
|
6065 coreFrame->selectionController()->clear(); |
|
6066 } |
|
6067 |
|
6068 - (NSString *)string |
|
6069 { |
|
6070 return [[self _bridge] stringForRange:[self _documentRange]]; |
|
6071 } |
|
6072 |
|
6073 - (NSAttributedString *)_attributeStringFromDOMRange:(DOMRange *)range |
|
6074 { |
|
6075 NSAttributedString *attributedString; |
|
6076 #if !LOG_DISABLED |
|
6077 double start = CFAbsoluteTimeGetCurrent(); |
|
6078 #endif |
|
6079 attributedString = [[[NSAttributedString alloc] _initWithDOMRange:range] autorelease]; |
|
6080 #if !LOG_DISABLED |
|
6081 double duration = CFAbsoluteTimeGetCurrent() - start; |
|
6082 LOG(Timing, "creating attributed string from selection took %f seconds.", duration); |
|
6083 #endif |
|
6084 return attributedString; |
|
6085 } |
|
6086 |
|
6087 - (NSAttributedString *)attributedString |
|
6088 { |
|
6089 DOMDocument *document = [[self _frame] DOMDocument]; |
|
6090 NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[document _documentRange]]; |
|
6091 if (!attributedString) { |
|
6092 Document* coreDocument = core(document); |
|
6093 Range range(coreDocument, coreDocument, 0, 0, 0); |
|
6094 attributedString = [NSAttributedString _web_attributedStringFromRange:&range]; |
|
6095 } |
|
6096 return attributedString; |
|
6097 } |
|
6098 |
|
6099 - (NSString *)selectedString |
|
6100 { |
|
6101 return [[self _bridge] selectedString]; |
|
6102 } |
|
6103 |
|
6104 - (NSAttributedString *)selectedAttributedString |
|
6105 { |
|
6106 NSAttributedString *attributedString = [self _attributeStringFromDOMRange:[self _selectedRange]]; |
|
6107 if (!attributedString) { |
|
6108 Frame* coreFrame = core([self _frame]); |
|
6109 if (coreFrame) { |
|
6110 RefPtr<Range> range = coreFrame->selectionController()->selection().toRange(); |
|
6111 attributedString = [NSAttributedString _web_attributedStringFromRange:range.get()]; |
|
6112 } |
|
6113 } |
|
6114 return attributedString; |
|
6115 } |
|
6116 |
|
6117 - (BOOL)supportsTextEncoding |
|
6118 { |
|
6119 return YES; |
|
6120 } |
|
6121 |
|
6122 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag startInSelection:(BOOL)startInSelection |
|
6123 { |
|
6124 if (![string length]) |
|
6125 return NO; |
|
6126 |
|
6127 return [[self _bridge] searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag startInSelection:startInSelection]; |
|
6128 } |
|
6129 |
|
6130 @end |
|
6131 |
|
6132 @implementation WebHTMLView (WebDocumentInternalProtocols) |
|
6133 |
|
6134 - (NSDictionary *)elementAtPoint:(NSPoint)point |
|
6135 { |
|
6136 return [self elementAtPoint:point allowShadowContent:NO]; |
|
6137 } |
|
6138 |
|
6139 - (NSDictionary *)elementAtPoint:(NSPoint)point allowShadowContent:(BOOL)allow; |
|
6140 { |
|
6141 Frame* coreframe = core([self _frame]); |
|
6142 if (coreframe) |
|
6143 return [[[WebElementDictionary alloc] initWithHitTestResult:coreframe->eventHandler()->hitTestResultAtPoint(IntPoint(point), allow)] autorelease]; |
|
6144 return nil; |
|
6145 } |
|
6146 |
|
6147 - (NSUInteger)markAllMatchesForText:(NSString *)string caseSensitive:(BOOL)caseFlag limit:(NSUInteger)limit |
|
6148 { |
|
6149 return [[self _bridge] markAllMatchesForText:string caseSensitive:caseFlag limit:limit]; |
|
6150 } |
|
6151 |
|
6152 - (void)setMarkedTextMatchesAreHighlighted:(BOOL)newValue |
|
6153 { |
|
6154 [[self _bridge] setMarkedTextMatchesAreHighlighted:newValue]; |
|
6155 } |
|
6156 |
|
6157 - (BOOL)markedTextMatchesAreHighlighted |
|
6158 { |
|
6159 return [[self _bridge] markedTextMatchesAreHighlighted]; |
|
6160 } |
|
6161 |
|
6162 - (void)unmarkAllTextMatches |
|
6163 { |
|
6164 return [[self _bridge] unmarkAllTextMatches]; |
|
6165 } |
|
6166 |
|
6167 - (NSArray *)rectsForTextMatches |
|
6168 { |
|
6169 return [[self _bridge] rectsForTextMatches]; |
|
6170 } |
|
6171 |
|
6172 @end |
|
6173 |
|
6174 // This is used by AppKit and is included here so that WebDataProtocolScheme is only defined once. |
|
6175 @implementation NSURL (WebDataURL) |
|
6176 |
|
6177 + (NSURL *)_web_uniqueWebDataURL |
|
6178 { |
|
6179 CFUUIDRef UUIDRef = CFUUIDCreate(kCFAllocatorDefault); |
|
6180 NSString *UUIDString = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, UUIDRef); |
|
6181 CFRelease(UUIDRef); |
|
6182 NSURL *URL = [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@", WebDataProtocolScheme, UUIDString]]; |
|
6183 CFRelease(UUIDString); |
|
6184 return URL; |
|
6185 } |
|
6186 |
|
6187 @end |