/*
* Copyright (C) 2004 Apple Computer, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "KWQComboBox.h"
#import "KWQAssertions.h"
#import "KWQButton.h"
#import "KWQExceptions.h"
#import "KWQKHTMLPart.h"
#import "KWQFoundationExtras.h"
#import "KWQView.h"
#import "WebCoreBridge.h"
#import "WebCoreTextRenderer.h"
#import "WebCoreTextRendererFactory.h"
#import "render_form.h"
using khtml::RenderWidget;
using khtml::RenderLayer;
@interface NSCell (KWQComboBoxKnowsAppKitSecrets)
- (NSMutableDictionary *)_textAttributes;
@end
enum {
topMargin,
bottomMargin,
leftMargin,
rightMargin,
baselineFudgeFactor,
widthNotIncludingText,
minimumTextWidth
};
@interface KWQPopUpButtonCell : NSPopUpButtonCell <KWQWidgetHolder>
{
QComboBox *box;
NSWritingDirection baseWritingDirection;
}
- (id)initWithQComboBox:(QComboBox *)b;
- (void)detachQComboBox;
- (void)setBaseWritingDirection:(NSWritingDirection)direction;
- (NSWritingDirection)baseWritingDirection;
@end
@interface KWQPopUpButton : NSPopUpButton <KWQWidgetHolder>
{
BOOL inNextValidKeyView;
BOOL populatingMenu;
}
- (void)setPopulatingMenu:(BOOL)populating;
@end
QComboBox::QComboBox()
: _widthGood(false)
, _currentItem(0)
, _menuPopulated(true)
, _labelFont(nil)
, _activated(this, SIGNAL(activated(int)))
{
KWQ_BLOCK_EXCEPTIONS;
KWQPopUpButton *button = [[KWQPopUpButton alloc] init];
setView(button);
[button release];
KWQPopUpButtonCell *cell = [[KWQPopUpButtonCell alloc] initWithQComboBox:this];
#ifndef OMIT_TIGER_FEATURES
// Work around problem where the pop-up menu gets a "..." in it
// by turning off the default "ellipsizing" behavior.
[cell setLineBreakMode:NSLineBreakByClipping];
#endif
[button setCell:cell];
[cell release];
[button setTarget:button];
[button setAction:@selector(action:)];
[[button cell] setControlSize:NSSmallControlSize];
[button setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]]];
KWQ_UNBLOCK_EXCEPTIONS;
}
QComboBox::~QComboBox()
{
KWQ_BLOCK_EXCEPTIONS;
KWQPopUpButton *button = (KWQPopUpButton *)getView();
[button setTarget:nil];
[[button cell] detachQComboBox];
KWQRelease(_labelFont);
KWQ_UNBLOCK_EXCEPTIONS;
}
void QComboBox::setTitle(NSMenuItem *menuItem, const KWQListBoxItem &title)
{
if (title.type == KWQListBoxGroupLabel) {
NSDictionary *attributes = [[NSDictionary alloc] initWithObjectsAndKeys:labelFont(), NSFontAttributeName, nil];
NSAttributedString *string = [[NSAttributedString alloc] initWithString:title.string.getNSString() attributes:attributes];
[menuItem setAttributedTitle:string];
[string release];
[attributes release];
[menuItem setAction:NULL /*@selector(fakeSelectorForDisabledItem)*/];
} else {
[menuItem setTitle:title.string.getNSString()];
}
}
void QComboBox::appendItem(const QString &text, KWQListBoxItemType type)
{
const KWQListBoxItem listItem(text, type);
_items.append(listItem);
if (_menuPopulated) {
KWQPopUpButton *button = (KWQPopUpButton *)getView();
if (![[button cell] isHighlighted]) {
_menuPopulated = false;
} else {
KWQ_BLOCK_EXCEPTIONS;
if (type == KWQListBoxSeparator) {
NSMenuItem *separator = [NSMenuItem separatorItem];
[[button menu] addItem:separator];
} else {
// We must add the item with no title and then set the title because
// addItemWithTitle does not allow duplicate titles.
[button addItemWithTitle:@""];
NSMenuItem *menuItem = [button lastItem];
setTitle(menuItem, listItem);
}
KWQ_UNBLOCK_EXCEPTIONS;
}
}
_widthGood = false;
}
QSize QComboBox::sizeHint() const
{
KWQ_BLOCK_EXCEPTIONS;
KWQPopUpButton *button = (KWQPopUpButton *)getView();
if (!_widthGood) {
float width = 0;
QValueListConstIterator<KWQListBoxItem> i = const_cast<const QValueList<KWQListBoxItem> &>(_items).begin();
QValueListConstIterator<KWQListBoxItem> e = const_cast<const QValueList<KWQListBoxItem> &>(_items).end();
if (i != e) {
id <WebCoreTextRenderer> itemRenderer = [[WebCoreTextRendererFactory sharedFactory]
rendererWithFont:[button font]
usingPrinterFont:![NSGraphicsContext currentContextDrawingToScreen]];
id <WebCoreTextRenderer> labelRenderer = nil;
WebCoreTextStyle style;
WebCoreInitializeEmptyTextStyle(&style);
style.applyRunRounding = NO;
style.applyWordRounding = NO;
do {
const QString &s = (*i).string;
bool isGroupLabel = ((*i).type == KWQListBoxGroupLabel);
++i;
WebCoreTextRun run;
int length = s.length();
WebCoreInitializeTextRun(&run, reinterpret_cast<const UniChar *>(s.unicode()), length, 0, length);
id <WebCoreTextRenderer> renderer;
if (isGroupLabel) {
if (labelRenderer == nil) {
labelRenderer = [[WebCoreTextRendererFactory sharedFactory]
rendererWithFont:labelFont()
usingPrinterFont:![NSGraphicsContext currentContextDrawingToScreen]];
}
renderer = labelRenderer;
} else {
renderer = itemRenderer;
}
float textWidth = [renderer floatWidthForRun:&run style:&style widths:0];
width = kMax(width, textWidth);
} while (i != e);
}
_width = kMax(static_cast<int>(ceilf(width)), dimensions()[minimumTextWidth]);
_widthGood = true;
}
return QSize(_width + dimensions()[widthNotIncludingText],
static_cast<int>([[button cell] cellSize].height) - (dimensions()[topMargin] + dimensions()[bottomMargin]));
KWQ_UNBLOCK_EXCEPTIONS;
return QSize(0, 0);
}
QRect QComboBox::frameGeometry() const
{
QRect r = QWidget::frameGeometry();
return QRect(r.x() + dimensions()[leftMargin], r.y() + dimensions()[topMargin],
r.width() - (dimensions()[leftMargin] + dimensions()[rightMargin]),
r.height() - (dimensions()[topMargin] + dimensions()[bottomMargin]));
}
void QComboBox::setFrameGeometry(const QRect &r)
{
QWidget::setFrameGeometry(QRect(-dimensions()[leftMargin] + r.x(), -dimensions()[topMargin] + r.y(),
dimensions()[leftMargin] + r.width() + dimensions()[rightMargin],
dimensions()[topMargin] + r.height() + dimensions()[bottomMargin]));
}
int QComboBox::baselinePosition(int height) const
{
// Menu text is at the top.
KWQPopUpButton *button = (KWQPopUpButton *)getView();
return static_cast<int>(ceilf(-dimensions()[topMargin] + dimensions()[baselineFudgeFactor] + [[button font] ascender]));
}
void QComboBox::clear()
{
KWQPopUpButton *button = (KWQPopUpButton *)getView();
[button removeAllItems];
_widthGood = false;
_currentItem = 0;
_items.clear();
_menuPopulated = true;
}
void QComboBox::setCurrentItem(int index)
{
ASSERT(index < (int)_items.count());
KWQ_BLOCK_EXCEPTIONS;
KWQPopUpButton *button = (KWQPopUpButton *)getView();
if (_menuPopulated) {
[button selectItemAtIndex:index];
} else {
[button removeAllItems];
[button addItemWithTitle:@""];
NSMenuItem *menuItem = [button itemAtIndex:0];
setTitle(menuItem, _items[index]);
}
KWQ_UNBLOCK_EXCEPTIONS;
_currentItem = index;
}
void QComboBox::itemSelected()
{
ASSERT(_menuPopulated);
KWQ_BLOCK_EXCEPTIONS;
KWQPopUpButton *button = (KWQPopUpButton *)getView();
int i = [button indexOfSelectedItem];
if (_currentItem == i) {
return;
}
_currentItem = i;
KWQ_UNBLOCK_EXCEPTIONS;
_activated.call(_currentItem);
}
void QComboBox::setFont(const QFont &f)
{
QWidget::setFont(f);
const NSControlSize size = KWQNSControlSizeForFont(f);
NSControl * const button = static_cast<NSControl *>(getView());
KWQ_BLOCK_EXCEPTIONS;
if (size != [[button cell] controlSize]) {
[[button cell] setControlSize:size];
[button setFont:[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:size]]];
KWQRelease(_labelFont);
_labelFont = nil;
_widthGood = false;
}
KWQ_UNBLOCK_EXCEPTIONS;
}
NSFont *QComboBox::labelFont() const
{
if (_labelFont == nil) {
NSControl * const button = static_cast<NSControl *>(getView());
_labelFont = KWQRetain([NSFont boldSystemFontOfSize:[[button font] pointSize]]);
}
return _labelFont;
}
const int *QComboBox::dimensions() const
{
// We empirically determined these dimensions.
// It would be better to get this info from AppKit somehow.
static const int w[3][7] = {
{ 2, 3, 3, 3, 4, 34, 9 },
{ 1, 3, 3, 3, 3, 31, 5 },
{ 0, 0, 1, 1, 2, 32, 0 }
};
NSControl * const button = static_cast<NSControl *>(getView());
KWQ_BLOCK_EXCEPTIONS;
return w[[[button cell] controlSize]];
KWQ_UNBLOCK_EXCEPTIONS;
return w[NSSmallControlSize];
}
QWidget::FocusPolicy QComboBox::focusPolicy() const
{
FocusPolicy policy = QWidget::focusPolicy();
return policy == TabFocus ? StrongFocus : policy;
}
void QComboBox::setWritingDirection(QPainter::TextDirection direction)
{
KWQ_BLOCK_EXCEPTIONS;
KWQPopUpButton *button = static_cast<KWQPopUpButton *>(getView());
KWQPopUpButtonCell *cell = [button cell];
NSWritingDirection d = direction == QPainter::RTL ? NSWritingDirectionRightToLeft : NSWritingDirectionLeftToRight;
if ([cell baseWritingDirection] != d) {
[cell setBaseWritingDirection:d];
[button setNeedsDisplay:YES];
}
KWQ_UNBLOCK_EXCEPTIONS;
}
void QComboBox::populateMenu()
{
if (!_menuPopulated) {
KWQ_BLOCK_EXCEPTIONS;
KWQPopUpButton *button = static_cast<KWQPopUpButton *>(getView());
[button setPopulatingMenu:YES];
[button removeAllItems];
QValueListConstIterator<KWQListBoxItem> i = const_cast<const QValueList<KWQListBoxItem> &>(_items).begin();
QValueListConstIterator<KWQListBoxItem> e = const_cast<const QValueList<KWQListBoxItem> &>(_items).end();
for (; i != e; ++i) {
if ((*i).type == KWQListBoxSeparator) {
NSMenuItem *separator = [NSMenuItem separatorItem];
[[button menu] addItem:separator];
} else {
// We must add the item with no title and then set the title because
// addItemWithTitle does not allow duplicate titles.
[button addItemWithTitle:@""];
NSMenuItem *menuItem = [button lastItem];
setTitle(menuItem, *i);
}
}
[button selectItemAtIndex:_currentItem];
[button setPopulatingMenu:NO];
KWQ_UNBLOCK_EXCEPTIONS;
_menuPopulated = true;
}
}
void QComboBox::populate()
{
populateMenu();
}
@implementation KWQPopUpButtonCell
- (id)initWithQComboBox:(QComboBox *)b
{
box = b;
return [super init];
}
- (void)detachQComboBox
{
box = 0;
}
- (BOOL)trackMouse:(NSEvent *)event inRect:(NSRect)rect ofView:(NSView *)view untilMouseUp:(BOOL)flag
{
WebCoreBridge *bridge = box ? [KWQKHTMLPart::bridgeForWidget(box) retain] : nil;
// we need to retain the event because it is the [NSApp currentEvent], which can change
// and therefore be released during [super trackMouse:...]
[event retain];
BOOL result = [super trackMouse:event inRect:rect ofView:view untilMouseUp:flag];
if (result && bridge) {
// Give KHTML a chance to fix up its event state, since the popup eats all the
// events during tracking. [NSApp currentEvent] is still the original mouseDown
// at this point!
[bridge part]->sendFakeEventsAfterWidgetTracking(event);
}
[event release];
[bridge release];
return result;
}
- (QWidget *)widget
{
return box;
}
- (void)setBaseWritingDirection:(NSWritingDirection)direction
{
baseWritingDirection = direction;
}
- (NSWritingDirection)baseWritingDirection
{
return baseWritingDirection;
}
- (NSMutableDictionary *)_textAttributes
{
NSMutableDictionary *attributes = [super _textAttributes];
NSParagraphStyle *style = [attributes objectForKey:NSParagraphStyleAttributeName];
ASSERT(style != nil);
if ([style baseWritingDirection] != baseWritingDirection) {
NSMutableParagraphStyle *mutableStyle = [style mutableCopy];
[mutableStyle setBaseWritingDirection:baseWritingDirection];
[attributes setObject:mutableStyle forKey:NSParagraphStyleAttributeName];
[mutableStyle release];
}
return attributes;
}
- (void)setHighlighted:(BOOL)highlighted
{
if (highlighted && box) {
box->populateMenu();
}
[super setHighlighted:highlighted];
}
@end
@implementation KWQPopUpButton
- (void)action:(id)sender
{
QComboBox *box = static_cast<QComboBox *>([self widget]);
if (box) {
box->itemSelected();
}
}
- (QWidget *)widget
{
return [[self cell] widget];
}
- (void)mouseDown:(NSEvent *)event
{
QWidget::beforeMouseDown(self);
[super mouseDown:event];
QWidget::afterMouseDown(self);
}
- (BOOL)becomeFirstResponder
{
BOOL become = [super becomeFirstResponder];
if (become) {
QWidget *widget = [self widget];
if (widget) {
if (!KWQKHTMLPart::currentEventIsMouseDownInWidget(widget)) {
RenderWidget *w = const_cast<RenderWidget *> (static_cast<const RenderWidget *>(widget->eventFilterObject()));
RenderLayer *layer = w->enclosingLayer();
if (layer)
layer->scrollRectToVisible(w->absoluteBoundingBoxRect());
}
QFocusEvent event(QEvent::FocusIn);
if (widget->eventFilterObject())
const_cast<QObject *>(widget->eventFilterObject())->eventFilter(widget, &event);
}
}
return become;
}
- (BOOL)resignFirstResponder
{
BOOL resign = [super resignFirstResponder];
if (resign) {
QWidget *widget = [self widget];
if (widget) {
QFocusEvent event(QEvent::FocusOut);
if (widget->eventFilterObject())
const_cast<QObject *>(widget->eventFilterObject())->eventFilter(widget, &event);
}
}
return resign;
}
- (BOOL)needsPanelToBecomeKey
{
// override this NSView method so that <select> elements gain focus when clicked - 4011544
return YES;
}
- (BOOL)canBecomeKeyView
{
// Simplified method from NSView; overridden to replace NSView's way of checking
// for full keyboard access with ours.
return ([self window] != nil) && ![self isHiddenOrHasHiddenAncestor] && [self acceptsFirstResponder];
}
- (NSView *)nextKeyView
{
QWidget *widget = [self widget];
return widget && inNextValidKeyView
? KWQKHTMLPart::nextKeyViewForWidget(widget, KWQSelectingNext)
: [super nextKeyView];
}
- (NSView *)previousKeyView
{
QWidget *widget = [self widget];
return widget && inNextValidKeyView
? KWQKHTMLPart::nextKeyViewForWidget(widget, KWQSelectingPrevious)
: [super previousKeyView];
}
- (NSView *)nextValidKeyView
{
inNextValidKeyView = YES;
NSView *view = [super nextValidKeyView];
inNextValidKeyView = NO;
return view;
}
- (NSView *)previousValidKeyView
{
inNextValidKeyView = YES;
NSView *view = [super previousValidKeyView];
inNextValidKeyView = NO;
return view;
}
- (void)setPopulatingMenu:(BOOL)populating
{
populatingMenu = populating;
}
- (void)setNeedsDisplayInRect:(NSRect)rect
{
if (!populatingMenu) {
[super setNeedsDisplayInRect:rect];
}
}
@end