blob: c393f52ca23a605f449c04cd37273937f299f53a [file] [log] [blame]
/*
* This file is part of the DOM implementation for KDE.
*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000 Dirk Mueller (mueller@kde.org)
* Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
*/
#include "config.h"
#include "DeprecatedRenderSelect.h"
#include "HTMLNames.h"
#include "HTMLOptGroupElement.h"
#include "HTMLOptionElement.h"
#include "HTMLSelectElement.h"
#include "PopUpButton.h"
using std::min;
namespace WebCore {
using namespace HTMLNames;
DeprecatedRenderSelect::DeprecatedRenderSelect(HTMLSelectElement* element)
: RenderFormElement(element)
, m_size(element->size())
, m_multiple(element->multiple())
, m_selectionChanged(true)
, m_ignoreSelectEvents(false)
, m_optionsChanged(true)
{
setWidget(createListBox());
}
void DeprecatedRenderSelect::setWidgetWritingDirection()
{
static_cast<ListBox*>(m_widget)->setWritingDirection(style()->direction());
}
void DeprecatedRenderSelect::setStyle(RenderStyle* s)
{
RenderFormElement::setStyle(s);
setWidgetWritingDirection();
}
void DeprecatedRenderSelect::updateFromElement()
{
m_ignoreSelectEvents = true;
// change widget type
bool oldMultiple = m_multiple;
m_multiple = static_cast<HTMLSelectElement*>(node())->multiple();
if (oldMultiple != m_multiple) {
static_cast<ListBox*>(m_widget)->setSelectionMode(m_multiple ? ListBox::Extended : ListBox::Single);
m_selectionChanged = true;
m_optionsChanged = true;
}
// update contents listbox/combobox based on options in m_element
if (m_optionsChanged) {
static_cast<HTMLSelectElement*>(node())->recalcListItems();
const Vector<HTMLElement*>& listItems = static_cast<HTMLSelectElement*>(node())->listItems();
int listIndex;
static_cast<ListBox*>(m_widget)->clear();
bool groupEnabled = true;
for (listIndex = 0; listIndex < int(listItems.size()); listIndex++) {
if (listItems[listIndex]->hasTagName(optgroupTag)) {
HTMLOptGroupElement* optgroupElement = static_cast<HTMLOptGroupElement*>(listItems[listIndex]);
DeprecatedString label = optgroupElement->getAttribute(labelAttr).deprecatedString();
label.replace('\\', backslashAsCurrencySymbol());
// In WinIE, an optgroup can't start or end with whitespace (other than the indent
// we give it). We match this behavior.
label = label.stripWhiteSpace();
// We want to collapse our whitespace too. This will match other browsers.
label = label.simplifyWhiteSpace();
groupEnabled = optgroupElement->isEnabled();
static_cast<ListBox*>(m_widget)->appendGroupLabel(label, groupEnabled);
} else if (listItems[listIndex]->hasTagName(optionTag)) {
HTMLOptionElement* optionElement = static_cast<HTMLOptionElement*>(listItems[listIndex]);
DeprecatedString itemText = optionElement->text().deprecatedString();
if (itemText.isEmpty())
itemText = optionElement->getAttribute(labelAttr).deprecatedString();
itemText.replace('\\', backslashAsCurrencySymbol());
// In WinIE, leading and trailing whitespace is ignored in options. We match this behavior.
itemText = itemText.stripWhiteSpace();
// We want to collapse our whitespace too. This will match other browsers.
itemText = itemText.simplifyWhiteSpace();
if (listItems[listIndex]->parentNode()->hasTagName(optgroupTag))
itemText.prepend(" ");
static_cast<ListBox*>(m_widget)->appendItem(itemText, groupEnabled && optionElement->isEnabled());
} else
ASSERT(false);
m_selectionChanged = true;
}
static_cast<ListBox*>(m_widget)->doneAppendingItems();
setNeedsLayoutAndMinMaxRecalc();
m_optionsChanged = false;
}
// update selection
if (m_selectionChanged)
updateSelection();
m_ignoreSelectEvents = false;
RenderFormElement::updateFromElement();
}
short DeprecatedRenderSelect::baselinePosition(bool f, bool isRootLineBox) const
{
// FIXME: Should get the hardcoded constant of 7 by calling a ListBox function,
// as we do for other widget classes.
return RenderWidget::baselinePosition(f, isRootLineBox) - 7;
}
void DeprecatedRenderSelect::calcMinMaxWidth()
{
ASSERT(!minMaxKnown());
if (m_optionsChanged)
updateFromElement();
// ### ugly HACK FIXME!!!
setMinMaxKnown();
layoutIfNeeded();
setNeedsLayoutAndMinMaxRecalc();
// ### end FIXME
RenderFormElement::calcMinMaxWidth();
}
void DeprecatedRenderSelect::layout()
{
ASSERT(needsLayout());
ASSERT(minMaxKnown());
// ### maintain selection properly between type/size changes, and work
// out how to handle multiselect->singleselect (probably just select
// first selected one)
// calculate size
ListBox* w = static_cast<ListBox*>(m_widget);
int size = m_size;
// check if multiple and size was not given or invalid
// Internet Exploder sets size to min(number of elements, 4)
// Netscape seems to simply set it to "number of elements"
// the average of that is IMHO min(number of elements, 10)
// so I did that ;-)
if (size < 1)
size = min(static_cast<ListBox*>(m_widget)->count(), 10U);
// Let the widget tell us how big it wants to be.
IntSize s(w->sizeForNumberOfLines(size));
setIntrinsicWidth(s.width());
setIntrinsicHeight(s.height());
RenderFormElement::layout();
// and now disable the widget in case there is no <option> given
const Vector<HTMLElement*>& listItems = static_cast<HTMLSelectElement*>(node())->listItems();
bool foundOption = false;
for (unsigned i = 0; i < listItems.size() && !foundOption; i++)
foundOption = (listItems[i]->hasTagName(optionTag));
m_widget->setEnabled(foundOption && ! static_cast<HTMLSelectElement*>(node())->disabled());
}
void DeprecatedRenderSelect::selectionChanged(Widget*)
{
if (m_ignoreSelectEvents)
return;
// don't use listItems() here as we have to avoid recalculations - changing the
// option list will make use update options not in the way the user expects them
const Vector<HTMLElement*>& listItems = static_cast<HTMLSelectElement*>(node())->m_listItems;
int j = 0;
unsigned size = listItems.size();
for (unsigned i = 0; i < size; i++) {
// don't use setSelected() here because it will cause us to be called
// again with updateSelection.
if (listItems[i]->hasTagName(optionTag))
static_cast<HTMLOptionElement*>(listItems[i])
->m_selected = static_cast<ListBox*>(m_widget)->isSelected(j);
if (listItems[i]->hasTagName(optionTag) || listItems[i]->hasTagName(optgroupTag))
++j;
}
static_cast<HTMLSelectElement*>(node())->onChange();
}
void DeprecatedRenderSelect::setOptionsChanged(bool _optionsChanged)
{
m_optionsChanged = _optionsChanged;
}
ListBox* DeprecatedRenderSelect::createListBox()
{
ListBox *lb = new ListBox();
lb->setSelectionMode(m_multiple ? ListBox::Extended : ListBox::Single);
m_ignoreSelectEvents = false;
return lb;
}
void DeprecatedRenderSelect::updateSelection()
{
const Vector<HTMLElement*>& listItems = static_cast<HTMLSelectElement*>(node())->listItems();
int i;
// if multi-select, we select only the new selected index
ListBox *listBox = static_cast<ListBox*>(m_widget);
int j = 0;
for (i = 0; i < int(listItems.size()); i++) {
listBox->setSelected(j, listItems[i]->hasTagName(optionTag) &&
static_cast<HTMLOptionElement*>(listItems[i])->selected());
if (listItems[i]->hasTagName(optionTag) || listItems[i]->hasTagName(optgroupTag))
++j;
}
m_selectionChanged = false;
}
} // namespace WebCore