WebCore:
[WebKit-https.git] / WebKit / WebCoreSupport.subproj / WebFileButton.m
1 /*
2  * Copyright (C) 2005 Apple Computer, 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  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #import "WebFileButton.h"
30
31 #import <WebKit/WebAssertions.h>
32 #import <WebKit/WebLocalizableStrings.h>
33 #import <WebKit/WebFrameBridge.h>
34 #import <WebKit/WebStringTruncator.h>
35
36 #define NO_FILE_SELECTED 
37
38 #define AFTER_BUTTON_SPACING 4
39 #define ICON_HEIGHT 16
40 #define ICON_WIDTH 16
41 #define ICON_FILENAME_SPACING 2
42
43 // We empirically determined that buttons have these extra pixels on all
44 // sides. It would be better to get this info from AppKit somehow.
45 #define BUTTON_TOP_MARGIN 4
46 #define BUTTON_BOTTOM_MARGIN 6
47 #define BUTTON_LEFT_MARGIN 5
48 #define BUTTON_RIGHT_MARGIN 5
49
50 // AppKit calls this kThemePushButtonSmallTextOffset.
51 #define BUTTON_VERTICAL_FUDGE_FACTOR 2
52
53 @interface WebFileChooserButton : NSButton
54 {
55     id <WebCoreFileButtonDelegate> _delegate;
56 }
57 - (id)initWithDelegate:(id <WebCoreFileButtonDelegate>)delegate;
58 @end
59
60 @implementation WebFileButton
61
62 - (void)positionButton
63 {
64     [_button sizeToFit];
65     [_button setFrameOrigin:NSMakePoint(0, 0)];
66 }
67
68 - (id)initWithBridge:(WebFrameBridge *)bridge delegate:(id <WebCoreFileButtonDelegate>)delegate
69 {
70     self = [super init];
71     if (self) {
72         _bridge = bridge; // Don't retain to avoid cycle
73         _delegate = [delegate retain];
74     }
75     return self;
76 }
77
78 - (id)initWithFrame:(NSRect)frame
79 {
80     self = [super initWithFrame:frame];
81     if (self) {
82         _button = [[WebFileChooserButton alloc] initWithDelegate:_delegate];
83         
84         [_button setTitle:UI_STRING("Choose File", "title for file button used in HTML forms")];
85         [[_button cell] setControlSize:NSSmallControlSize];
86         [_button setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
87         [_button setBezelStyle:NSRoundedBezelStyle];
88         [_button setTarget:self];
89         [_button setAction:@selector(chooseButtonPressed:)];
90         [_button setNextResponder:self];
91         
92         [self addSubview:_button];
93         
94         [self positionButton];
95         [self setFilename:nil];
96     }
97     return self;
98 }
99
100 - (void)dealloc
101 {
102     [_filename release];
103     [_button release];
104     [_icon release];
105     [_label release];
106     [_delegate release];
107     [super dealloc];
108 }
109
110 - (BOOL)isFlipped
111 {
112     return YES;
113 }
114         
115 - (void)drawRect:(NSRect)rect
116 {
117     NSRect bounds = [self bounds];
118     
119     [NSGraphicsContext saveGraphicsState];
120     NSRectClip(NSIntersectionRect(bounds, rect));
121
122     float left = NSMaxX([_button frame]) + AFTER_BUTTON_SPACING;
123
124     if (_icon) {
125         float top = (bounds.size.height - BUTTON_BOTTOM_MARGIN - BUTTON_TOP_MARGIN - ICON_HEIGHT) / 2
126             + BUTTON_TOP_MARGIN;
127         [_icon drawInRect:NSMakeRect(left, top, ICON_WIDTH, ICON_HEIGHT)
128             fromRect:NSMakeRect(0, 0, [_icon size].width, [_icon size].height)
129             operation:NSCompositeSourceOver fraction:1.0];
130         left += ICON_WIDTH + ICON_FILENAME_SPACING;
131     }
132
133     NSFont *font = [_button font];
134     NSDictionary *attributes = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
135     [_label drawAtPoint:NSMakePoint(left, [self baseline] - [[_button font] ascender]) withAttributes:attributes];
136
137     [NSGraphicsContext restoreGraphicsState];
138 }
139
140 - (void)updateLabel
141 {
142     [_label release];
143     
144     NSString *label;
145     if ([_filename length]) {
146         label = _filename;
147     } else {
148         label = [UI_STRING("no file selected", "text to display in file button used in HTML forms when no file is selected") retain];
149     }
150     
151     float left = NSMaxX([_button frame]) + AFTER_BUTTON_SPACING;
152     if (_icon) {
153         left += ICON_WIDTH + ICON_FILENAME_SPACING;
154     }
155     float labelWidth = [self bounds].size.width - left;
156
157     _label = labelWidth <= 0 ? nil : [[WebStringTruncator centerTruncateString:
158         [[NSFileManager defaultManager] displayNameAtPath:label]
159         toWidth:labelWidth withFont:[_button font]] copy];
160 }
161
162 - (void)setFilename:(NSString *)filename
163 {
164     NSString *copy = [filename copy];
165     [_filename release];
166     _filename = copy;
167     
168     [_icon release];
169     if ([_filename length] == 0 || [_filename characterAtIndex:0] != '/') {
170         _icon = nil;
171     } else {
172         _icon = [[[NSWorkspace sharedWorkspace] iconForFile:_filename] retain];
173         // I'm not sure why this has any effect, but including this line of code seems to make
174         // the image appear right-side-up. As far as I know, the drawInRect method used above
175         // in our drawRect method should work regardless of whether the image is flipped or not.
176         [_icon setFlipped:YES];
177     }
178     
179     [self updateLabel];
180     
181     [self setNeedsDisplay:YES];
182 }
183
184 - (NSString *)filename
185 {
186     return [[_filename copy] autorelease];
187 }
188
189 - (void)setFrameSize:(NSSize)size
190 {
191     [super setFrameSize:size];
192     // FIXME: Can we just springs and struts instead of calling positionButton?
193     [self positionButton];
194     [self updateLabel];
195 }
196
197 - (NSSize)bestVisualFrameSizeForCharacterCount:(int)count
198 {
199     ASSERT(count > 0);
200     NSSize size = [[_button cell] cellSize];
201     size.height -= BUTTON_TOP_MARGIN + BUTTON_BOTTOM_MARGIN;
202     size.width -= BUTTON_LEFT_MARGIN + BUTTON_RIGHT_MARGIN;
203     size.width += AFTER_BUTTON_SPACING + ICON_WIDTH + ICON_FILENAME_SPACING;
204     size.width += count * [@"x" sizeWithAttributes:[NSDictionary dictionaryWithObject:[_button font] forKey:NSFontAttributeName]].width;
205     return size;
206 }
207
208 - (NSRect)visualFrame
209 {
210     ASSERT([self superview] == nil || [[self superview] isFlipped]);
211     NSRect frame = [self frame];
212     frame.origin.x += BUTTON_LEFT_MARGIN;
213     frame.size.width -= BUTTON_LEFT_MARGIN;
214     frame.origin.y += BUTTON_TOP_MARGIN;
215     frame.size.height -= BUTTON_TOP_MARGIN + BUTTON_BOTTOM_MARGIN;
216     return frame;
217 }
218
219 - (void)setVisualFrame:(NSRect)frame
220 {
221     ASSERT([self superview] == nil || [[self superview] isFlipped]);
222     frame.origin.x -= BUTTON_LEFT_MARGIN;
223     frame.size.width += BUTTON_LEFT_MARGIN;
224     frame.origin.y -= BUTTON_TOP_MARGIN;
225     frame.size.height += BUTTON_TOP_MARGIN + BUTTON_BOTTOM_MARGIN;
226     [self setFrame:frame];
227 }
228
229 - (float)baseline
230 {
231     // Button text is centered vertically, with a fudge factor to account for the shadow.
232     ASSERT(_button);
233     NSFont *buttonFont = [_button font];
234     float ascender = [buttonFont ascender];
235     float descender = [buttonFont descender];
236     return -BUTTON_TOP_MARGIN
237         + ([[_button cell] cellSize].height + BUTTON_TOP_MARGIN + BUTTON_BOTTOM_MARGIN - (ascender - descender)) / 2.0
238         + ascender - BUTTON_VERTICAL_FUDGE_FACTOR;
239 }
240
241 - (void)beginSheet
242 {
243     [_bridge retain];
244     [_bridge runOpenPanelForFileButtonWithResultListener:self];
245 }
246
247 - (void)chooseFilename:(NSString *)filename
248 {
249     // The != check here makes sure we don't consider a change from nil to nil as a change.
250     if (_filename != filename && ![_filename isEqualToString:filename]) {
251         [self setFilename:filename];
252         [_delegate filenameChanged:filename];
253     }
254     [_bridge release];
255 }
256
257 - (void)cancel
258 {
259     [_bridge release];
260 }
261
262 - (void)chooseButtonPressed:(id)sender
263 {
264     [_delegate clicked];
265     [self beginSheet];
266 }
267
268 - (void)mouseDown:(NSEvent *)event
269 {
270     [self beginSheet];
271 }
272
273 - (BOOL)acceptsFirstResponder
274 {
275     return YES;
276 }
277
278 - (BOOL)becomeFirstResponder
279 {
280     BOOL become = [_button acceptsFirstResponder];
281     if (become) {
282         [_delegate focusChanged:YES];
283         [[self window] makeFirstResponder:_button];
284     }
285     return become;
286 }
287
288 - (NSView *)nextKeyView
289 {
290     return (_inNextValidKeyView && ![_bridge inNextKeyViewOutsideWebFrameViews])
291     ? [_bridge nextKeyView]
292     : [super nextKeyView];
293 }
294
295 - (NSView *)previousKeyView
296 {
297     return (_inNextValidKeyView)
298     ? [_bridge previousKeyView]
299     : [super previousKeyView];
300 }
301
302 - (NSView *)nextValidKeyView
303 {
304     _inNextValidKeyView = YES;
305     NSView *view = [super nextValidKeyView];
306     _inNextValidKeyView = NO;
307     return view;
308 }
309
310 - (NSView *)previousValidKeyView
311 {
312     _inNextValidKeyView = YES;
313     NSView *view = [super previousValidKeyView];
314     _inNextValidKeyView = NO;
315     return view;
316 }
317
318 - (void)performClick
319 {
320     [_button performClick:nil];
321 }
322
323 @end
324
325 @implementation WebFileChooserButton
326
327 - (id)initWithDelegate:(id <WebCoreFileButtonDelegate>)delegate
328 {
329     [super init];
330     _delegate = delegate; // Don't retain to avoid cycle
331     return self;
332 }
333
334 - (NSView *)nextValidKeyView
335 {
336     return [[self superview] nextValidKeyView];
337 }
338
339 - (NSView *)previousValidKeyView
340 {
341     return [[self superview] previousValidKeyView];
342 }
343
344 - (BOOL)resignFirstResponder
345 {
346     [_delegate focusChanged:NO];
347     return YES;
348 }
349
350 @end