/* This file is part of the KDE project Copyright (C) 2006-2007 Jaroslaw Staniek This program 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 program 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 program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kexidbcombobox.h" #include "kexidblineedit.h" #include "../kexiformscrollview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include //! @internal class KexiDBComboBox::Private { public: Private() : popup(0) , visibleColumnInfo(0) , subWidgetsWithDisabledEvents(0) , isEditable(false) , buttonPressed(false) , mouseOver(false) , dataEnteredByHand(true) { } ~Private() { delete subWidgetsWithDisabledEvents; subWidgetsWithDisabledEvents = 0; } KexiComboBoxPopup *popup; KComboBox *paintedCombo; //!< fake combo used only to pass it as 'this' for TQStyle (because styles use ) TQSize sizeHint; //!< A cache for KexiDBComboBox::sizeHint(), //!< rebuilt by KexiDBComboBox::fontChange() and KexiDBComboBox::styleChange() KexiDB::QueryColumnInfo* visibleColumnInfo; TQPtrDict *subWidgetsWithDisabledEvents; //! used to collect subwidget and its children (if isEditable is false) bool isEditable : 1; //!< true is the combo box is editable bool buttonPressed : 1; bool mouseOver : 1; bool dataEnteredByHand : 1; bool designMode : 1; }; //------------------------------------- KexiDBComboBox::KexiDBComboBox(TQWidget *parent, const char *name, bool designMode) : KexiDBAutoField(parent, name, designMode, NoLabel) , KexiComboBoxBase() , d(new Private()) { setMouseTracking(true); setFocusPolicy(TQWidget::WheelFocus); installEventFilter(this); d->designMode = designMode; d->paintedCombo = new KComboBox(this); d->paintedCombo->hide(); d->paintedCombo->move(0,0); } KexiDBComboBox::~KexiDBComboBox() { delete d; } KexiComboBoxPopup *KexiDBComboBox::popup() const { return d->popup; } void KexiDBComboBox::setPopup(KexiComboBoxPopup *popup) { d->popup = popup; } void KexiDBComboBox::setEditable(bool set) { if (d->isEditable == set) return; d->isEditable = set; d->paintedCombo->setEditable(set); if (set) createEditor(); else { delete m_subwidget; m_subwidget = 0; } update(); } bool KexiDBComboBox::isEditable() const { return d->isEditable; } void KexiDBComboBox::paintEvent( TQPaintEvent * ) { TQPainter p( this ); TQColorGroup cg( palette().active() ); // if ( hasFocus() ) // cg.setColor(TQColorGroup::Base, cg.highlight()); // else cg.setColor(TQColorGroup::Base, paletteBackgroundColor()); //update base color using (reimplemented) bg color p.setPen(cg.text()); TQStyle::SFlags flags = TQStyle::Style_Default; if (isEnabled()) flags |= TQStyle::Style_Enabled; if (hasFocus()) flags |= TQStyle::Style_HasFocus; if (d->mouseOver) flags |= TQStyle::Style_MouseOver; if ( width() < 5 || height() < 5 ) { qDrawShadePanel( &p, rect(), cg, FALSE, 2, &cg.brush( TQColorGroup::Button ) ); return; } //! @todo support reverse layout //bool reverse = TQApplication::reverseLayout(); style().drawComplexControl( TQStyle::CC_ComboBox, &p, d->paintedCombo /*this*/, rect(), cg, flags, (uint)TQStyle::SC_All, (d->buttonPressed ? TQStyle::SC_ComboBoxArrow : TQStyle::SC_None ) ); if (d->isEditable) { //if editable, editor paints itself, nothing to do } else { //not editable: we need to paint the current item TQRect editorGeometry( this->editorGeometry() ); if ( hasFocus() ) { if (0==qstrcmp(style().name(), "windows")) //a hack p.fillRect( editorGeometry, cg.brush( TQColorGroup::Highlight ) ); TQRect r( TQStyle::visualRect( style().subRect( TQStyle::SR_ComboBoxFocusRect, d->paintedCombo ), this ) ); r = TQRect(r.left()-1, r.top()-1, r.width()+2, r.height()+2); //enlare by 1 pixel each side to avoid covering by the subwidget style().drawPrimitive( TQStyle::PE_FocusRect, &p, r, cg, flags | TQStyle::Style_FocusAtBorder, TQStyleOption(cg.highlight())); } //todo } } TQRect KexiDBComboBox::editorGeometry() const { TQRect r( TQStyle::visualRect( style().querySubControlMetrics(TQStyle::CC_ComboBox, d->paintedCombo, TQStyle::SC_ComboBoxEditField), d->paintedCombo ) ); //if ((height()-r.bottom())<6) // r.setBottom(height()-6); return r; } void KexiDBComboBox::createEditor() { KexiDBAutoField::createEditor(); if (m_subwidget) { m_subwidget->setGeometry( editorGeometry() ); if (!d->isEditable) { m_subwidget->setCursor(TQCursor(TQt::ArrowCursor)); // widgets like listedit have IbeamCursor, we don't want that TQPalette subwidgetPalette( m_subwidget->palette() ); subwidgetPalette.setColor(TQPalette::Active, TQColorGroup::Base, subwidgetPalette.color(TQPalette::Active, TQColorGroup::Button)); m_subwidget->setPalette( subwidgetPalette ); if (d->subWidgetsWithDisabledEvents) d->subWidgetsWithDisabledEvents->clear(); else d->subWidgetsWithDisabledEvents = new TQPtrDict(); d->subWidgetsWithDisabledEvents->insert(m_subwidget, (char*)1); m_subwidget->installEventFilter(this); TQObjectList *l = m_subwidget->queryList( "TQWidget" ); for ( TQObjectListIt it( *l ); it.current(); ++it ) { d->subWidgetsWithDisabledEvents->insert(it.current(), (char*)1); it.current()->installEventFilter(this); } delete l; } } updateGeometry(); } void KexiDBComboBox::setLabelPosition(LabelPosition position) { if(m_subwidget) { if (-1 != m_subwidget->metaObject()->findProperty("frameShape", true)) m_subwidget->setProperty("frameShape", TQVariant((int)TQFrame::NoFrame)); m_subwidget->setGeometry( editorGeometry() ); } // KexiSubwidgetInterface *subwidgetInterface = dynamic_cast((TQWidget*)m_subwidget); // update size policy // if (subwidgetInterface && subwidgetInterface->subwidgetStretchRequired(this)) { TQSizePolicy sizePolicy( this->sizePolicy() ); if(position == Left) sizePolicy.setHorData( TQSizePolicy::Minimum ); else sizePolicy.setVerData( TQSizePolicy::Minimum ); //m_subwidget->setSizePolicy(sizePolicy); setSizePolicy(sizePolicy); //} // } } TQRect KexiDBComboBox::buttonGeometry() const { TQRect arrowRect( style().querySubControlMetrics( TQStyle::CC_ComboBox, d->paintedCombo, TQStyle::SC_ComboBoxArrow) ); arrowRect = TQStyle::visualRect(arrowRect, d->paintedCombo); arrowRect.setHeight( TQMAX( height() - (2 * arrowRect.y()), arrowRect.height() ) ); // a fix for Motif style return arrowRect; } bool KexiDBComboBox::handleMousePressEvent(TQMouseEvent *e) { if ( e->button() != TQt::LeftButton || d->designMode ) return true; /*todo if ( m_discardNextMousePress ) { d->discardNextMousePress = FALSE; return; }*/ if ( /*count() &&*/ ( !isEditable() || buttonGeometry().contains( e->pos() ) ) ) { d->buttonPressed = false; /* if ( d->usingListBox() ) { listBox()->blockSignals( TRUE ); tqApp->sendEvent( listBox(), e ); // trigger the listbox's autoscroll listBox()->setCurrentItem(d->current); listBox()->blockSignals( FALSE ); popup(); if ( arrowRect.contains( e->pos() ) ) { d->arrowPressed = TRUE; d->arrowDown = TRUE; repaint( FALSE ); } } else {*/ showPopup(); return true; } return false; } bool KexiDBComboBox::handleKeyPressEvent(TQKeyEvent *ke) { const int k = ke->key(); const bool dropDown = (ke->state() == TQt::NoButton && ((k==TQt::Key_F2 && !d->isEditable) || k==TQt::Key_F4)) || (ke->state() == TQt::AltButton && k==TQt::Key_Down); const bool escPressed = ke->state() == TQt::NoButton && k==TQt::Key_Escape; const bool popupVisible = popup() && popup()->isVisible(); if ((dropDown || escPressed) && popupVisible) { popup()->hide(); return true; } else if (dropDown && !popupVisible) { d->buttonPressed = false; showPopup(); return true; } else if (popupVisible) { const bool enterPressed = k==TQt::Key_Enter || k==TQt::Key_Return; if (enterPressed/* && m_internalEditorValueChanged*/) { acceptPopupSelection(); return true; } return handleKeyPressForPopup( ke ); } return false; } bool KexiDBComboBox::keyPressed(TQKeyEvent *ke) { if (KexiDBAutoField::keyPressed(ke)) return true; const int k = ke->key(); const bool popupVisible = popup() && popup()->isVisible(); const bool escPressed = ke->state() == TQt::NoButton && k==TQt::Key_Escape; if (escPressed && popupVisible) { popup()->hide(); return true; } if (ke->state() == TQt::NoButton && (k==TQt::Key_PageDown || k==TQt::Key_PageUp) && popupVisible) return true; return false; } void KexiDBComboBox::mousePressEvent( TQMouseEvent *e ) { if (handleMousePressEvent(e)) return; // TQTimer::singleShot( 200, this, TQ_SLOT(internalClickTimeout())); // d->shortClick = TRUE; // } KexiDBAutoField::mousePressEvent( e ); } void KexiDBComboBox::mouseDoubleClickEvent( TQMouseEvent *e ) { mousePressEvent( e ); } bool KexiDBComboBox::eventFilter( TQObject *o, TQEvent *e ) { if (o==this) { if (e->type()==TQEvent::Resize) { d->paintedCombo->resize(size()); if (m_subwidget) m_subwidget->setGeometry( editorGeometry() ); } else if (e->type()==TQEvent::Enter) { if (!d->isEditable || /*over button if editable combo*/buttonGeometry().contains( static_cast(e)->pos() )) { d->mouseOver = true; update(); } } else if (e->type()==TQEvent::MouseMove) { if (d->isEditable) { const bool overButton = buttonGeometry().contains( static_cast(e)->pos() ); if (overButton != d->mouseOver) { d->mouseOver = overButton; update(); } } } else if (e->type()==TQEvent::Leave) { d->mouseOver = false; update(); } else if (e->type()==TQEvent::KeyPress) { // handle F2/F4 if (handleKeyPressEvent(static_cast(e))) return true; } else if (e->type()==TQEvent::FocusOut) { if (popup() && popup()->isVisible()) { popup()->hide(); undoChanges(); } } } else if (!d->isEditable && d->subWidgetsWithDisabledEvents && d->subWidgetsWithDisabledEvents->find(o)) { if (e->type()==TQEvent::MouseButtonPress) { // clicking the subwidget should mean the same as clicking the combo box (i.e. show the popup) if (handleMousePressEvent(static_cast(e))) return true; } else if (e->type()==TQEvent::KeyPress) { if (handleKeyPressEvent(static_cast(e))) return true; } return e->type()!=TQEvent::Paint; } return KexiDBAutoField::eventFilter( o, e ); } bool KexiDBComboBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const { Q_UNUSED(autoField); return true; } void KexiDBComboBox::setPaletteBackgroundColor( const TQColor & color ) { KexiDBAutoField::setPaletteBackgroundColor(color); TQPalette pal(palette()); TQColorGroup cg(pal.active()); pal.setColor(TQColorGroup::Base, red); pal.setColor(TQColorGroup::Background, red); pal.setActive(cg); TQWidget::setPalette(pal); update(); } bool KexiDBComboBox::valueChanged() { kdDebug() << "KexiDataItemInterface::valueChanged(): " << m_origValue.toString() << " ? " << value().toString() << endl; return m_origValue != value(); } void KexiDBComboBox::setColumnInfo(KexiDB::QueryColumnInfo* cinfo) { KexiFormDataItemInterface::setColumnInfo(cinfo); } void KexiDBComboBox::setVisibleColumnInfo(KexiDB::QueryColumnInfo* cinfo) { d->visibleColumnInfo = cinfo; // we're assuming we already have columnInfo() setColumnInfoInternal(columnInfo(), d->visibleColumnInfo); } KexiDB::QueryColumnInfo* KexiDBComboBox::visibleColumnInfo() const { return d->visibleColumnInfo; } void KexiDBComboBox::moveCursorToEndInInternalEditor() { if (d->isEditable && m_moveCursorToEndInInternalEditor_enabled) moveCursorToEnd(); } void KexiDBComboBox::selectAllInInternalEditor() { if (d->isEditable && m_selectAllInInternalEditor_enabled) selectAll(); } void KexiDBComboBox::setValueInternal(const TQVariant& add, bool removeOld) { //// use KexiDBAutoField instead of KexiComboBoxBase::setValueInternal //// expects existing popup(), but we want to have delayed creation if (popup()) popup()->hide(); KexiComboBoxBase::setValueInternal(add, removeOld); } void KexiDBComboBox::setVisibleValueInternal(const TQVariant& value) { KexiFormDataItemInterface *iface = dynamic_cast((TQWidget*)m_subwidget); if(iface) iface->setValue(value, TQVariant(), false /*!removeOld*/); } TQVariant KexiDBComboBox::visibleValue() { return KexiComboBoxBase::visibleValue(); } void KexiDBComboBox::setValueInInternalEditor(const TQVariant& value) { if (!m_setValueInInternalEditor_enabled) return; KexiFormDataItemInterface *iface = dynamic_cast((TQWidget*)m_subwidget); if(iface) iface->setValue(value, TQVariant(), false/*!removeOld*/); } TQVariant KexiDBComboBox::valueFromInternalEditor() { return KexiDBAutoField::value(); } TQPoint KexiDBComboBox::mapFromParentToGlobal(const TQPoint& pos) const { // const KexiFormScrollView* view = KexiUtils::findParentConst(this, "KexiFormScrollView"); if (!parentWidget()) return TQPoint(-1,-1); return parentWidget()->mapToGlobal(pos); // return view->viewport()->mapToGlobal(pos); } int KexiDBComboBox::popupWidthHint() const { return width(); //popup() ? popup()->width() : 0; } void KexiDBComboBox::fontChange( const TQFont & oldFont ) { d->sizeHint = TQSize(); //force rebuild the cache KexiDBAutoField::fontChange(oldFont); } void KexiDBComboBox::styleChange( TQStyle& oldStyle ) { KexiDBAutoField::styleChange( oldStyle ); d->sizeHint = TQSize(); //force rebuild the cache if (m_subwidget) m_subwidget->setGeometry( editorGeometry() ); } TQSize KexiDBComboBox::sizeHint() const { if ( isVisible() && d->sizeHint.isValid() ) return d->sizeHint; const int maxWidth = 7 * fontMetrics().width(TQChar('x')) + 18; const int maxHeight = TQMAX( fontMetrics().lineSpacing(), 14 ) + 2; d->sizeHint = (style().sizeFromContents(TQStyle::CT_ComboBox, d->paintedCombo, TQSize(maxWidth, maxHeight)).expandedTo(TQApplication::globalStrut())); return d->sizeHint; } void KexiDBComboBox::editRequested() { } void KexiDBComboBox::acceptRequested() { signalValueChanged(); } void KexiDBComboBox::slotRowAccepted(KexiTableItem *item, int row) { d->dataEnteredByHand = false; KexiComboBoxBase::slotRowAccepted(item, row); d->dataEnteredByHand = true; } void KexiDBComboBox::beforeSignalValueChanged() { if (d->dataEnteredByHand) { KexiFormDataItemInterface *iface = dynamic_cast((TQWidget*)m_subwidget); if (iface) { slotInternalEditorValueChanged( iface->value() ); } } } void KexiDBComboBox::undoChanges() { KexiDBAutoField::undoChanges(); KexiComboBoxBase::undoChanges(); } #include "kexidbcombobox.moc"