/* * 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) * (C) 2006 Maksim Orlovich (maksim@kde.org) * (C) 2007-2009 Germain Garand (germain@ebooksfrance.org) * (C) 2007 Mitz Pettel (mitz@webkit.org) * (C) 2007 Charles Samuels (charles@kde.org) * * 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ #include "render_form.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace khtml; using namespace DOM; // ----------------- proxy style used to apply some CSS properties to native Qt widgets ----------------- struct KHTMLProxyStyle : public KdeUiProxyStyle { KHTMLProxyStyle(QWidget *parent) : KdeUiProxyStyle(parent) { noBorder = false; left = right = top = bottom = 0; clearButtonOverlay = 0; setParent(parent); } QRect subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const { QRect r = style()->subElementRect(element, option, widget); switch (element) { case QStyle::SE_PushButtonContents: case QStyle::SE_LineEditContents: case QStyle::SE_ShapedFrameContents: r.adjust(left, top, -qMax(0, right - clearButtonOverlay), -bottom); default: break; } return r; } void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { if (element == QStyle::CE_ComboBoxLabel) { const QStyleOptionComboBox *o = qstyleoption_cast(option); if (o) { QStyleOptionComboBox comboOpt = *o; comboOpt.currentText = comboOpt.currentText.trimmed(); // by default combobox label is drawn left justified, vertical centered // translate it to reflect padding values comboOpt.rect.translate(left, (top - bottom) / 2); if (noBorder) { // Need to expand a bit for some styles comboOpt.rect.adjust(-1, -2, 1, 2); comboOpt.state &= ~State_On; } return style()->drawControl(element, &comboOpt, painter, widget); } } style()->drawControl(element, option, painter, widget); } void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *painter, const QWidget *widget) const { if ((cc == QStyle::CC_ComboBox) && noBorder) { if (const QStyleOptionComboBox *cbOpt = qstyleoption_cast(opt)) { bool enabled = (cbOpt->state & State_Enabled); QColor color = cbOpt->palette.color(QPalette::ButtonText); painter->save(); painter->setBackgroundMode(Qt::TransparentMode); painter->setPen(color); painter->setRenderHint(QPainter::Antialiasing); // Drop down indicator QRect arrowRect = style()->subControlRect(cc, opt, SC_ComboBoxArrow, widget); arrowRect.setTop(cbOpt->rect.top()); arrowRect.setBottom(cbOpt->rect.bottom()); arrowRect.setRight(cbOpt->rect.right() - 1); if (enabled && (cbOpt->state & State_On)) arrowRect.translate(1, 1); // push effect //if (!enabled) color = color.lighter(); painter->setBrush(enabled ? QBrush(color, Qt::SolidPattern) : Qt::NoBrush); QPolygon cbArrowDown; cbArrowDown.setPoints(6, 3,-2, 4,-2, 0,2, -4,-2, -3,-2, 0,1); cbArrowDown.translate((arrowRect.x() + (arrowRect.width() >> 1)), (arrowRect.y() + (arrowRect.height() >> 1))); painter->drawPolygon(cbArrowDown); // Focus rect (from qcleanlooksstyle) if (enabled && (cbOpt->state & State_HasFocus)) { QRect focusRect = style()->subElementRect(SE_ComboBoxFocusRect, cbOpt, widget); focusRect.adjust(0, -2, 0, 2); painter->setBrush(QBrush(color, Qt::Dense4Pattern)); painter->setBrushOrigin(focusRect.topLeft()); painter->setPen(Qt::NoPen); const QRect rects[4] = { QRect(focusRect.left(), focusRect.top(), focusRect.width(), 1), // Top QRect(focusRect.left(), focusRect.bottom(), focusRect.width(), 1), // Bottom QRect(focusRect.left(), focusRect.top(), 1, focusRect.height()), // Left QRect(focusRect.right(), focusRect.top(), 1, focusRect.height()) // Right }; painter->drawRects(rects, 4); } painter->restore(); return; } } style()->drawComplexControl(cc, opt, painter, widget); } QRect subControlRect(ComplexControl cc, const QStyleOptionComplex* opt, SubControl sc, const QWidget* widget) const { // Make sure we give combo popup's enough room to display contents; // Qt doesn't do this by default if (cc == QStyle::CC_ComboBox && sc == SC_ComboBoxListBoxPopup) { const QComboBox* cb = qobject_cast(widget); const QStyleOptionComboBox* cbOpt = qstyleoption_cast(opt); if (cb && cbOpt) { QFontMetrics fm = cb->fontMetrics(); // Compute content width; Qt uses the usual +4 magic number for icon/text margin int maxW = 0; for (int c = 0; c < cb->count(); ++c) { int iw = fm.width(cb->itemText(c)); if (!cb->itemIcon(c).isNull()) iw += 4 + cb->iconSize().width(); maxW = qMax(maxW, iw); } // Now let sizeFromContent add in extra stuff. maxW = style()->sizeFromContents(QStyle::CT_ComboBox, opt, QSize(maxW, 1), widget).width(); // How much more room do we need for the text? int extraW = maxW > cbOpt->rect.width() ? maxW - cbOpt->rect.width() : 0; QRect r = style()->subControlRect(cc, opt, sc, widget); r.setWidth(r.width() + extraW); return r; } } return style()->subControlRect(cc, opt, sc, widget); } int left, right, top, bottom; int clearButtonOverlay; bool noBorder; }; // --------------------------------------------------------------------- RenderFormElement::RenderFormElement(HTMLGenericFormElementImpl *element) : RenderWidget(element) // , m_state(0) , m_proxyStyle(0) , m_exposeInternalPadding(false) , m_isOxygenStyle(false) { // init RenderObject attributes setInline(true); // our object is Inline } RenderFormElement::~RenderFormElement() {} void RenderFormElement::setStyle(RenderStyle *_style) { RenderWidget::setStyle(_style); setPadding(); if (!shouldDisableNativeBorders()) { // When the widget shows native border, clipping background to border // results in a nasty rendering effects if (style()->backgroundLayers()->backgroundClip() == BGBORDER) { style()->accessBackgroundLayers()->setBackgroundClip(BGPADDING); } m_isOxygenStyle = QApplication::style()->objectName().contains("oxygen"); } } int RenderFormElement::paddingTop() const { return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingTop() : 0; } int RenderFormElement::paddingBottom() const { return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingBottom() : 0; } int RenderFormElement::paddingLeft() const { return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingLeft() : 0; } int RenderFormElement::paddingRight() const { return (!includesPadding() || m_exposeInternalPadding) ? RenderWidget::paddingRight() : 0; } bool RenderFormElement::includesPadding() const { return true; } void RenderFormElement::setPadding() { if (!includesPadding()) return; KHTMLProxyStyle *style = static_cast(getProxyStyle()); style->left = RenderWidget::paddingLeft(); style->right = RenderWidget::paddingRight(); style->top = RenderWidget::paddingTop(); style->bottom = RenderWidget::paddingBottom(); } KdeUiProxyStyle* RenderFormElement::getProxyStyle() { assert(widget()); if (m_proxyStyle) return m_proxyStyle; m_proxyStyle = new KHTMLProxyStyle(widget()); widget()->setStyle( m_proxyStyle ); return m_proxyStyle; } short RenderFormElement::baselinePosition( bool f ) const { return RenderWidget::baselinePosition( f ) - 2 - style()->fontMetrics().descent(); } void RenderFormElement::setQWidget( QWidget *w ) { // Avoid dangling proxy pointer when we switch widgets. // the widget will cleanup the proxy, as it is its kid. m_proxyStyle = 0; // sets the Qt Object Name for the purposes // of setPadding() -- this is because QStyleSheet // will propagate children of 'w' even if they are toplevel, like // the "find" dialog or the popup menu w->setObjectName("RenderFormElementWidget"); RenderWidget::setQWidget(w); } void RenderFormElement::updateFromElement() { m_widget->setEnabled(!element()->disabled()); // If we've disabled a focused element, clear its focus, // so Qt doesn't do funny stuff like let one type into a disabled // line edit. if (element()->disabled() && element()->focused()) document()->quietResetFocus(); RenderWidget::updateFromElement(); } // Some form widgets apply the padding internally (i.e. as if they were // some kind of inline-block). Thus we only want to expose that padding // while layouting (so that width/height calculations are correct), and // then pretend it does not exist, as it is beyond the replaced edge and // thus should not affect other calculations. void RenderFormElement::calcMinMaxWidth() { m_exposeInternalPadding = true; RenderWidget::calcMinMaxWidth(); m_exposeInternalPadding = false; } void RenderFormElement::calcWidth() { m_exposeInternalPadding = true; RenderWidget::calcWidth(); m_exposeInternalPadding = false; } void RenderFormElement::calcHeight() { m_exposeInternalPadding = true; RenderWidget::calcHeight(); m_exposeInternalPadding = false; } void RenderFormElement::layout() { KHTMLAssert( needsLayout() ); KHTMLAssert( minMaxKnown() ); // minimum height m_height = 0; calcWidth(); calcHeight(); if ( m_widget ) resizeWidget(m_width-borderLeft()-borderRight()-paddingLeft()-paddingRight(), m_height-borderTop()-borderBottom()-paddingTop()-paddingBottom()); setNeedsLayout(false); } int RenderFormElement::calcContentWidth(int w) const { if (!shouldDisableNativeBorders()) { if (style()->boxSizing() == CONTENT_BOX) { int nativeBorderWidth = m_widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, m_widget); return RenderBox::calcContentWidth(w) + 2 * nativeBorderWidth; } } return RenderBox::calcContentWidth(w); } int RenderFormElement::calcContentHeight(int h) const { if (!shouldDisableNativeBorders()) { if (style()->boxSizing() == CONTENT_BOX) { int nativeBorderWidth = m_widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, m_widget); return RenderBox::calcContentHeight(h) + 2 * nativeBorderWidth; } } return RenderBox::calcContentHeight(h); } void RenderFormElement::paintOneBackground(QPainter *p, const QColor& c, const BackgroundLayer* bgLayer, QRect clipr, int _tx, int _ty, int w, int height) { int fudge = 0; if (!shouldDisableNativeBorders()) { fudge = m_isOxygenStyle ? 3 : 1; } paintBackgroundExtended(p, c, bgLayer, clipr, _tx, _ty, w, height, fudge ? fudge : borderLeft() , fudge ? fudge : borderRight(), RenderWidget::paddingLeft(), RenderWidget::paddingRight(), fudge ? fudge : borderTop(), fudge ? fudge : borderBottom(), RenderWidget::paddingTop(), RenderWidget::paddingBottom()); } Qt::Alignment RenderFormElement::textAlignment() const { switch (style()->textAlign()) { case LEFT: case KHTML_LEFT: return Qt::AlignLeft; case RIGHT: case KHTML_RIGHT: return Qt::AlignRight; case CENTER: case KHTML_CENTER: return Qt::AlignHCenter; case JUSTIFY: // Just fall into the auto code for justify. case TAAUTO: return style()->direction() == RTL ? Qt::AlignRight : Qt::AlignLeft; } assert(false); // Should never be reached. return Qt::AlignLeft; } // ------------------------------------------------------------------------- RenderButton::RenderButton(HTMLGenericFormElementImpl *element) : RenderFormElement(element) { m_hasTextIndentHack = false; } short RenderButton::baselinePosition( bool f ) const { int ret = (height()-RenderWidget::paddingTop()-RenderWidget::paddingBottom()+1)/2; ret += marginTop() + RenderWidget::paddingTop(); ret += ((fontMetrics( f ).ascent())/2)-1; return ret; } void RenderButton::layout() { RenderFormElement::layout(); bool needsTextIndentHack = false; if (!style()->width().isAuto()) { // check if we need to simulate the effect of a popular // button text hiding 'trick' that makes use of negative text-indent, // which we do not support on form widgets. int ti = style()->textIndent().minWidth( containingBlockWidth() ); if (m_widget->width() <= qAbs(ti)) { needsTextIndentHack = true; } } if (m_hasTextIndentHack != needsTextIndentHack) { m_hasTextIndentHack = needsTextIndentHack; updateFromElement(); } } void RenderButton::setStyle(RenderStyle *style) { RenderFormElement::setStyle(style); if (shouldDisableNativeBorders()) { // we paint the borders ourselves on this button, // remove the widget's native ones. KHTMLProxyStyle* style = static_cast(getProxyStyle()); style->noBorder = true; } } // ------------------------------------------------------------------------------- RenderCheckBox::RenderCheckBox(HTMLInputElementImpl *element) : RenderButton(element) { CheckBoxWidget* b = new CheckBoxWidget(view()->widget()); //b->setAutoMask(true); b->setMouseTracking(true); setQWidget(b); // prevent firing toggled() signals on initialization b->setChecked(element->checked()); connect(b,SIGNAL(stateChanged(int)),this,SLOT(slotStateChanged(int))); m_ignoreStateChanged = false; } void RenderCheckBox::calcMinMaxWidth() { KHTMLAssert( !minMaxKnown() ); QCheckBox *cb = static_cast( m_widget ); QSize s( qMin(22, qMax(14, cb->style()->pixelMetric( QStyle::PM_IndicatorWidth ))), qMin(22, qMax(12, cb->style()->pixelMetric( QStyle::PM_IndicatorHeight ))) ); setIntrinsicWidth( s.width() ); setIntrinsicHeight( s.height() ); RenderButton::calcMinMaxWidth(); } void RenderCheckBox::updateFromElement() { if (widget()->isChecked() != element()->checked()) { m_ignoreStateChanged = true; widget()->setChecked(element()->checked()); m_ignoreStateChanged = false; } RenderButton::updateFromElement(); } void RenderCheckBox::slotStateChanged(int state) { if (m_ignoreStateChanged) return; element()->setChecked(state == Qt::Checked); } bool RenderCheckBox::handleEvent(const DOM::EventImpl& ev) { switch(ev.id()) { case EventImpl::DOMFOCUSIN_EVENT: case EventImpl::DOMFOCUSOUT_EVENT: case EventImpl::MOUSEMOVE_EVENT: case EventImpl::MOUSEOUT_EVENT: case EventImpl::MOUSEOVER_EVENT: return RenderButton::handleEvent(ev); default: break; } return false; } // ------------------------------------------------------------------------------- RenderRadioButton::RenderRadioButton(HTMLInputElementImpl *element) : RenderButton(element) { RadioButtonWidget* b = new RadioButtonWidget(view()->widget()); b->setMouseTracking(true); b->setAutoExclusive(false); setQWidget(b); // prevent firing toggled() signals on initialization b->setChecked(element->checked()); connect(b,SIGNAL(toggled(bool)),this,SLOT(slotToggled(bool))); m_ignoreToggled = false; } void RenderRadioButton::updateFromElement() { m_ignoreToggled = true; widget()->setChecked(element()->checked()); m_ignoreToggled = false; RenderButton::updateFromElement(); } void RenderRadioButton::calcMinMaxWidth() { KHTMLAssert( !minMaxKnown() ); QRadioButton *rb = static_cast( m_widget ); QSize s( qMin(22, qMax(14, rb->style()->pixelMetric( QStyle::PM_ExclusiveIndicatorWidth ))), qMin(20, qMax(12, rb->style()->pixelMetric( QStyle::PM_ExclusiveIndicatorHeight ))) ); setIntrinsicWidth( s.width() ); setIntrinsicHeight( s.height() ); RenderButton::calcMinMaxWidth(); } void RenderRadioButton::slotToggled(bool /*activated*/) { if (m_ignoreToggled) return; } bool RenderRadioButton::handleEvent(const DOM::EventImpl& ev) { switch(ev.id()) { case EventImpl::DOMFOCUSIN_EVENT: case EventImpl::DOMFOCUSOUT_EVENT: case EventImpl::MOUSEMOVE_EVENT: case EventImpl::MOUSEOUT_EVENT: case EventImpl::MOUSEOVER_EVENT: return RenderButton::handleEvent(ev); default: break; } return false; } // ------------------------------------------------------------------------------- static const QString &sBorderNoneSheet = KGlobal::staticQString("QPushButton{border:none}"); RenderSubmitButton::RenderSubmitButton(HTMLInputElementImpl *element) : RenderButton(element) { PushButtonWidget* p = new PushButtonWidget(view()->widget()); setQWidget(p); //p->setAutoMask(true); p->setMouseTracking(true); p->setDefault(false); p->setAutoDefault(false); } static inline void setStyleSheet_helper(const QString& s, QWidget* w) { // ### buggy Qt stylesheets mess with the widget palette. // force it again after any stylesheet update. QPalette pal = w->palette(); w->setStyleSheet(s); w->setPalette(pal); } void RenderSubmitButton::setPadding() { // Proxy styling doesn't work well enough for buttons. // Use stylesheets instead. tests/css/button-padding-top.html assert(!m_proxyStyle); if (!includesPadding()) return; if (!RenderWidget::paddingLeft() && !RenderWidget::paddingRight() && !RenderWidget::paddingTop() && !RenderWidget::paddingBottom()) { setStyleSheet_helper( (shouldDisableNativeBorders() ? sBorderNoneSheet : QString()), widget() ); return; } setStyleSheet_helper( QString("QPushButton{padding-left:%1px; padding-right:%2px; padding-top:%3px; padding-bottom:%4px}") .arg( RenderWidget::paddingLeft() ) .arg( RenderWidget::paddingRight() ) .arg( RenderWidget::paddingTop() ) .arg( RenderWidget::paddingBottom()) + (shouldDisableNativeBorders() ? sBorderNoneSheet : QString()) , widget()); } void RenderSubmitButton::setStyle(RenderStyle *style) { // Proxy styling doesn't work well enough for buttons. // Use stylesheets instead. tests/css/button-padding-top.html assert(!m_proxyStyle); RenderFormElement::setStyle(style); QString s = widget()->styleSheet(); if (shouldDisableNativeBorders()) { // we paint the borders ourselves on this button, // remove the widget's native ones. if (!s.contains(sBorderNoneSheet)) { s.append(sBorderNoneSheet); setStyleSheet_helper( s, widget() ); } } else { setStyleSheet_helper( s.remove(sBorderNoneSheet), widget() ); } } QString RenderSubmitButton::rawText() { QString value = element()->valueWithDefault().string(); value = value.trimmed(); QString raw; for(int i = 0; i < value.length(); i++) { raw += value[i]; if(value[i] == '&') raw += '&'; } return raw; } bool RenderSubmitButton::canHaveBorder() const { // ### TODO would be nice to be able to // return style()->hasBackgroundImage() here, // depending on a config option (e.g. 'favour usability/integration over aspect') // so that only buttons with both a custom border // and a background image are drawn without native styling. // // This would go in the same place, gui wise, as a choice of b/w default color scheme, // versus native color scheme. return true; } void RenderSubmitButton::calcMinMaxWidth() { KHTMLAssert( !minMaxKnown() ); QString raw = rawText(); QPushButton* pb = static_cast(m_widget); pb->setText(raw); pb->setFont(style()->font()); bool empty = raw.isEmpty(); if ( empty ) raw = QLatin1Char('X'); QFontMetrics fm = pb->fontMetrics(); QSize ts = fm.size( Qt::TextShowMnemonic, raw); //Oh boy. QStyleOptionButton butOpt; butOpt.init(pb); butOpt.text = raw; QSize s = pb->style()->sizeFromContents( QStyle::CT_PushButton, &butOpt, ts, pb ); s = s.expandedTo(QApplication::globalStrut()); int margin = pb->style()->pixelMetric( QStyle::PM_ButtonMargin) + pb->style()->pixelMetric( QStyle::PM_DefaultFrameWidth ) * 2; int w = ts.width() + margin; int h = s.height(); assert(includesPadding()); int hpadding = RenderWidget::paddingLeft() + RenderWidget::paddingRight(); int vpadding = RenderWidget::paddingTop() + RenderWidget::paddingBottom(); // add 30% margins to the width (heuristics to make it look similar to IE) // ### FIXME BASELINE: we could drop this emulation and adopt Mozilla style buttons // (+/- padding: 0px 8px 0px 8px) - IE is most often in a separate css // code path nowadays, so we have wider buttons than other engines. int toAdd = (w*13/10)-w-hpadding; toAdd = qMax(0,toAdd); w += toAdd; if (shouldDisableNativeBorders()) { // we paint the borders ourselves, so let's override our height to something saner h = ts.height(); } else { h -= vpadding; } s = QSize(w,h).expandedTo(QApplication::globalStrut()); setIntrinsicWidth( s.width() ); setIntrinsicHeight( s.height() ); RenderButton::calcMinMaxWidth(); } void RenderSubmitButton::updateFromElement() { QString oldText = static_cast(m_widget)->text(); QString newText = rawText(); static_cast(m_widget)->setText(newText); if ( oldText != newText ) setNeedsLayoutAndMinMaxRecalc(); RenderFormElement::updateFromElement(); } short RenderSubmitButton::baselinePosition( bool f ) const { int ret = (height()-RenderWidget::paddingTop()-RenderWidget::paddingBottom()+1)/2; ret += marginTop() + RenderWidget::paddingTop(); ret += ((fontMetrics( f ).ascent())/2)-2; return ret; } // ------------------------------------------------------------------------------- RenderResetButton::RenderResetButton(HTMLInputElementImpl *element) : RenderSubmitButton(element) { } // ------------------------------------------------------------------------------- namespace khtml { class CompletionWidget: public KCompletionBox { public: CompletionWidget( QWidget *parent = 0 ) : KCompletionBox( parent ) {} virtual QPoint globalPositionHint() const { QWidget* pw = parentWidget(); KHTMLWidget* kwp = dynamic_cast(pw); if (!kwp) { qDebug() << "CompletionWidget has no KHTMLWidget parent" << endl; return KCompletionBox::globalPositionHint(); } QPoint dest; KHTMLView* v = kwp->m_kwp->rootViewPos(dest); QPoint ret; if (v) { ret = v->mapToGlobal( dest + QPoint(0, pw->height()) ); int zoomLevel = v->zoomLevel(); if (zoomLevel != 100) { ret.setX( ret.x()*zoomLevel/100 ); ret.setY( ret.y()*zoomLevel/100 ); } } return ret; } }; } LineEditWidget::LineEditWidget(DOM::HTMLInputElementImpl* input, KHTMLView* view, QWidget* parent) : KLineEdit(parent), m_input(input), m_view(view) { m_kwp->setIsRedirected( true ); setMouseTracking(true); KActionCollection *ac = new KActionCollection(this); m_spellAction = KStandardAction::spelling( this, SLOT(slotCheckSpelling()), ac ); setCompletionBox( new CompletionWidget( this ) ); completionBox()->setObjectName("completion box"); completionBox()->setFont(font()); } LineEditWidget::~LineEditWidget() { } void LineEditWidget::slotCheckSpelling() { if ( text().isEmpty() ) { return; } Sonnet::Dialog *spellDialog = new Sonnet::Dialog(new Sonnet::BackgroundChecker(this), 0); connect(spellDialog, SIGNAL(replace(QString,int,QString)), this, SLOT(spellCheckerCorrected(QString,int,QString))); connect(spellDialog, SIGNAL(misspelling(QString,int)), this, SLOT(spellCheckerMisspelling(QString,int))); connect(spellDialog, SIGNAL(done(QString)), this, SLOT(slotSpellCheckDone(QString))); connect(spellDialog, SIGNAL(cancel()), this, SLOT(spellCheckerFinished())); connect(spellDialog, SIGNAL(stop()), this, SLOT(spellCheckerFinished())); spellDialog->setBuffer(text()); spellDialog->show(); } void LineEditWidget::spellCheckerMisspelling( const QString &_text, int pos) { highLightWord( _text.length(),pos ); } void LineEditWidget::setFocus() { KLineEdit::setFocus(); end( false ); } void LineEditWidget::highLightWord( unsigned int length, unsigned int pos ) { setSelection ( pos, length ); } void LineEditWidget::spellCheckerCorrected( const QString &old, int pos, const QString &corr ) { if( old!= corr ) { setSelection ( pos, old.length() ); insert( corr ); setSelection ( pos, corr.length() ); } } void LineEditWidget::spellCheckerFinished() { } void LineEditWidget::slotSpellCheckDone( const QString &s ) { if( s != text() ) setText( s ); } namespace khtml { /** * @internal */ class WebShortcutCreator { public: /** * @short Creates a Web Shourtcut without using kdebase SearchProvider class. * It is used by LineEditWidget. */ static bool createWebShortcut(QString query); private: static bool askData(QString &name, QString &keys); static void createFile(QString query, QString name, QString keys); }; bool WebShortcutCreator::createWebShortcut(QString query) { QString name = i18n( "New Web Shortcut" ); QString keys; if ( askData( name, keys ) ) { bool isOk; do { //It's going to be checked if the keys have already been assigned isOk = true; QStringList keyList( keys.split( ',' ) ); KService::List providers = KServiceTypeTrader::self()->query( "SearchProvider" ); foreach ( const KService::Ptr &provider, providers ) { if ( !isOk ) { break; } foreach ( const QString &s, provider->property( "Keys" ).toStringList() ) { if ( !isOk ) { break; } foreach ( const QString &t, keys ) { if ( !isOk ) { break; } if ( s == t ) { KMessageBox::sorry( 0, i18n( "%1 is already assigned to %2", s, provider->name() ), i18n( "Error" ) ); isOk = false; } } } } if ( !isOk && !askData( name, keys ) ) { return false; } } while ( !isOk ); createFile( query, name, keys ); return true; } else return false; } void WebShortcutCreator::createFile(QString query, QString name, QString keys) { // SearchProvider class is part of kdebase, so the file is written as // an standard desktop file. QString fileName( keys ); KStandardDirs dirs; QString dir = dirs.saveLocation( "services", "searchproviders" ); while ( KGlobal::dirs()->exists( dir + fileName + ".desktop" ) ) fileName += '_'; KDesktopFile f( dir + fileName + ".desktop"); f.desktopGroup().writeEntry( "Keys", keys ); f.desktopGroup().writeEntry( "Type", "Service" ); f.desktopGroup().writeEntry( "ServiceTypes", "SearchProvider" ); f.desktopGroup().writeEntry( "Name", name ); f.desktopGroup().writeEntry( "Query", query ); f.sync(); KBuildSycocaProgressDialog::rebuildKSycoca( 0 ); } bool WebShortcutCreator::askData(QString &name, QString &keys) { KDialog *dialog = new KDialog(); QWidget *widget = new QWidget(); dialog->setButtons( KDialog::Ok | KDialog::Cancel ); dialog->setCaption( name ); QVBoxLayout *mainLayout = new QVBoxLayout(); widget->setLayout( mainLayout ); dialog->setMainWidget( widget ); QHBoxLayout *layout = new QHBoxLayout(); mainLayout->addLayout( layout ); QLabel *label = new QLabel( i18n( "Search &provider name:" ) ); layout->addWidget( label ); QLineEdit *nameEdit = new QLineEdit( i18n( "New search provider" ) ); label->setBuddy( nameEdit ); layout->addWidget( nameEdit ); layout = new QHBoxLayout(); mainLayout->addLayout( layout ); label = new QLabel( i18n( "UR&I shortcuts:" ) ); layout->addWidget( label ); QLineEdit *keysEdit = new QLineEdit(); label->setBuddy( keysEdit ); layout->addWidget( keysEdit ); bool res = dialog->exec(); if (res) { name = nameEdit->text(); keys = keysEdit->text(); } delete dialog; return res; } } void LineEditWidget::slotCreateWebShortcut() { QString queryName( m_input->name().string() ); HTMLFormElementImpl *form = m_input->form(); KUrl url( form->action().string() ); KUrl baseUrl( m_view->part()->baseURL().url() + '?' ); if ( !url.hasPath() ) { url.setPath( baseUrl.path() ); } if ( !url.hasHost() ) { url.setProtocol( baseUrl.protocol() ); url.setHost( baseUrl.host() ); } NodeImpl *node; HTMLInputElementImpl *inputNode; for( unsigned long i = 0; ( node = form->elements()->item( i ) ); i++ ) { inputNode = dynamic_cast( node ); if ( inputNode ) { if ( ( !inputNode->name().string().size() ) || (inputNode->name().string() == queryName) ) { continue; } else { switch ( inputNode->inputType() ) { case HTMLInputElementImpl::CHECKBOX: case HTMLInputElementImpl::RADIO: if ( !inputNode->checked() ) { break; } case HTMLInputElementImpl::TEXT: case HTMLInputElementImpl::PASSWORD: case HTMLInputElementImpl::HIDDEN: url.addQueryItem( inputNode->name().string(), inputNode->value().string() ); default: break; } } } } QString query( url.url() ); if ( !query.contains( "?" ) ) { query += '?'; //This input is the only one of the form } query += '&' + queryName + "=\\{@}"; WebShortcutCreator::createWebShortcut( query ); } void LineEditWidget::contextMenuEvent(QContextMenuEvent *e) { QMenu* popup = createStandardContextMenu(); if ( !popup ) return; if (m_input->autoComplete()) { popup->addSeparator(); QAction* act = popup->addAction( KIcon("edit-clear-history"), i18n("Clear &History")); act->setEnabled(compObj() && !compObj()->isEmpty()); connect(act, SIGNAL(triggered()), this, SLOT(clearHistoryActivated())); } if (echoMode() == QLineEdit::Normal && !isReadOnly()) { popup->addSeparator(); popup->addAction( m_spellAction ); m_spellAction->setEnabled( !text().isEmpty() ); } if ( !m_view->part()->onlyLocalReferences() ) { popup->addSeparator(); QAction *act = popup->addAction( i18n("Create Web Shortcut") ); connect(act, SIGNAL(triggered()), this, SLOT(slotCreateWebShortcut())); } emit aboutToShowContextMenu(popup); popup->exec(e->globalPos()); delete popup; } void LineEditWidget::clearHistoryActivated() { m_view->clearCompletionHistory(m_input->name().string()); if (compObj()) compObj()->clear(); } bool LineEditWidget::event( QEvent *e ) { if (KLineEdit::event(e)) return true; #if 0 if ( e->type() == QEvent::AccelAvailable && isReadOnly() ) { QKeyEvent* ke = (QKeyEvent*) e; if ( ke->modifiers() & Qt::ControlModifier ) { switch ( ke->key() ) { case Qt::Key_Left: case Qt::Key_Right: case Qt::Key_Up: case Qt::Key_Down: case Qt::Key_Home: case Qt::Key_End: ke->accept(); default: break; } } } #endif return false; } void LineEditWidget::mouseMoveEvent(QMouseEvent *e) { // hack to prevent Qt from calling setCursor on the widget setDragEnabled(false); KLineEdit::mouseMoveEvent(e); setDragEnabled(true); } // ----------------------------------------------------------------------------- RenderLineEdit::RenderLineEdit(HTMLInputElementImpl *element) : RenderFormElement(element), m_blockElementUpdates(false) { LineEditWidget *edit = new LineEditWidget(element, view(), view()->widget()); connect(edit,SIGNAL(returnPressed()), this, SLOT(slotReturnPressed())); connect(edit,SIGNAL(textChanged(QString)),this,SLOT(slotTextChanged(QString))); if(element->inputType() == HTMLInputElementImpl::PASSWORD) edit->setEchoMode( QLineEdit::Password ); if ( element->autoComplete() ) { QStringList completions = view()->formCompletionItems(element->name().string()); if (completions.count()) { edit->completionObject()->setItems(completions); edit->setContextMenuPolicy(Qt::NoContextMenu); edit->completionBox()->setTabHandling( false ); } } setQWidget(edit); } short RenderLineEdit::baselinePosition( bool f ) const { bool hasFrame = static_cast(widget())->hasFrame(); int bTop = hasFrame ? 0 : borderTop(); int bBottom = hasFrame ? 0 : borderBottom(); int ret = (height()-RenderWidget::paddingTop()-RenderWidget::paddingBottom()-bTop-bBottom+1)/2; ret += marginTop() + RenderWidget::paddingTop() + bTop; ret += ((fontMetrics( f ).ascent())/2)-2; return ret; } void RenderLineEdit::setStyle(RenderStyle* _style) { RenderFormElement::setStyle( _style ); if (widget()->alignment() != textAlignment()) widget()->setAlignment(textAlignment()); bool showClearButton = (!shouldDisableNativeBorders() && !_style->hasBackgroundImage()); if (!showClearButton && widget()->isClearButtonShown()) { widget()->setClearButtonShown(false); } else if (showClearButton && !widget()->isClearButtonShown()) { widget()->setClearButtonShown(true); QObjectList children = widget()->children(); foreach (QObject* object, children) { QWidget *w = qobject_cast(object); if (w && !w->isWindow() && (w->objectName() == "KLineEditButton")) { // this duplicates KHTMLView's handleWidget but this widget // is created on demand, so it might not be here at ChildPolished time w->installEventFilter(view()); } } } if (m_proxyStyle) { static_cast(m_proxyStyle)->clearButtonOverlay = qMax(0, widget()->clearButtonUsedSize().width()); } } void RenderLineEdit::highLightWord( unsigned int length, unsigned int pos ) { LineEditWidget* w = static_cast(m_widget); if ( w ) w->highLightWord( length, pos ); } void RenderLineEdit::slotReturnPressed() { // don't submit the form when return was pressed in a completion-popup KCompletionBox *box = widget()->completionBox(false); if ( box && box->isVisible() && box->currentRow() != -1 ) { box->hide(); return; } // Emit onChange if necessary // Works but might not be enough, dirk said he had another solution at // hand (can't remember which) - David handleFocusOut(); HTMLFormElementImpl* fe = element()->form(); if ( fe ) fe->submitFromKeyboard(); } void RenderLineEdit::handleFocusOut() { if ( widget() && widget()->isModified() ) { element()->onChange(); widget()->setModified( false ); } } void RenderLineEdit::calcMinMaxWidth() { KHTMLAssert( !minMaxKnown() ); const QFontMetrics &fm = style()->fontMetrics(); QSize s; int size = (element()->size() > 0) ? (element()->size() + 1) : 17; // "some" int h = fm.lineSpacing(); int w = (fm.height() * size) / 2; // on average a character cell is twice as tall as it is wide QStyleOptionFrame opt; opt.initFrom(widget()); if (widget()->hasFrame()) opt.lineWidth = widget()->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, widget()); s = QSize(w, qMax(h, 14)); s = widget()->style()->sizeFromContents(QStyle::CT_LineEdit, &opt, s, widget()); s = s.expandedTo(QApplication::globalStrut()); setIntrinsicWidth( s.width() ); setIntrinsicHeight( s.height() ); RenderFormElement::calcMinMaxWidth(); } void RenderLineEdit::updateFromElement() { int ml = element()->maxLength(); if ( ml < 0 ) ml = 32767; if ( widget()->maxLength() != ml ) { widget()->setMaxLength( ml ); } if (element()->value().string() != widget()->text()) { m_blockElementUpdates = true; // Do not block signals here (#188374) int pos = widget()->cursorPosition(); widget()->setText(element()->value().string()); widget()->setCursorPosition(pos); m_blockElementUpdates = false; } widget()->setReadOnly(element()->readOnly()); widget()->setClickMessage(element()->placeholder().string().remove(QLatin1Char('\n')).remove(QLatin1Char('\r'))); RenderFormElement::updateFromElement(); } void RenderLineEdit::slotTextChanged(const QString &string) { if (m_blockElementUpdates) return; // don't use setValue here! element()->m_value = string.isNull() ? DOMString("") : string; element()->m_unsubmittedFormChange = true; } void RenderLineEdit::select() { static_cast(m_widget)->selectAll(); } long RenderLineEdit::selectionStart() { LineEditWidget* w = static_cast(m_widget); if (w->hasSelectedText()) return w->selectionStart(); else return w->cursorPosition(); } long RenderLineEdit::selectionEnd() { LineEditWidget* w = static_cast(m_widget); if (w->hasSelectedText()) return w->selectionStart() + w->selectedText().length(); else return w->cursorPosition(); } void RenderLineEdit::setSelectionStart(long pos) { LineEditWidget* w = static_cast(m_widget); //See whether we have a non-empty selection now. long end = selectionEnd(); if (end > pos) w->setSelection(pos, end - pos); w->setCursorPosition(pos); } void RenderLineEdit::setSelectionEnd(long pos) { LineEditWidget* w = static_cast(m_widget); //See whether we have a non-empty selection now. long start = selectionStart(); if (start < pos) w->setSelection(start, pos - start); w->setCursorPosition(pos); } void RenderLineEdit::setSelectionRange(long start, long end) { LineEditWidget* w = static_cast(m_widget); w->setCursorPosition(end); w->setSelection(start, end - start); } // --------------------------------------------------------------------------- RenderFieldset::RenderFieldset(HTMLGenericFormElementImpl *element) : RenderBlock(element) { m_intrinsicWidth = 0; } void RenderFieldset::calcMinMaxWidth() { RenderBlock::calcMinMaxWidth(); if (style()->htmlHacks()){ if (RenderObject* legend = findLegend()) { int legendMinWidth = legend->minWidth(); Length legendMarginLeft = legend->style()->marginLeft(); Length legendMarginRight = legend->style()->marginLeft(); if (legendMarginLeft.isFixed()) legendMinWidth += legendMarginLeft.value(); if (legendMarginRight.isFixed()) legendMinWidth += legendMarginRight.value(); m_intrinsicWidth = qMax((int)m_minWidth, legendMinWidth + paddingLeft() + paddingRight() + borderLeft() + borderRight()); }} } RenderObject* RenderFieldset::layoutLegend(bool relayoutChildren) { RenderObject* legend = findLegend(); if (legend) { if (relayoutChildren) legend->setNeedsLayout(true); legend->layoutIfNeeded(); int xPos = borderLeft() + paddingLeft() + legend->marginLeft(); if (style()->direction() == RTL) xPos = m_width - paddingRight() - borderRight() - legend->width() - legend->marginRight(); int b = borderTop(); int h = legend->height(); legend->setPos(xPos, qMax((b-h)/2, 0)); m_height = qMax(b,h) + paddingTop(); } return legend; } RenderObject* RenderFieldset::findLegend() const { for (RenderObject* legend = firstChild(); legend; legend = legend->nextSibling()) { if (!legend->isFloatingOrPositioned() && legend->element() && legend->element()->id() == ID_LEGEND) return legend; } return 0; } void RenderFieldset::paintBoxDecorations(PaintInfo& pI, int _tx, int _ty) { //kDebug( 6040 ) << renderName() << "::paintDecorations()"; RenderObject* legend = findLegend(); if (!legend) return RenderBlock::paintBoxDecorations(pI, _tx, _ty); int w = width(); int h = height() + borderTopExtra() + borderBottomExtra(); int yOff = (legend->yPos() > 0) ? 0 : (legend->height()-borderTop())/2; int legendBottom = _ty + legend->yPos() + legend->height(); h -= yOff; _ty += yOff - borderTopExtra(); QRect cr = QRect(_tx, _ty, w, h).intersected( pI.r ); paintOneBackground(pI.p, style()->backgroundColor(), style()->backgroundLayers(), cr, _tx, _ty, w, h); if ( style()->hasBorder() ) paintBorderMinusLegend(pI.p, _tx, _ty, w, h, style(), legend->xPos(), legend->width(), legendBottom); } void RenderFieldset::paintBorderMinusLegend(QPainter *p, int _tx, int _ty, int w, int h, const RenderStyle* style, int lx, int lw, int lb) { const QColor& tc = style->borderTopColor(); const QColor& bc = style->borderBottomColor(); EBorderStyle ts = style->borderTopStyle(); EBorderStyle bs = style->borderBottomStyle(); EBorderStyle ls = style->borderLeftStyle(); EBorderStyle rs = style->borderRightStyle(); bool render_t = ts > BHIDDEN; bool render_l = ls > BHIDDEN; bool render_r = rs > BHIDDEN; bool render_b = bs > BHIDDEN; int borderLeftWidth = style->borderLeftWidth(); int borderRightWidth = style->borderRightWidth(); if(render_t) { if (lx >= borderLeftWidth) drawBorder(p, _tx, _ty, _tx + lx, _ty + style->borderTopWidth(), BSTop, tc, style->color(), ts, (render_l && (ls == DOTTED || ls == DASHED || ls == DOUBLE)?style->borderLeftWidth():0), 0); if (lx + lw <= w - borderRightWidth) drawBorder(p, _tx+lx+lw, _ty, _tx + w, _ty + style->borderTopWidth(), BSTop, tc, style->color(), ts, 0, (render_r && (rs == DOTTED || rs == DASHED || rs == DOUBLE)?style->borderRightWidth():0)); } if(render_b) drawBorder(p, _tx, _ty + h - style->borderBottomWidth(), _tx + w, _ty + h, BSBottom, bc, style->color(), bs, (render_l && (ls == DOTTED || ls == DASHED || ls == DOUBLE)?style->borderLeftWidth():0), (render_r && (rs == DOTTED || rs == DASHED || rs == DOUBLE)?style->borderRightWidth():0)); if(render_l) { const QColor& lc = style->borderLeftColor(); bool ignore_top = (tc == lc) && (ls >= OUTSET) && (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET); bool ignore_bottom = (bc == lc) && (ls >= OUTSET) && (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET); int startY = _ty; if (lx < borderLeftWidth && lx + lw > 0) { // The legend intersects the border. ignore_top = true; startY = lb; } drawBorder(p, _tx, startY, _tx + borderLeftWidth, _ty + h, BSLeft, lc, style->color(), ls, ignore_top?0:style->borderTopWidth(), ignore_bottom?0:style->borderBottomWidth()); } if(render_r) { const QColor& rc = style->borderRightColor(); bool ignore_top = (tc == rc) && (rs >= DOTTED || rs == INSET) && (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET); bool ignore_bottom = (bc == rc) && (rs >= DOTTED || rs == INSET) && (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET); int startY = _ty; if (lx < w && lx + lw > w - borderRightWidth) { // The legend intersects the border. ignore_top = true; startY = lb; } drawBorder(p, _tx + w - borderRightWidth, startY, _tx + w, _ty + h, BSRight, rc, style->color(), rs, ignore_top?0:style->borderTopWidth(), ignore_bottom?0:style->borderBottomWidth()); } } void RenderFieldset::setStyle(RenderStyle* _style) { RenderBlock::setStyle(_style); // WinIE renders fieldsets with display:inline like they're inline-blocks. For us, // an inline-block is just a block element with replaced set to true and inline set // to true. Ensure that if we ended up being inline that we set our replaced flag // so that we're treated like an inline-block. if (isInline()) setReplaced(true); } // ------------------------------------------------------------------------- RenderFileButton::RenderFileButton(HTMLInputElementImpl *element) : RenderFormElement(element) { FileButtonWidget* w = new FileButtonWidget( view()->widget() ); w->setMode(KFile::File | KFile::ExistingOnly); w->lineEdit()->setCompletionBox( new CompletionWidget(w) ); w->completionObject()->setDir(KGlobalSettings::documentPath()); connect(w->lineEdit(), SIGNAL(returnPressed()), this, SLOT(slotReturnPressed())); connect(w->lineEdit(), SIGNAL(textChanged(QString)),this,SLOT(slotTextChanged(QString))); connect(w, SIGNAL(urlSelected(KUrl)),this,SLOT(slotUrlSelected(KUrl))); setQWidget(w); m_haveFocus = false; } short RenderFileButton::baselinePosition( bool f ) const { int bTop = borderTop(); int bBottom = borderBottom(); int ret = (height()-paddingTop()-paddingBottom()-bTop-bBottom+1)/2; ret += marginTop() + paddingTop() + bTop; ret += ((fontMetrics( f ).ascent())/2)-2; return ret; } void RenderFileButton::calcMinMaxWidth() { KHTMLAssert( !minMaxKnown() ); const QFontMetrics &fm = style()->fontMetrics(); int size = (element()->size() > 0) ? (element()->size() + 1) : 17; // "some" int h = fm.lineSpacing(); int w = (fm.height() * size) / 2; // on average a character cell is twice as tall as it is wide KLineEdit* edit = widget()->lineEdit(); QStyleOptionFrame opt; opt.initFrom(edit); if (edit->hasFrame()) opt.lineWidth = edit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, edit); QSize s(w, qMax(h, 14)); s = edit->style()->sizeFromContents(QStyle::CT_LineEdit, &opt, s, edit); s = s.expandedTo(QApplication::globalStrut()); QSize bs = widget()->minimumSizeHint() - edit->minimumSizeHint(); setIntrinsicWidth( s.width() + bs.width() ); setIntrinsicHeight( qMax(s.height(), bs.height()) ); RenderFormElement::calcMinMaxWidth(); } void RenderFileButton::handleFocusOut() { if ( widget()->lineEdit() && widget()->lineEdit()->isModified() ) { element()->onChange(); widget()->lineEdit()->setModified( false ); } } void RenderFileButton::updateFromElement() { KLineEdit* edit = widget()->lineEdit(); bool blocked = edit->blockSignals(true); edit->setText(element()->value().string()); edit->blockSignals(blocked); edit->setModified(false); RenderFormElement::updateFromElement(); } void RenderFileButton::slotReturnPressed() { // don't submit the form when return was pressed in a completion-popup KCompletionBox* box = widget()->lineEdit()->completionBox(false); if (box && box->isVisible() && box->currentRow() != -1) { box->hide(); return; } handleFocusOut(); if (element()->form()) element()->form()->submitFromKeyboard(); } void RenderFileButton::slotTextChanged(const QString &/*string*/) { element()->m_value = KUrl( widget()->url() ).pathOrUrl(); } void RenderFileButton::slotUrlSelected(const KUrl &) { element()->onChange(); } void RenderFileButton::select() { widget()->lineEdit()->selectAll(); } // ------------------------------------------------------------------------- RenderLabel::RenderLabel(HTMLGenericFormElementImpl *element) : RenderFormElement(element) { } // ------------------------------------------------------------------------- RenderLegend::RenderLegend(HTMLGenericFormElementImpl *element) : RenderBlock(element) { } // ------------------------------------------------------------------------------- bool ListBoxWidget::event( QEvent * event ) { // accept all wheel events so that they are not propagated to the view // once either end of the list is reached. bool ret = KListWidget::event(event); if (event->type() == QEvent::Wheel) { event->accept(); ret = true; } return ret; } ComboBoxWidget::ComboBoxWidget(QWidget *parent) : KComboBox(false, parent) { m_kwp->setIsRedirected( true ); //setAutoMask(true); if (view()) view()->installEventFilter(this); setMouseTracking(true); } void ComboBoxWidget::showPopup() { QPoint p = pos(); QPoint dest( p ); QWidget* parent = parentWidget(); KHTMLView* v = m_kwp->rootViewPos(dest); int zoomLevel = v ? v->zoomLevel() : 100; if (zoomLevel != 100) { if (v) { // we need to place the popup even lower on the screen, take in count the widget is bigger // now, so we add also the difference between the original height, and the zoomed height dest.setY(dest.y() + (sizeHint().height() * zoomLevel / 100 - sizeHint().height())); } } bool blocked = blockSignals(true); if (v != parent) { setParent(v); } move( dest ); blockSignals(blocked); KComboBox::showPopup(); blocked = blockSignals(true); if (v != parent) { setParent(parent); // undo side effect of setParent() show(); } move( p ); blockSignals(blocked); } void ComboBoxWidget::hidePopup() { KComboBox::hidePopup(); } bool ComboBoxWidget::event(QEvent *e) { if (KComboBox::event(e)) return true; if (e->type()==QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); switch(ke->key()) { case Qt::Key_Return: case Qt::Key_Enter: showPopup(); ke->accept(); return true; default: return false; } } return false; } bool ComboBoxWidget::eventFilter(QObject *dest, QEvent *e) { if (dest==view() && e->type()==QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); bool forward = false; switch(ke->key()) { case Qt::Key_Tab: forward=true; // fall through case Qt::Key_Backtab: // ugly hack. emulate popdownlistbox() (private in QComboBox) // we re-use ke here to store the reference to the generated event. ke = new QKeyEvent(QEvent::KeyPress, Qt::Key_Escape, Qt::NoModifier); QApplication::sendEvent(dest,ke); focusNextPrevChild(forward); delete ke; return true; default: return KComboBox::eventFilter(dest, e); } } return KComboBox::eventFilter(dest, e); } void ComboBoxWidget::keyPressEvent(QKeyEvent *e) { // Normally, widgets are not sent Tab keys this way in the first // place as they are handled by QWidget::event() for focus handling // already. But we get our events via EventPropagator::sendEvent() // directly. Ignore them so that HTMLGenericFormElementImpl:: // defaultEventHandler() can call focusNextPrev(). if (e->key() == Qt::Key_Tab || e->key() == Qt::Key_Backtab) { e->ignore(); return; } KComboBox::keyPressEvent(e); } // ------------------------------------------------------------------------- RenderSelect::RenderSelect(HTMLSelectElementImpl *element) : RenderFormElement(element) { m_ignoreSelectEvents = false; m_multiple = element->multiple(); m_size = element->size(); m_useListBox = (m_multiple || m_size > 1); m_selectionChanged = true; m_optionsChanged = true; if(m_useListBox) setQWidget(createListBox()); else { setQWidget(createComboBox()); getProxyStyle(); // We always need it to make sure popups are big enough } } void RenderSelect::clearItemFlags(int index, Qt::ItemFlags flags) { if(m_useListBox) { QListWidgetItem* item = static_cast(m_widget)->item(index); item->setFlags(item->flags() & ~flags); } else { KComboBox* combo = static_cast(m_widget); if (QStandardItemModel* model = qobject_cast(combo->model())) { QStandardItem* item = model->item(index); item->setFlags(item->flags() & ~flags); } } } void RenderSelect::setStyle(RenderStyle *_style) { RenderFormElement::setStyle(_style); if (!m_useListBox) { KHTMLProxyStyle* proxyStyle = static_cast(getProxyStyle()); proxyStyle->noBorder = shouldDisableNativeBorders(); } } void RenderSelect::updateFromElement() { m_ignoreSelectEvents = true; // change widget type bool oldMultiple = m_multiple; unsigned oldSize = m_size; bool oldListbox = m_useListBox; m_multiple = element()->multiple(); m_size = element()->size(); m_useListBox = (m_multiple || m_size > 1); if (oldMultiple != m_multiple || oldSize != m_size) { if (m_useListBox != oldListbox) { // type of select has changed if (m_useListBox) setQWidget(createListBox()); else setQWidget(createComboBox()); // Call setStyle() to fix unwanted font size change (#142722) // and to update our proxy style properties setStyle(style()); } if (m_useListBox && oldMultiple != m_multiple) { static_cast(m_widget)->setSelectionMode(m_multiple ? QListWidget::ExtendedSelection : QListWidget::SingleSelection); } m_selectionChanged = true; m_optionsChanged = true; } // update contents listbox/combobox based on options in m_element if ( m_optionsChanged ) { if (element()->m_recalcListItems) element()->recalcListItems(); const QVector listItems = element()->listItems(); int listIndex; if(m_useListBox) static_cast(m_widget)->clear(); else static_cast(m_widget)->clear(); for (listIndex = 0; listIndex < int(listItems.size()); listIndex++) { if (listItems[listIndex]->id() == ID_OPTGROUP) { DOMString text = listItems[listIndex]->getAttribute(ATTR_LABEL); if (text.isNull()) text = ""; text = text.implementation()->collapseWhiteSpace(false, false); if(m_useListBox) { QListWidgetItem *item = new QListWidgetItem(QString(text.implementation()->s, text.implementation()->l)); static_cast(m_widget)->insertItem(listIndex,item); } else { static_cast(m_widget)->insertItem(listIndex, QString(text.implementation()->s, text.implementation()->l)); } bool disabled = !listItems[listIndex]->getAttribute(ATTR_DISABLED).isNull(); if (disabled) clearItemFlags(listIndex, Qt::ItemIsSelectable | Qt::ItemIsEnabled); else clearItemFlags(listIndex, Qt::ItemIsSelectable); } else if (listItems[listIndex]->id() == ID_OPTION) { HTMLOptionElementImpl* optElem = static_cast(listItems[listIndex]); DOMString domText = optElem->text(); // Prefer label if set DOMString label = optElem->getAttribute(ATTR_LABEL); if (!label.isEmpty()) domText = label; domText = domText.implementation()->collapseWhiteSpace(false, false); QString text; ElementImpl* parentOptGroup = optElem->parentNode()->id() == ID_OPTGROUP ? static_cast(optElem->parentNode()) : 0; if (parentOptGroup) { text = QLatin1String(" ") + domText.string(); } else { text = domText.string(); } if(m_useListBox) static_cast(m_widget)->insertItem(listIndex,text); else static_cast(m_widget)->insertItem(listIndex, text); bool disabled = !optElem->getAttribute(ATTR_DISABLED).isNull(); if (parentOptGroup) disabled = disabled || !parentOptGroup->getAttribute(ATTR_DISABLED).isNull(); if (disabled) clearItemFlags(listIndex, Qt::ItemIsSelectable | Qt::ItemIsEnabled); } else { KHTMLAssert(false); } m_selectionChanged = true; } // QComboBox caches the size hint unless you call setFont (ref: TT docu) if(!m_useListBox) { KComboBox *that = static_cast(m_widget); that->setFont( that->font() ); } setNeedsLayoutAndMinMaxRecalc(); m_optionsChanged = false; } // update selection if (m_selectionChanged) { updateSelection(); } m_ignoreSelectEvents = false; RenderFormElement::updateFromElement(); } short RenderSelect::baselinePosition( bool f ) const { if (m_useListBox) return RenderFormElement::baselinePosition(f); int bTop = shouldDisableNativeBorders() ? borderTop() : 0; int bBottom = shouldDisableNativeBorders() ? borderBottom() : 0; int ret = (height()-RenderWidget::paddingTop()-RenderWidget::paddingBottom()-bTop-bBottom+1)/2; ret += marginTop() + RenderWidget::paddingTop() + bTop; ret += ((fontMetrics( f ).ascent())/2)-2; return ret; } void RenderSelect::calcMinMaxWidth() { KHTMLAssert( !minMaxKnown() ); if (m_optionsChanged) updateFromElement(); // ### ugly HACK FIXME!!! setMinMaxKnown(); layoutIfNeeded(); setNeedsLayoutAndMinMaxRecalc(); // ### end FIXME RenderFormElement::calcMinMaxWidth(); } void RenderSelect::layout( ) { KHTMLAssert(needsLayout()); KHTMLAssert(minMaxKnown()); // ### maintain selection properly between type/size changes, and work // out how to handle multiselect->singleselect (probably just select // first selected one) // calculate size if(m_useListBox) { KListWidget* w = static_cast(m_widget); int width = 0; int height = 0; QAbstractItemModel *m = w->model(); QAbstractItemDelegate *d = w->itemDelegate(); QStyleOptionViewItem so; so.font = w->font(); for ( int rowIndex = 0 ; rowIndex < w->count() ; rowIndex++ ) { QModelIndex mi = m->index(rowIndex, 0); QSize s = d->sizeHint( so, mi); width = qMax(width, s.width()); height = qMax(height, s.height()); } if ( !height ) height = w->fontMetrics().height(); if ( !width ) width = w->fontMetrics().width( 'x' ); int size = m_size; // check if multiple and size was not given or invalid // Internet Exploder sets size to qMin(number of elements, 4) // Netscape seems to simply set it to "number of elements" // the average of that is IMHO qMin(number of elements, 10) // so I did that ;-) if(size < 1) size = qMin(w->count(), 10); QStyleOptionFrameV3 opt; opt.initFrom(w); opt.lineWidth = w->lineWidth(); opt.midLineWidth = w->midLineWidth(); opt.frameShape = w->frameShape(); QRect r = w->style()->subElementRect(QStyle::SE_ShapedFrameContents, &opt, w); QRect o = opt.rect; int hfw = (r.left()-o.left()) + (o.right()-r.right()); int vfw = (r.top()-o.top()) + (o.bottom()-r.bottom()); width += hfw + w->verticalScrollBar()->sizeHint().width(); // FIXME BASELINE: the 3 lines below could be removed. int lhs = m_widget->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); if (lhs>0) width += lhs; height = size*height + vfw; assert( includesPadding() ); width -= RenderWidget::paddingLeft() + RenderWidget::paddingRight(); height -= RenderWidget::paddingTop() + RenderWidget::paddingBottom(); setIntrinsicWidth( width ); setIntrinsicHeight( height ); } else { QSize s(m_widget->sizeHint()); int w = s.width(); int h = s.height(); if (shouldDisableNativeBorders()) { const int dfw = 2 * m_widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth, 0, m_widget); w -= dfw; h -= dfw; } setIntrinsicWidth(w); setIntrinsicHeight(h); } /// uuh, ignore the following line.. setNeedsLayout(true); RenderFormElement::layout(); // and now disable the widget in case there is no