LayoutTests:
[WebKit-https.git] / WebCore / kwq / KWQFileButton.mm
1 /*
2  * Copyright (C) 2003 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  * 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 "KWQFileButton.h"
28
29 #import "BlockExceptions.h"
30 #import "FoundationExtras.h"
31 #import "FrameMac.h"
32 #import "WebCoreFrameBridge.h"
33 #import "WebCoreStringTruncator.h"
34 #import "WebCoreViewFactory.h"
35 #import "render_form.h"
36
37 using namespace WebCore;
38
39 #define NO_FILE_SELECTED 
40
41 #define AFTER_BUTTON_SPACING 4
42 #define ICON_HEIGHT 16
43 #define ICON_WIDTH 16
44 #define ICON_FILENAME_SPACING 2
45
46 // We empirically determined that buttons have these extra pixels on all
47 // sides. It would be better to get this info from AppKit somehow.
48 #define BUTTON_TOP_MARGIN 4
49 #define BUTTON_BOTTOM_MARGIN 6
50 #define BUTTON_LEFT_MARGIN 5
51 #define BUTTON_RIGHT_MARGIN 5
52
53 // AppKit calls this kThemePushButtonSmallTextOffset.
54 #define BUTTON_VERTICAL_FUDGE_FACTOR 2
55
56 @interface WebFileChooserButton : NSButton
57 {
58     KWQFileButton *_widget;
59 }
60 - (id)initWithWidget:(KWQFileButton *)widget;
61 @end
62
63 @interface WebCoreFileButton : NSView <WebCoreOpenPanelResultListener>
64 {
65     NSString *_filename;
66     WebFileChooserButton *_button;
67     NSImage *_icon;
68     NSString *_label;
69     BOOL _inNextValidKeyView;
70     KWQFileButton *_widget;
71     BOOL _isCurrentDragTarget;
72 }
73 - (void)setFilename:(NSString *)filename;
74 - (void)performClick;
75 - (NSString *)filename;
76 - (float)baseline;
77 - (void)setVisualFrame:(NSRect)rect;
78 - (NSRect)visualFrame;
79 - (NSSize)bestVisualFrameSizeForCharacterCount:(int)count;
80 - (id)initWithWidget:(KWQFileButton *)widget;
81 - (void)setEnabled:(BOOL)flag;
82 @end
83
84 @implementation WebCoreFileButton
85
86 - (void)positionButton
87 {
88     [_button sizeToFit];
89     [_button setFrameOrigin:NSMakePoint(0, 0)];
90 }
91
92 - (id)initWithWidget:(KWQFileButton*)widget
93 {
94     self = [super init];
95     if (!self)
96         return nil;
97     [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
98
99     _widget = widget;
100     return self;
101 }
102
103 - (id)initWithFrame:(NSRect)frame
104 {
105     self = [super initWithFrame:frame];
106     if (self) {
107         _button = [[WebFileChooserButton alloc] initWithWidget:_widget];
108         
109         [_button setTitle:[[WebCoreViewFactory sharedFactory] fileButtonChooseFileLabel]];
110         [[_button cell] setControlSize:NSSmallControlSize];
111         [_button setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
112         [_button setBezelStyle:NSRoundedBezelStyle];
113         [_button setTarget:self];
114         [_button setAction:@selector(chooseButtonPressed:)];
115         [_button setNextResponder:self];
116         
117         [self addSubview:_button];
118         
119         [self positionButton];
120         [self setFilename:nil];
121     }
122     return self;
123 }
124
125 - (void)dealloc
126 {
127     [_filename release];
128     [_button release];
129     [_icon release];
130     [_label release];
131     [super dealloc];
132 }
133
134 - (BOOL)isFlipped
135 {
136     return YES;
137 }
138         
139 - (void)drawRect:(NSRect)rect
140 {
141     NSRect bounds = [self bounds];
142     
143     [NSGraphicsContext saveGraphicsState];
144     NSRectClip(NSIntersectionRect(bounds, rect));
145     
146     if (_isCurrentDragTarget) {
147         [[NSColor colorWithCalibratedWhite:0.0 alpha:0.25f] set];
148         NSRectFillUsingOperation([self bounds], NSCompositeSourceOver);
149     }
150
151     float left = NSMaxX([_button frame]) + AFTER_BUTTON_SPACING;
152
153     if (_icon) {
154         float top = (bounds.size.height - BUTTON_BOTTOM_MARGIN - BUTTON_TOP_MARGIN - ICON_HEIGHT) / 2
155             + BUTTON_TOP_MARGIN;
156         [_icon drawInRect:NSMakeRect(left, top, ICON_WIDTH, ICON_HEIGHT)
157             fromRect:NSMakeRect(0, 0, [_icon size].width, [_icon size].height)
158             operation:NSCompositeSourceOver fraction:1.0];
159         left += ICON_WIDTH + ICON_FILENAME_SPACING;
160     }
161
162     NSFont *font = [_button font];
163     NSDictionary *attributes = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
164     [_label drawAtPoint:NSMakePoint(left, [self baseline] - [[_button font] ascender]) withAttributes:attributes];
165
166     [NSGraphicsContext restoreGraphicsState];
167 }
168
169 - (void)updateLabel
170 {
171     [_label release];
172     
173     NSString *label;
174     if ([_filename length])
175         label = _filename;
176     else
177         label = [[WebCoreViewFactory sharedFactory] fileButtonNoFileSelectedLabel];
178     
179     float left = NSMaxX([_button frame]) + AFTER_BUTTON_SPACING;
180     if (_icon)
181         left += ICON_WIDTH + ICON_FILENAME_SPACING;
182     float labelWidth = [self bounds].size.width - left;
183
184     _label = labelWidth <= 0 ? nil : [[WebCoreStringTruncator centerTruncateString:
185         [[NSFileManager defaultManager] displayNameAtPath:label]
186         toWidth:labelWidth withFont:[_button font]] copy];
187 }
188
189 - (void)setFilename:(NSString *)filename
190 {
191     NSString *copy = [filename copy];
192     [_filename release];
193     _filename = copy;
194     
195     [_icon release];
196     if ([_filename length] == 0 || [_filename characterAtIndex:0] != '/')
197         _icon = nil;
198     else {
199         _icon = [[[NSWorkspace sharedWorkspace] iconForFile:_filename] retain];
200         // I'm not sure why this has any effect, but including this line of code seems to make
201         // the image appear right-side-up. As far as I know, the drawInRect method used above
202         // in our drawRect method should work regardless of whether the image is flipped or not.
203         [_icon setFlipped:YES];
204     }
205     
206     [self updateLabel];
207     
208     [self setNeedsDisplay:YES];
209 }
210
211 - (NSString *)filename
212 {
213     return [[_filename copy] autorelease];
214 }
215
216 - (void)setFrameSize:(NSSize)size
217 {
218     [super setFrameSize:size];
219     // FIXME: Can we just springs and struts instead of calling positionButton?
220     [self positionButton];
221     [self updateLabel];
222 }
223
224 - (NSSize)bestVisualFrameSizeForCharacterCount:(int)count
225 {
226     ASSERT(count > 0);
227     NSSize size = [[_button cell] cellSize];
228     size.height -= BUTTON_TOP_MARGIN + BUTTON_BOTTOM_MARGIN;
229     size.width -= BUTTON_LEFT_MARGIN + BUTTON_RIGHT_MARGIN;
230     size.width += AFTER_BUTTON_SPACING + ICON_WIDTH + ICON_FILENAME_SPACING;
231     size.width += count * [@"x" sizeWithAttributes:[NSDictionary dictionaryWithObject:[_button font] forKey:NSFontAttributeName]].width;
232     return size;
233 }
234
235 - (NSRect)visualFrame
236 {
237     ASSERT([self superview] == nil || [[self superview] isFlipped]);
238     NSRect frame = [self frame];
239     frame.origin.x += BUTTON_LEFT_MARGIN;
240     frame.size.width -= BUTTON_LEFT_MARGIN;
241     frame.origin.y += BUTTON_TOP_MARGIN;
242     frame.size.height -= BUTTON_TOP_MARGIN + BUTTON_BOTTOM_MARGIN;
243     return frame;
244 }
245
246 - (void)setVisualFrame:(NSRect)frame
247 {
248     ASSERT([self superview] == nil || [[self superview] isFlipped]);
249     frame.origin.x -= BUTTON_LEFT_MARGIN;
250     frame.size.width += BUTTON_LEFT_MARGIN;
251     frame.origin.y -= BUTTON_TOP_MARGIN;
252     frame.size.height += BUTTON_TOP_MARGIN + BUTTON_BOTTOM_MARGIN;
253     [self setFrame:frame];
254 }
255
256 - (float)baseline
257 {
258     // Button text is centered vertically, with a fudge factor to account for the shadow.
259     ASSERT(_button);
260     NSFont *buttonFont = [_button font];
261     float ascender = [buttonFont ascender];
262     float descender = [buttonFont descender];
263     return -BUTTON_TOP_MARGIN
264         + ([[_button cell] cellSize].height + BUTTON_TOP_MARGIN + BUTTON_BOTTOM_MARGIN - (ascender - descender)) / 2.0
265         + ascender - BUTTON_VERTICAL_FUDGE_FACTOR;
266 }
267
268 - (void)beginSheet
269 {
270     WebCoreFrameBridge *bridge = FrameMac::bridgeForWidget(_widget);
271     [bridge retain];
272     [bridge runOpenPanelForFileButtonWithResultListener:self];
273 }
274
275 - (void)changeFilename:(NSString *)filename
276 {
277     // The != check here makes sure we don't consider a change from nil to nil as a change.
278     if (_filename != filename && ![_filename isEqualToString:filename]) {
279         [self setFilename:filename];
280         if (_widget)
281             _widget->filenameChanged(DeprecatedString::fromNSString(filename));
282     }
283 }
284
285 - (void)chooseFilename:(NSString *)filename
286 {
287     [self changeFilename:filename];
288     WebCoreFrameBridge *bridge = FrameMac::bridgeForWidget(_widget);
289     [bridge release];
290 }
291
292 - (void)cancel
293 {
294     WebCoreFrameBridge *bridge = FrameMac::bridgeForWidget(_widget);
295     [bridge release];
296 }
297
298 - (void)chooseButtonPressed:(id)sender
299 {
300     if (_widget)
301         _widget->sendConsumedMouseUp();
302     if (_widget && _widget->client())
303         _widget->client()->clicked(_widget);
304     [self beginSheet];
305 }
306
307 - (void)mouseDown:(NSEvent *)event
308 {
309     [self beginSheet];
310 }
311
312 - (BOOL)acceptsFirstResponder
313 {
314     return YES;
315 }
316
317 - (BOOL)becomeFirstResponder
318 {
319     BOOL become = [_button acceptsFirstResponder];
320     if (become) {
321         if (_widget && _widget->client() && !FrameMac::currentEventIsMouseDownInWidget(_widget))
322             _widget->client()->scrollToVisible(_widget);
323         if (_widget && _widget->client())
324             _widget->client()->focusIn(_widget);
325         [[self window] makeFirstResponder:_button];
326     }
327     return become;
328 }
329
330 - (NSView *)nextKeyView
331 {
332     return _inNextValidKeyView
333     ? FrameMac::nextKeyViewForWidget(_widget, KWQSelectingNext)
334     : [super nextKeyView];
335 }
336
337 - (NSView *)previousKeyView
338 {
339     return _inNextValidKeyView
340     ? FrameMac::nextKeyViewForWidget(_widget, KWQSelectingPrevious)
341     : [super previousKeyView];
342 }
343
344 - (NSView *)nextValidKeyView
345 {
346     _inNextValidKeyView = YES;
347     NSView *view = [super nextValidKeyView];
348     _inNextValidKeyView = NO;
349     return view;
350 }
351
352 - (NSView *)previousValidKeyView
353 {
354     _inNextValidKeyView = YES;
355     NSView *view = [super previousValidKeyView];
356     _inNextValidKeyView = NO;
357     return view;
358 }
359
360 - (void)performClick
361 {
362     [_button performClick:nil];
363 }
364
365 - (void)setEnabled:(BOOL)flag
366 {
367     [_button setEnabled:flag];
368 }
369
370 static NSString *validFilenameFromPasteboard(NSPasteboard* pBoard)
371 {
372     NSArray *filenames = [pBoard propertyListForType:NSFilenamesPboardType];
373     if ([filenames count] == 1) {
374         NSString *filename = [filenames objectAtIndex:0];
375         NSDictionary *fileAttributes = [[NSFileManager defaultManager] fileAttributesAtPath:filename traverseLink:YES];
376         if ([[fileAttributes fileType] isEqualToString:NSFileTypeRegular])
377             return filename;
378     }
379     return nil;
380 }
381
382 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
383 {
384     if (validFilenameFromPasteboard([sender draggingPasteboard])) {
385         _isCurrentDragTarget = YES;
386         [self setNeedsDisplay:YES];
387         return NSDragOperationCopy;
388     }
389     return NSDragOperationNone;
390 }
391
392 - (void)draggingExited:(id <NSDraggingInfo>)sender
393 {
394     if (_isCurrentDragTarget) {
395         _isCurrentDragTarget = NO;
396         [self setNeedsDisplay:YES];
397     }
398 }
399
400 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
401 {
402     _isCurrentDragTarget = NO;
403     NSString *filename = validFilenameFromPasteboard([sender draggingPasteboard]);
404     if (filename) {
405         [self changeFilename:filename];
406         [self setNeedsDisplay:YES];
407         return YES;
408     }
409     return NO;
410 }
411
412 @end
413
414 @implementation WebFileChooserButton
415
416 - (id)initWithWidget:(KWQFileButton *)widget
417 {
418     [super init];
419     _widget = widget;
420     return self;
421 }
422
423 - (NSView *)nextValidKeyView
424 {
425     return [[self superview] nextValidKeyView];
426 }
427
428 - (NSView *)previousValidKeyView
429 {
430     return [[self superview] previousValidKeyView];
431 }
432
433 - (BOOL)resignFirstResponder
434 {
435     if (_widget && _widget->client())
436         _widget->client()->focusOut(_widget);
437     return YES;
438 }
439
440 @end
441
442
443 KWQFileButton::KWQFileButton(Frame *frame)
444 {
445     BEGIN_BLOCK_OBJC_EXCEPTIONS;
446
447     _buttonView = [[WebCoreFileButton alloc] initWithWidget:this];
448     setView(_buttonView);
449     [_buttonView release];
450
451     END_BLOCK_OBJC_EXCEPTIONS;
452 }
453
454 void KWQFileButton::setFilename(const DeprecatedString &f)
455 {
456     BEGIN_BLOCK_OBJC_EXCEPTIONS;
457     [_buttonView setFilename:f.getNSString()];
458     END_BLOCK_OBJC_EXCEPTIONS;
459 }
460
461 void KWQFileButton::click(bool sendMouseEvents)
462 {
463     BEGIN_BLOCK_OBJC_EXCEPTIONS;
464     [_buttonView performClick];
465     END_BLOCK_OBJC_EXCEPTIONS;
466 }
467
468 IntSize KWQFileButton::sizeForCharacterWidth(int characters) const
469 {
470     ASSERT(characters > 0);
471
472     NSSize size = {0,0};
473     BEGIN_BLOCK_OBJC_EXCEPTIONS;
474     size = [_buttonView bestVisualFrameSizeForCharacterCount:characters];
475     return IntSize(size);
476     END_BLOCK_OBJC_EXCEPTIONS;
477     return IntSize(0, 0);
478 }
479
480 IntRect KWQFileButton::frameGeometry() const
481 {
482     BEGIN_BLOCK_OBJC_EXCEPTIONS;
483     return enclosingIntRect([_buttonView visualFrame]);
484     END_BLOCK_OBJC_EXCEPTIONS;
485     return IntRect();
486 }
487
488 void KWQFileButton::setFrameGeometry(const IntRect &rect)
489 {
490     BEGIN_BLOCK_OBJC_EXCEPTIONS;
491     [_buttonView setVisualFrame:rect];
492     END_BLOCK_OBJC_EXCEPTIONS;
493 }
494
495 int KWQFileButton::baselinePosition(int height) const
496 {
497     BEGIN_BLOCK_OBJC_EXCEPTIONS;
498     return (int)([_buttonView frame].origin.y + [_buttonView baseline] - [_buttonView visualFrame].origin.y);
499     END_BLOCK_OBJC_EXCEPTIONS;
500
501     return 0;
502 }
503
504 Widget::FocusPolicy KWQFileButton::focusPolicy() const
505 {
506     BEGIN_BLOCK_OBJC_EXCEPTIONS;
507     
508     WebCoreFrameBridge *bridge = FrameMac::bridgeForWidget(this);
509     if (!bridge || ![bridge impl] || ![bridge impl]->tabsToAllControls()) {
510         return NoFocus;
511     }
512     
513     END_BLOCK_OBJC_EXCEPTIONS;
514     
515     return Widget::focusPolicy();
516 }
517
518 void KWQFileButton::filenameChanged(const DeprecatedString& filename)
519 {
520     m_name = filename;
521     if (client())
522         client()->valueChanged(this);
523 }
524
525 void KWQFileButton::setDisabled(bool flag)
526 {
527     BEGIN_BLOCK_OBJC_EXCEPTIONS;
528     [_buttonView setEnabled:!flag];
529     END_BLOCK_OBJC_EXCEPTIONS;
530 }
531