1/*
2 * Copyright (C) 2007, 2009 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "DragImage.h"
28
29#if ENABLE(DRAG_SUPPORT)
30#import "CachedImage.h"
31#import "Font.h"
32#import "FontDescription.h"
33#import "FontSelector.h"
34#import "GraphicsContext.h"
35#import "Image.h"
36#import "KURL.h"
37#import "ResourceResponse.h"
38#import "Settings.h"
39#import "StringTruncator.h"
40#import "TextRun.h"
41
42namespace WebCore {
43
44IntSize dragImageSize(RetainPtr<NSImage> image)
45{
46    return (IntSize)[image.get() size];
47}
48
49void deleteDragImage(RetainPtr<NSImage>)
50{
51    // Since this is a RetainPtr, there's nothing additional we need to do to
52    // delete it. It will be released when it falls out of scope.
53}
54
55RetainPtr<NSImage> scaleDragImage(RetainPtr<NSImage> image, FloatSize scale)
56{
57    NSSize originalSize = [image.get() size];
58    NSSize newSize = NSMakeSize((originalSize.width * scale.width()), (originalSize.height * scale.height()));
59    newSize.width = roundf(newSize.width);
60    newSize.height = roundf(newSize.height);
61    [image.get() setScalesWhenResized:YES];
62    [image.get() setSize:newSize];
63    return image;
64}
65
66RetainPtr<NSImage> dissolveDragImageToFraction(RetainPtr<NSImage> image, float delta)
67{
68    RetainPtr<NSImage> dissolvedImage(AdoptNS, [[NSImage alloc] initWithSize:[image.get() size]]);
69
70    NSPoint point = [image.get() isFlipped] ? NSMakePoint(0, [image.get() size].height) : NSZeroPoint;
71
72    // In this case the dragging image is always correct.
73    [dissolvedImage.get() setFlipped:[image.get() isFlipped]];
74
75    [dissolvedImage.get() lockFocus];
76    [image.get() dissolveToPoint:point fraction: delta];
77    [dissolvedImage.get() unlockFocus];
78
79    [image.get() lockFocus];
80    [dissolvedImage.get() compositeToPoint:point operation:NSCompositeCopy];
81    [image.get() unlockFocus];
82
83    return image;
84}
85
86RetainPtr<NSImage> createDragImageFromImage(Image* image)
87{
88    RetainPtr<NSImage> dragImage(AdoptNS, [image->getNSImage() copy]);
89    [dragImage.get() setSize:(NSSize)(image->size())];
90    return dragImage;
91}
92
93RetainPtr<NSImage> createDragImageIconForCachedImage(CachedImage* image)
94{
95    const String& filename = image->response().suggestedFilename();
96    NSString *extension = nil;
97    size_t dotIndex = filename.reverseFind('.');
98
99    if (dotIndex != notFound && dotIndex < (filename.length() - 1)) // require that a . exists after the first character and before the last
100        extension = filename.substring(dotIndex + 1);
101    else {
102        // It might be worth doing a further lookup to pull the extension from the MIME type.
103        extension = @"";
104    }
105
106    return [[NSWorkspace sharedWorkspace] iconForFileType:extension];
107}
108
109
110const float DragLabelBorderX = 4;
111//Keep border_y in synch with DragController::LinkDragBorderInset
112const float DragLabelBorderY = 2;
113const float DragLabelRadius = 5;
114const float LabelBorderYOffset = 2;
115
116const float MinDragLabelWidthBeforeClip = 120;
117const float MaxDragLabelWidth = 320;
118
119const float DragLinkLabelFontsize = 11;
120const float DragLinkUrlFontSize = 10;
121
122// FIXME - we should move all the functionality of NSString extras to WebCore
123
124static Font& fontFromNSFont(NSFont *font)
125{
126    static NSFont *currentFont;
127    DEFINE_STATIC_LOCAL(Font, currentRenderer, ());
128
129    if ([font isEqual:currentFont])
130        return currentRenderer;
131    if (currentFont)
132        CFRelease(currentFont);
133    currentFont = font;
134    CFRetain(currentFont);
135    FontPlatformData f(font, [font pointSize]);
136    currentRenderer = Font(f, ![[NSGraphicsContext currentContext] isDrawingToScreen]);
137    return currentRenderer;
138}
139
140static bool canUseFastRenderer(const UniChar* buffer, unsigned length)
141{
142    unsigned i;
143    for (i = 0; i < length; i++) {
144        UCharDirection direction = u_charDirection(buffer[i]);
145        if (direction == U_RIGHT_TO_LEFT || direction > U_OTHER_NEUTRAL)
146            return false;
147    }
148    return true;
149}
150
151static float widthWithFont(NSString *string, NSFont *font)
152{
153    unsigned length = [string length];
154    Vector<UniChar, 2048> buffer(length);
155
156    [string getCharacters:buffer.data()];
157
158    if (canUseFastRenderer(buffer.data(), length)) {
159        Font webCoreFont(FontPlatformData(font, [font pointSize]), ![[NSGraphicsContext currentContext] isDrawingToScreen]);
160        TextRun run(buffer.data(), length);
161        return webCoreFont.width(run);
162    }
163
164    return [string sizeWithAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil]].width;
165}
166
167static inline CGFloat webkit_CGCeiling(CGFloat value)
168{
169    if (sizeof(value) == sizeof(float))
170        return ceilf(value);
171    return static_cast<CGFloat>(ceil(value));
172}
173
174static void drawAtPoint(NSString *string, NSPoint point, NSFont *font, NSColor *textColor)
175{
176    unsigned length = [string length];
177    Vector<UniChar, 2048> buffer(length);
178
179    [string getCharacters:buffer.data()];
180
181    if (canUseFastRenderer(buffer.data(), length)) {
182        // The following is a half-assed attempt to match AppKit's rounding rules for drawAtPoint.
183        // It's probably incorrect for high DPI.
184        // If you change this, be sure to test all the text drawn this way in Safari, including
185        // the status bar, bookmarks bar, tab bar, and activity window.
186        point.y = webkit_CGCeiling(point.y);
187
188        NSGraphicsContext *nsContext = [NSGraphicsContext currentContext];
189        CGContextRef cgContext = static_cast<CGContextRef>([nsContext graphicsPort]);
190        GraphicsContext graphicsContext(cgContext);
191
192        // Safari doesn't flip the NSGraphicsContext before calling WebKit, yet WebCore requires a flipped graphics context.
193        BOOL flipped = [nsContext isFlipped];
194        if (!flipped)
195            CGContextScaleCTM(cgContext, 1, -1);
196
197        Font webCoreFont(FontPlatformData(font, [font pointSize]), ![nsContext isDrawingToScreen], Antialiased);
198        TextRun run(buffer.data(), length);
199
200        CGFloat red;
201        CGFloat green;
202        CGFloat blue;
203        CGFloat alpha;
204        [[textColor colorUsingColorSpaceName:NSDeviceRGBColorSpace] getRed:&red green:&green blue:&blue alpha:&alpha];
205        graphicsContext.setFillColor(makeRGBA(red * 255, green * 255, blue * 255, alpha * 255), ColorSpaceDeviceRGB);
206
207        webCoreFont.drawText(&graphicsContext, run, FloatPoint(point.x, (flipped ? point.y : (-1 * point.y))));
208
209        if (!flipped)
210            CGContextScaleCTM(cgContext, 1, -1);
211    } else {
212        // The given point is on the baseline.
213        if ([[NSView focusView] isFlipped])
214            point.y -= [font ascender];
215        else
216            point.y += [font descender];
217
218        [string drawAtPoint:point withAttributes:[NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, textColor, NSForegroundColorAttributeName, nil]];
219    }
220}
221
222static void drawDoubledAtPoint(NSString *string, NSPoint textPoint, NSColor *topColor, NSColor *bottomColor, NSFont *font)
223{
224        // turn off font smoothing so translucent text draws correctly (Radar 3118455)
225        drawAtPoint(string, textPoint, font, bottomColor);
226
227        textPoint.y += 1;
228        drawAtPoint(string, textPoint, font, topColor);
229}
230
231DragImageRef createDragImageForLink(KURL& url, const String& title, Frame* frame)
232{
233    if (!frame)
234        return nil;
235    NSString *label = 0;
236    if (!title.isEmpty())
237        label = title;
238    NSURL *cocoaURL = url;
239    NSString *urlString = [cocoaURL absoluteString];
240
241    BOOL drawURLString = YES;
242    BOOL clipURLString = NO;
243    BOOL clipLabelString = NO;
244
245    if (!label) {
246        drawURLString = NO;
247        label = urlString;
248    }
249
250    NSFont *labelFont = [[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:DragLinkLabelFontsize]
251                                                           toHaveTrait:NSBoldFontMask];
252    NSFont *urlFont = [NSFont systemFontOfSize:DragLinkUrlFontSize];
253    NSSize labelSize;
254    labelSize.width = widthWithFont(label, labelFont);
255    labelSize.height = [labelFont ascender] - [labelFont descender];
256    if (labelSize.width > MaxDragLabelWidth){
257        labelSize.width = MaxDragLabelWidth;
258        clipLabelString = YES;
259    }
260
261    NSSize imageSize;
262    imageSize.width = labelSize.width + DragLabelBorderX * 2;
263    imageSize.height = labelSize.height + DragLabelBorderY * 2;
264    if (drawURLString) {
265        NSSize urlStringSize;
266        urlStringSize.width = widthWithFont(urlString, urlFont);
267        urlStringSize.height = [urlFont ascender] - [urlFont descender];
268        imageSize.height += urlStringSize.height;
269        if (urlStringSize.width > MaxDragLabelWidth) {
270            imageSize.width = std::max(MaxDragLabelWidth + DragLabelBorderY * 2, MinDragLabelWidthBeforeClip);
271            clipURLString = YES;
272        } else
273            imageSize.width = std::max(labelSize.width + DragLabelBorderX * 2, urlStringSize.width + DragLabelBorderX * 2);
274    }
275    NSImage *dragImage = [[[NSImage alloc] initWithSize: imageSize] autorelease];
276    [dragImage lockFocus];
277
278    [[NSColor colorWithDeviceRed: 0.7f green: 0.7f blue: 0.7f alpha: 0.8f] set];
279
280    // Drag a rectangle with rounded corners
281    NSBezierPath *path = [NSBezierPath bezierPath];
282    [path appendBezierPathWithOvalInRect: NSMakeRect(0, 0, DragLabelRadius * 2, DragLabelRadius * 2)];
283    [path appendBezierPathWithOvalInRect: NSMakeRect(0, imageSize.height - DragLabelRadius * 2, DragLabelRadius * 2, DragLabelRadius * 2)];
284    [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DragLabelRadius * 2, imageSize.height - DragLabelRadius * 2, DragLabelRadius * 2, DragLabelRadius * 2)];
285    [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DragLabelRadius * 2, 0, DragLabelRadius * 2, DragLabelRadius * 2)];
286
287    [path appendBezierPathWithRect: NSMakeRect(DragLabelRadius, 0, imageSize.width - DragLabelRadius * 2, imageSize.height)];
288    [path appendBezierPathWithRect: NSMakeRect(0, DragLabelRadius, DragLabelRadius + 10, imageSize.height - 2 * DragLabelRadius)];
289    [path appendBezierPathWithRect: NSMakeRect(imageSize.width - DragLabelRadius - 20, DragLabelRadius, DragLabelRadius + 20, imageSize.height - 2 * DragLabelRadius)];
290    [path fill];
291
292    NSColor *topColor = [NSColor colorWithDeviceWhite:0.0f alpha:0.75f];
293    NSColor *bottomColor = [NSColor colorWithDeviceWhite:1.0f alpha:0.5f];
294    if (drawURLString) {
295        if (clipURLString)
296            urlString = StringTruncator::centerTruncate(urlString, imageSize.width - (DragLabelBorderX * 2), fontFromNSFont(urlFont));
297
298       drawDoubledAtPoint(urlString, NSMakePoint(DragLabelBorderX, DragLabelBorderY - [urlFont descender]), topColor, bottomColor, urlFont);
299    }
300
301    if (clipLabelString)
302        label = StringTruncator::rightTruncate(label, imageSize.width - (DragLabelBorderX * 2), fontFromNSFont(labelFont));
303    drawDoubledAtPoint(label, NSMakePoint(DragLabelBorderX, imageSize.height - LabelBorderYOffset - [labelFont pointSize]), topColor, bottomColor, labelFont);
304
305    [dragImage unlockFocus];
306
307    return dragImage;
308}
309
310} // namespace WebCore
311
312#endif // ENABLE(DRAG_SUPPORT)
313