diff options
Diffstat (limited to 'kexi/plugins/forms/widgets/kexidbcombobox.cpp')
-rw-r--r-- | kexi/plugins/forms/widgets/kexidbcombobox.cpp | 550 |
1 files changed, 550 insertions, 0 deletions
diff --git a/kexi/plugins/forms/widgets/kexidbcombobox.cpp b/kexi/plugins/forms/widgets/kexidbcombobox.cpp new file mode 100644 index 00000000..19366a15 --- /dev/null +++ b/kexi/plugins/forms/widgets/kexidbcombobox.cpp @@ -0,0 +1,550 @@ +/* This file is part of the KDE project + Copyright (C) 2006-2007 Jaroslaw Staniek <js@iidea.pl> + + 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 <kcombobox.h> +#include <kdebug.h> +#include <kapplication.h> + +#include <qmetaobject.h> +#include <qpainter.h> +#include <qstyle.h> +#include <qdrawutil.h> +#include <qptrdict.h> +#include <qcursor.h> + +#include <kexidb/queryschema.h> +#include <widget/tableview/kexicomboboxpopup.h> +#include <widget/tableview/kexicelleditorfactory.h> +#include <kexiutils/utils.h> + +//! @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 QStyle (because styles use <static_cast>) + QSize sizeHint; //!< A cache for KexiDBComboBox::sizeHint(), + //!< rebuilt by KexiDBComboBox::fontChange() and KexiDBComboBox::styleChange() + KexiDB::QueryColumnInfo* visibleColumnInfo; + QPtrDict<char> *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(QWidget *parent, const char *name, bool designMode) + : KexiDBAutoField(parent, name, designMode, NoLabel) + , KexiComboBoxBase() + , d(new Private()) +{ + setMouseTracking(true); + setFocusPolicy(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( QPaintEvent * ) +{ + QPainter p( this ); + QColorGroup cg( palette().active() ); +// if ( hasFocus() ) +// cg.setColor(QColorGroup::Base, cg.highlight()); +// else + cg.setColor(QColorGroup::Base, paletteBackgroundColor()); //update base color using (reimplemented) bg color + p.setPen(cg.text()); + + QStyle::SFlags flags = QStyle::Style_Default; + if (isEnabled()) + flags |= QStyle::Style_Enabled; + if (hasFocus()) + flags |= QStyle::Style_HasFocus; + if (d->mouseOver) + flags |= QStyle::Style_MouseOver; + + if ( width() < 5 || height() < 5 ) { + qDrawShadePanel( &p, rect(), cg, FALSE, 2, &cg.brush( QColorGroup::Button ) ); + return; + } + +//! @todo support reverse layout +//bool reverse = QApplication::reverseLayout(); + style().drawComplexControl( QStyle::CC_ComboBox, &p, d->paintedCombo /*this*/, rect(), cg, + flags, (uint)QStyle::SC_All, + (d->buttonPressed ? QStyle::SC_ComboBoxArrow : QStyle::SC_None ) + ); + + if (d->isEditable) { + //if editable, editor paints itself, nothing to do + } + else { //not editable: we need to paint the current item + QRect editorGeometry( this->editorGeometry() ); + if ( hasFocus() ) { + if (0==qstrcmp(style().name(), "windows")) //a hack + p.fillRect( editorGeometry, cg.brush( QColorGroup::Highlight ) ); + QRect r( QStyle::visualRect( style().subRect( QStyle::SR_ComboBoxFocusRect, d->paintedCombo ), this ) ); + r = QRect(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( QStyle::PE_FocusRect, &p, + r, cg, flags | QStyle::Style_FocusAtBorder, QStyleOption(cg.highlight())); + } + //todo + } +} + +QRect KexiDBComboBox::editorGeometry() const +{ + QRect r( QStyle::visualRect( + style().querySubControlMetrics(QStyle::CC_ComboBox, d->paintedCombo, + QStyle::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(QCursor(Qt::ArrowCursor)); // widgets like listedit have IbeamCursor, we don't want that +//! @todo Qt4: set transparent background, for now we're setting button color + QPalette subwidgetPalette( m_subwidget->palette() ); + subwidgetPalette.setColor(QPalette::Active, QColorGroup::Base, + subwidgetPalette.color(QPalette::Active, QColorGroup::Button)); + m_subwidget->setPalette( subwidgetPalette ); + if (d->subWidgetsWithDisabledEvents) + d->subWidgetsWithDisabledEvents->clear(); + else + d->subWidgetsWithDisabledEvents = new QPtrDict<char>(); + d->subWidgetsWithDisabledEvents->insert(m_subwidget, (char*)1); + m_subwidget->installEventFilter(this); + QObjectList *l = m_subwidget->queryList( "QWidget" ); + for ( QObjectListIt 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", QVariant((int)QFrame::NoFrame)); + m_subwidget->setGeometry( editorGeometry() ); + } +// KexiSubwidgetInterface *subwidgetInterface = dynamic_cast<KexiSubwidgetInterface*>((QWidget*)m_subwidget); + // update size policy +// if (subwidgetInterface && subwidgetInterface->subwidgetStretchRequired(this)) { + QSizePolicy sizePolicy( this->sizePolicy() ); + if(position == Left) + sizePolicy.setHorData( QSizePolicy::Minimum ); + else + sizePolicy.setVerData( QSizePolicy::Minimum ); + //m_subwidget->setSizePolicy(sizePolicy); + setSizePolicy(sizePolicy); + //} +// } +} + +QRect KexiDBComboBox::buttonGeometry() const +{ + QRect arrowRect( + style().querySubControlMetrics( QStyle::CC_ComboBox, d->paintedCombo, QStyle::SC_ComboBoxArrow) ); + arrowRect = QStyle::visualRect(arrowRect, d->paintedCombo); + arrowRect.setHeight( QMAX( height() - (2 * arrowRect.y()), arrowRect.height() ) ); // a fix for Motif style + return arrowRect; +} + +bool KexiDBComboBox::handleMousePressEvent(QMouseEvent *e) +{ + if ( e->button() != Qt::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 ); + qApp->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(QKeyEvent *ke) +{ + const int k = ke->key(); + const bool dropDown = (ke->state() == Qt::NoButton && ((k==Qt::Key_F2 && !d->isEditable) || k==Qt::Key_F4)) + || (ke->state() == Qt::AltButton && k==Qt::Key_Down); + const bool escPressed = ke->state() == Qt::NoButton && k==Qt::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==Qt::Key_Enter || k==Qt::Key_Return; + if (enterPressed/* && m_internalEditorValueChanged*/) { + acceptPopupSelection(); + return true; + } + return handleKeyPressForPopup( ke ); + } + + return false; +} + +bool KexiDBComboBox::keyPressed(QKeyEvent *ke) +{ + if (KexiDBAutoField::keyPressed(ke)) + return true; + + const int k = ke->key(); + const bool popupVisible = popup() && popup()->isVisible(); + const bool escPressed = ke->state() == Qt::NoButton && k==Qt::Key_Escape; + if (escPressed && popupVisible) { + popup()->hide(); + return true; + } + if (ke->state() == Qt::NoButton && (k==Qt::Key_PageDown || k==Qt::Key_PageUp) && popupVisible) + return true; + return false; +} + +void KexiDBComboBox::mousePressEvent( QMouseEvent *e ) +{ + if (handleMousePressEvent(e)) + return; + +// QTimer::singleShot( 200, this, SLOT(internalClickTimeout())); +// d->shortClick = TRUE; +// } + KexiDBAutoField::mousePressEvent( e ); +} + +void KexiDBComboBox::mouseDoubleClickEvent( QMouseEvent *e ) +{ + mousePressEvent( e ); +} + +bool KexiDBComboBox::eventFilter( QObject *o, QEvent *e ) +{ + if (o==this) { + if (e->type()==QEvent::Resize) { + d->paintedCombo->resize(size()); + if (m_subwidget) + m_subwidget->setGeometry( editorGeometry() ); + } + else if (e->type()==QEvent::Enter) { + if (!d->isEditable + || /*over button if editable combo*/buttonGeometry().contains( static_cast<QMouseEvent*>(e)->pos() )) + { + d->mouseOver = true; + update(); + } + } + else if (e->type()==QEvent::MouseMove) { + if (d->isEditable) { + const bool overButton = buttonGeometry().contains( static_cast<QMouseEvent*>(e)->pos() ); + if (overButton != d->mouseOver) { + d->mouseOver = overButton; + update(); + } + } + } + else if (e->type()==QEvent::Leave) { + d->mouseOver = false; + update(); + } + else if (e->type()==QEvent::KeyPress) { + // handle F2/F4 + if (handleKeyPressEvent(static_cast<QKeyEvent*>(e))) + return true; + } + else if (e->type()==QEvent::FocusOut) { + if (popup() && popup()->isVisible()) { + popup()->hide(); + undoChanges(); + } + } + } + else if (!d->isEditable && d->subWidgetsWithDisabledEvents && d->subWidgetsWithDisabledEvents->find(o)) { + if (e->type()==QEvent::MouseButtonPress) { + // clicking the subwidget should mean the same as clicking the combo box (i.e. show the popup) + if (handleMousePressEvent(static_cast<QMouseEvent*>(e))) + return true; + } + else if (e->type()==QEvent::KeyPress) { + if (handleKeyPressEvent(static_cast<QKeyEvent*>(e))) + return true; + } + return e->type()!=QEvent::Paint; + } + return KexiDBAutoField::eventFilter( o, e ); +} + +bool KexiDBComboBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const +{ + Q_UNUSED(autoField); + return true; +} + +void KexiDBComboBox::setPaletteBackgroundColor( const QColor & color ) +{ + KexiDBAutoField::setPaletteBackgroundColor(color); + QPalette pal(palette()); + QColorGroup cg(pal.active()); + pal.setColor(QColorGroup::Base, red); + pal.setColor(QColorGroup::Background, red); + pal.setActive(cg); + QWidget::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 QVariant& 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 QVariant& value) +{ + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + iface->setValue(value, QVariant(), false /*!removeOld*/); +} + +QVariant KexiDBComboBox::visibleValue() +{ + return KexiComboBoxBase::visibleValue(); +} + +void KexiDBComboBox::setValueInInternalEditor(const QVariant& value) +{ + if (!m_setValueInInternalEditor_enabled) + return; + KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if(iface) + iface->setValue(value, QVariant(), false/*!removeOld*/); +} + +QVariant KexiDBComboBox::valueFromInternalEditor() +{ + return KexiDBAutoField::value(); +} + +QPoint KexiDBComboBox::mapFromParentToGlobal(const QPoint& pos) const +{ +// const KexiFormScrollView* view = KexiUtils::findParentConst<const KexiFormScrollView>(this, "KexiFormScrollView"); + if (!parentWidget()) + return QPoint(-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 QFont & oldFont ) +{ + d->sizeHint = QSize(); //force rebuild the cache + KexiDBAutoField::fontChange(oldFont); +} + +void KexiDBComboBox::styleChange( QStyle& oldStyle ) +{ + KexiDBAutoField::styleChange( oldStyle ); + d->sizeHint = QSize(); //force rebuild the cache + if (m_subwidget) + m_subwidget->setGeometry( editorGeometry() ); +} + +QSize KexiDBComboBox::sizeHint() const +{ + if ( isVisible() && d->sizeHint.isValid() ) + return d->sizeHint; + + const int maxWidth = 7 * fontMetrics().width(QChar('x')) + 18; + const int maxHeight = QMAX( fontMetrics().lineSpacing(), 14 ) + 2; + d->sizeHint = (style().sizeFromContents(QStyle::CT_ComboBox, d->paintedCombo, + QSize(maxWidth, maxHeight)).expandedTo(QApplication::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<KexiFormDataItemInterface*>((QWidget*)m_subwidget); + if (iface) { + slotInternalEditorValueChanged( iface->value() ); + } + } +} + +void KexiDBComboBox::undoChanges() +{ + KexiDBAutoField::undoChanges(); + KexiComboBoxBase::undoChanges(); +} + +#include "kexidbcombobox.moc" |