kexi

kexidbcombobox.cpp

00001 /* This file is part of the KDE project
00002    Copyright (C) 2006 Jaroslaw Staniek <js@iidea.pl>
00003 
00004    This program is free software; you can redistribute it and/or
00005    modify it under the terms of the GNU Library General Public
00006    License as published by the Free Software Foundation; either
00007    version 2 of the License, or (at your option) any later version.
00008 
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY; without even the implied warranty of
00011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012    Library General Public License for more details.
00013 
00014    You should have received a copy of the GNU Library General Public License
00015    along with this program; see the file COPYING.  If not, write to
00016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017  * Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "kexidbcombobox.h"
00021 #include "kexidblineedit.h"
00022 #include "../kexiformscrollview.h"
00023 
00024 #include <kcombobox.h>
00025 #include <kdebug.h>
00026 #include <kapplication.h>
00027 
00028 #include <qmetaobject.h>
00029 #include <qpainter.h>
00030 #include <qstyle.h>
00031 #include <qdrawutil.h>
00032 #include <qptrdict.h>
00033 #include <qcursor.h>
00034 
00035 #include <kexidb/queryschema.h>
00036 #include <widget/tableview/kexicomboboxpopup.h>
00037 #include <widget/tableview/kexicelleditorfactory.h>
00038 #include <kexiutils/utils.h>
00039 
00041 class KexiDBComboBox::Private
00042 {
00043     public:
00044         Private()
00045          : popup(0)
00046          , visibleColumnInfo(0)
00047          , subWidgetsWithDisabledEvents(0)
00048          , isEditable(false)
00049          , buttonPressed(false)
00050          , mouseOver(false)
00051          , dataEnteredByHand(true)
00052         {
00053         }
00054         ~Private()
00055         {
00056             delete subWidgetsWithDisabledEvents;
00057             subWidgetsWithDisabledEvents = 0;
00058         }
00059 
00060     KexiComboBoxPopup *popup;
00061     KComboBox *paintedCombo; 
00062     QSize sizeHint; 
00063 
00064     KexiDB::QueryColumnInfo* visibleColumnInfo;
00065     QPtrDict<char> *subWidgetsWithDisabledEvents; 
00066     bool isEditable : 1; 
00067     bool buttonPressed : 1;
00068     bool mouseOver : 1;
00069     bool dataEnteredByHand : 1;
00070 };
00071 
00072 //-------------------------------------
00073 
00074 KexiDBComboBox::KexiDBComboBox(QWidget *parent, const char *name, bool designMode)
00075  : KexiDBAutoField(parent, name, designMode, NoLabel)
00076  , KexiComboBoxBase()
00077  , d(new Private())
00078 {
00079     setMouseTracking(true);
00080     setFocusPolicy(WheelFocus);
00081     installEventFilter(this);
00082     d->paintedCombo = new KComboBox(this);
00083     d->paintedCombo->hide();
00084     d->paintedCombo->move(0,0);
00085 }
00086 
00087 KexiDBComboBox::~KexiDBComboBox()
00088 {
00089     delete d;
00090 }
00091 
00092 KexiComboBoxPopup *KexiDBComboBox::popup() const
00093 {
00094     return d->popup;
00095 }
00096 
00097 void KexiDBComboBox::setPopup(KexiComboBoxPopup *popup)
00098 {
00099     d->popup = popup;
00100 }
00101 
00102 void KexiDBComboBox::setEditable(bool set)
00103 {
00104     if (d->isEditable == set)
00105         return;
00106     d->isEditable = set;
00107     d->paintedCombo->setEditable(set);
00108     if (set)
00109         createEditor();
00110     else {
00111         delete m_subwidget;
00112         m_subwidget = 0;
00113     }
00114     update();
00115 }
00116 
00117 bool KexiDBComboBox::isEditable() const
00118 {
00119     return d->isEditable;
00120 }
00121 
00122 void KexiDBComboBox::paintEvent( QPaintEvent * )
00123 {
00124     QPainter p( this );
00125     QColorGroup cg( palette().active() );
00126 //  if ( hasFocus() )
00127 //      cg.setColor(QColorGroup::Base, cg.highlight());
00128 //  else
00129         cg.setColor(QColorGroup::Base, paletteBackgroundColor()); //update base color using (reimplemented) bg color
00130     p.setPen(cg.text());
00131 
00132     QStyle::SFlags flags = QStyle::Style_Default;
00133     if (isEnabled())
00134         flags |= QStyle::Style_Enabled;
00135     if (hasFocus())
00136         flags |= QStyle::Style_HasFocus;
00137     if (d->mouseOver)
00138         flags |= QStyle::Style_MouseOver;
00139 
00140     if ( width() < 5 || height() < 5 ) {
00141         qDrawShadePanel( &p, rect(), cg, FALSE, 2, &cg.brush( QColorGroup::Button ) );
00142         return;
00143     }
00144 
00146 //bool reverse = QApplication::reverseLayout();
00147     style().drawComplexControl( QStyle::CC_ComboBox, &p, d->paintedCombo /*this*/, rect(), cg,
00148         flags, (uint)QStyle::SC_All, 
00149         (d->buttonPressed ? QStyle::SC_ComboBoxArrow : QStyle::SC_None )
00150     );
00151 
00152     if (d->isEditable) {
00153         //if editable, editor paints itself, nothing to do
00154     }
00155     else { //not editable: we need to paint the current item
00156         QRect editorGeometry( this->editorGeometry() );
00157         if ( hasFocus() ) {
00158             if (0==qstrcmp(style().name(), "windows")) //a hack
00159                 p.fillRect( editorGeometry, cg.brush( QColorGroup::Highlight ) );
00160             QRect r( QStyle::visualRect( style().subRect( QStyle::SR_ComboBoxFocusRect, d->paintedCombo ), this ) );
00161             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
00162             style().drawPrimitive( QStyle::PE_FocusRect, &p, 
00163                 r, cg, flags | QStyle::Style_FocusAtBorder, QStyleOption(cg.highlight()));
00164         }
00165         //todo
00166     }
00167 }
00168 
00169 QRect KexiDBComboBox::editorGeometry() const
00170 {
00171     QRect r( QStyle::visualRect(
00172         style().querySubControlMetrics(QStyle::CC_ComboBox, d->paintedCombo,
00173         QStyle::SC_ComboBoxEditField), d->paintedCombo ) );
00174     
00175     //if ((height()-r.bottom())<6)
00176     //  r.setBottom(height()-6);
00177     return r;
00178 }
00179 
00180 void KexiDBComboBox::createEditor()
00181 {
00182     KexiDBAutoField::createEditor();
00183     if (m_subwidget) {
00184         m_subwidget->setGeometry( editorGeometry() );
00185         if (!d->isEditable) {
00186             m_subwidget->setCursor(QCursor(Qt::ArrowCursor)); // widgets like listedit have IbeamCursor, we don't want that
00188             QPalette subwidgetPalette( m_subwidget->palette() );
00189             subwidgetPalette.setColor(QPalette::Active, QColorGroup::Base, 
00190                 subwidgetPalette.color(QPalette::Active, QColorGroup::Button));
00191             m_subwidget->setPalette( subwidgetPalette );
00192             if (d->subWidgetsWithDisabledEvents)
00193                 d->subWidgetsWithDisabledEvents->clear();
00194             else
00195                 d->subWidgetsWithDisabledEvents = new QPtrDict<char>();
00196             d->subWidgetsWithDisabledEvents->insert(m_subwidget, (char*)1);
00197             m_subwidget->installEventFilter(this);
00198             QObjectList *l = m_subwidget->queryList( "QWidget" );
00199             for ( QObjectListIt it( *l ); it.current(); ++it ) {
00200                 d->subWidgetsWithDisabledEvents->insert(it.current(), (char*)1);
00201                 it.current()->installEventFilter(this);
00202             }
00203             delete l;
00204         }
00205     }
00206     updateGeometry();
00207 }
00208 
00209 void KexiDBComboBox::setLabelPosition(LabelPosition position)
00210 {
00211     if(m_subwidget) {
00212         if (-1 != m_subwidget->metaObject()->findProperty("frameShape", true))
00213             m_subwidget->setProperty("frameShape", QVariant((int)QFrame::NoFrame));
00214         m_subwidget->setGeometry( editorGeometry() );
00215     }
00216 //      KexiSubwidgetInterface *subwidgetInterface = dynamic_cast<KexiSubwidgetInterface*>((QWidget*)m_subwidget);
00217         // update size policy
00218 //      if (subwidgetInterface && subwidgetInterface->subwidgetStretchRequired(this)) {
00219             QSizePolicy sizePolicy( this->sizePolicy() );
00220             if(position == Left)
00221                 sizePolicy.setHorData( QSizePolicy::Minimum );
00222             else
00223                 sizePolicy.setVerData( QSizePolicy::Minimum );
00224             //m_subwidget->setSizePolicy(sizePolicy);
00225             setSizePolicy(sizePolicy);
00226         //}
00227 //  }
00228 }
00229 
00230 QRect KexiDBComboBox::buttonGeometry() const
00231 {
00232     QRect arrowRect( 
00233         style().querySubControlMetrics( QStyle::CC_ComboBox, d->paintedCombo, QStyle::SC_ComboBoxArrow) );
00234     arrowRect = QStyle::visualRect(arrowRect, d->paintedCombo);
00235     arrowRect.setHeight( QMAX(  height() - (2 * arrowRect.y()), arrowRect.height() ) ); // a fix for Motif style
00236     return arrowRect;
00237 }
00238 
00239 bool KexiDBComboBox::handleMousePressEvent(QMouseEvent *e)
00240 {
00241     if ( e->button() != Qt::LeftButton )
00242         return true;
00243 /*todo  if ( m_discardNextMousePress ) {
00244         d->discardNextMousePress = FALSE;
00245         return;
00246     }*/
00247 
00248     if ( /*count() &&*/ ( !isEditable() || buttonGeometry().contains( e->pos() ) ) ) {
00249         d->buttonPressed = false;
00250 
00251 /*  if ( d->usingListBox() ) {
00252         listBox()->blockSignals( TRUE );
00253         qApp->sendEvent( listBox(), e ); // trigger the listbox's autoscroll
00254         listBox()->setCurrentItem(d->current);
00255         listBox()->blockSignals( FALSE );
00256         popup();
00257         if ( arrowRect.contains( e->pos() ) ) {
00258         d->arrowPressed = TRUE;
00259         d->arrowDown    = TRUE;
00260         repaint( FALSE );
00261         }
00262     } else {*/
00263         showPopup();
00264         return true;
00265     }
00266     return false;
00267 }
00268 
00269 bool KexiDBComboBox::handleKeyPressEvent(QKeyEvent *ke)
00270 {
00271     const int k = ke->key();
00272     const bool dropDown = (ke->state() == Qt::NoButton && ((k==Qt::Key_F2 && !d->isEditable) || k==Qt::Key_F4))
00273         || (ke->state() == Qt::AltButton && k==Qt::Key_Down);
00274     const bool escPressed = ke->state() == Qt::NoButton && k==Qt::Key_Escape;
00275     const bool popupVisible =  popup() && popup()->isVisible();
00276     if ((dropDown || escPressed) && popupVisible) {
00277         popup()->hide();
00278         return true;
00279     }
00280     else if (dropDown && !popupVisible) {
00281         d->buttonPressed = false;
00282         showPopup();
00283         return true;
00284     }
00285     else if (popupVisible) {
00286         const bool enterPressed = k==Qt::Key_Enter || k==Qt::Key_Return;
00287         if (enterPressed/* && m_internalEditorValueChanged*/) {
00288             acceptPopupSelection();
00289             return true;
00290         }
00291         return handleKeyPressForPopup( ke );
00292     }
00293 
00294     return false;
00295 }
00296 
00297 bool KexiDBComboBox::keyPressed(QKeyEvent *ke)
00298 {
00299     if (KexiDBAutoField::keyPressed(ke))
00300         return true;
00301 
00302     const int k = ke->key();
00303     const bool popupVisible =  popup() && popup()->isVisible();
00304     const bool escPressed = ke->state() == Qt::NoButton && k==Qt::Key_Escape;
00305     if (escPressed && popupVisible) {
00306         popup()->hide();
00307         return true;
00308     }
00309     return false;
00310 }
00311 
00312 void KexiDBComboBox::mousePressEvent( QMouseEvent *e )
00313 {
00314     if (handleMousePressEvent(e))
00315         return;
00316 
00317 //  QTimer::singleShot( 200, this, SLOT(internalClickTimeout()));
00318 //  d->shortClick = TRUE;
00319 //  }
00320     KexiDBAutoField::mousePressEvent( e );
00321 }
00322 
00323 void KexiDBComboBox::mouseDoubleClickEvent( QMouseEvent *e )
00324 {
00325     mousePressEvent( e );
00326 }
00327 
00328 bool KexiDBComboBox::eventFilter( QObject *o, QEvent *e )
00329 {
00330     if (o==this) {
00331         if (e->type()==QEvent::Resize) {
00332             d->paintedCombo->resize(size());
00333             if (m_subwidget)
00334                 m_subwidget->setGeometry( editorGeometry() );
00335         }
00336         else if (e->type()==QEvent::Enter) {
00337             if (!d->isEditable 
00338                 || /*over button if editable combo*/buttonGeometry().contains( static_cast<QMouseEvent*>(e)->pos() )) 
00339             {
00340                 d->mouseOver = true;
00341                 update();
00342             }
00343         }
00344         else if (e->type()==QEvent::MouseMove) {
00345             if (d->isEditable) {
00346                 const bool overButton = buttonGeometry().contains( static_cast<QMouseEvent*>(e)->pos() );
00347                 if (overButton != d->mouseOver) {
00348                     d->mouseOver = overButton;
00349                     update();
00350                 }
00351             }
00352         }
00353         else if (e->type()==QEvent::Leave) {
00354             d->mouseOver = false;
00355             update();
00356         }
00357         else if (e->type()==QEvent::KeyPress) {
00358             // handle F2/F4
00359             if (handleKeyPressEvent(static_cast<QKeyEvent*>(e)))
00360                 return true;
00361         }
00362     }
00363     else if (!d->isEditable && d->subWidgetsWithDisabledEvents && d->subWidgetsWithDisabledEvents->find(o)) {
00364         if (e->type()==QEvent::MouseButtonPress) {
00365             // clicking the subwidget should mean the same as clicking the combo box (i.e. show the popup)
00366             if (handleMousePressEvent(static_cast<QMouseEvent*>(e)))
00367                 return true;
00368         }
00369         else if (e->type()==QEvent::KeyPress) {
00370             if (handleKeyPressEvent(static_cast<QKeyEvent*>(e)))
00371                 return true;
00372         }
00373         return e->type()!=QEvent::Paint;
00374     }
00375     return KexiDBAutoField::eventFilter( o, e );
00376 }
00377 
00378 bool KexiDBComboBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const
00379 {
00380     Q_UNUSED(autoField);
00381     return true;
00382 }
00383 
00384 void KexiDBComboBox::setPaletteBackgroundColor( const QColor & color )
00385 {
00386     KexiDBAutoField::setPaletteBackgroundColor(color);
00387     QPalette pal(palette());
00388     QColorGroup cg(pal.active());
00389     pal.setColor(QColorGroup::Base, red);
00390     pal.setColor(QColorGroup::Background, red);
00391     pal.setActive(cg);
00392     QWidget::setPalette(pal);
00393     update();
00394 }
00395 
00396 bool KexiDBComboBox::valueChanged()
00397 {
00398     kdDebug() << "KexiDataItemInterface::valueChanged(): " << m_origValue.toString() << " ? " << value().toString() << endl;
00399     return m_origValue != value();
00400 }
00401 
00402 void
00403 KexiDBComboBox::setColumnInfo(KexiDB::QueryColumnInfo* cinfo)
00404 {
00405     KexiFormDataItemInterface::setColumnInfo(cinfo);
00406 }
00407 
00408 void KexiDBComboBox::setVisibleColumnInfo(KexiDB::QueryColumnInfo* cinfo)
00409 {
00410     d->visibleColumnInfo = cinfo;
00411     // we're assuming we already have columnInfo()
00412     setColumnInfoInternal(columnInfo(), d->visibleColumnInfo);
00413 }
00414 
00415 KexiDB::QueryColumnInfo* KexiDBComboBox::visibleColumnInfo() const
00416 {
00417     return d->visibleColumnInfo;
00418 }
00419 
00420 void KexiDBComboBox::moveCursorToEndInInternalEditor()
00421 {
00422     if (d->isEditable && m_moveCursorToEndInInternalEditor_enabled)
00423         moveCursorToEnd();
00424 }
00425 
00426 void KexiDBComboBox::selectAllInInternalEditor()
00427 {
00428     if (d->isEditable && m_selectAllInInternalEditor_enabled)
00429         selectAll();
00430 }
00431 
00432 void KexiDBComboBox::setValueInternal(const QVariant& add, bool removeOld)
00433 {
00436     KexiComboBoxBase::setValueInternal(add, removeOld);
00437 }
00438 
00439 void KexiDBComboBox::setVisibleValueInternal(const QVariant& value)
00440 {
00441     KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
00442     if(iface)
00443         iface->setValue(value, QVariant(), false );
00444 }
00445 
00446 QVariant KexiDBComboBox::visibleValue()
00447 {
00448     return KexiComboBoxBase::visibleValue();
00449 }
00450 
00451 void KexiDBComboBox::setValueInInternalEditor(const QVariant& value)
00452 {
00453     if (!m_setValueInInternalEditor_enabled)
00454         return;
00455     KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
00456     if(iface)
00457         iface->setValue(value, QVariant(), false);
00458 }
00459 
00460 QVariant KexiDBComboBox::valueFromInternalEditor()
00461 {
00462     return KexiDBAutoField::value();
00463 }
00464 
00465 QPoint KexiDBComboBox::mapFromParentToGlobal(const QPoint& pos) const
00466 {
00467     const KexiFormScrollView* view = KexiUtils::findParentConst<const KexiFormScrollView>(this, "KexiFormScrollView"); 
00468     if (!view)
00469         return QPoint(-1,-1);
00470     return view->viewport()->mapToGlobal(pos);
00471 }
00472 
00473 int KexiDBComboBox::popupWidthHint() const
00474 {
00475     return width(); //popup() ? popup()->width() : 0;
00476 }
00477 
00478 void KexiDBComboBox::fontChange( const QFont & oldFont )
00479 {
00480     d->sizeHint = QSize(); //force rebuild the cache
00481     KexiDBAutoField::fontChange(oldFont);
00482 }
00483 
00484 void KexiDBComboBox::styleChange( QStyle& oldStyle )
00485 {
00486     KexiDBAutoField::styleChange( oldStyle );
00487     d->sizeHint = QSize(); //force rebuild the cache
00488     if (m_subwidget)
00489         m_subwidget->setGeometry( editorGeometry() );
00490 }
00491 
00492 QSize KexiDBComboBox::sizeHint() const
00493 {
00494     if ( isVisible() && d->sizeHint.isValid() )
00495         return d->sizeHint;
00496 
00497     const int maxWidth = 7 * fontMetrics().width(QChar('x')) + 18;
00498     const int maxHeight = QMAX( fontMetrics().lineSpacing(), 14 ) + 2;
00499     d->sizeHint = (style().sizeFromContents(QStyle::CT_ComboBox, d->paintedCombo,
00500         QSize(maxWidth, maxHeight)).expandedTo(QApplication::globalStrut()));
00501 
00502     return d->sizeHint;
00503 }
00504 
00505 void KexiDBComboBox::editRequested()
00506 {
00507 }
00508 
00509 void KexiDBComboBox::acceptRequested()
00510 {
00511     signalValueChanged();
00512 }
00513 
00514 void KexiDBComboBox::slotRowAccepted(KexiTableItem *item, int row)
00515 {
00516     d->dataEnteredByHand = false;
00517     KexiComboBoxBase::slotRowAccepted(item, row);
00518     d->dataEnteredByHand = true;
00519 }
00520 
00521 void KexiDBComboBox::beforeSignalValueChanged()
00522 {
00523     if (d->dataEnteredByHand)   {
00524         KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
00525         if (iface) {
00526             slotInternalEditorValueChanged( iface->value() );
00527         }
00528     }
00529 }
00530 
00531 void KexiDBComboBox::undoChanges()
00532 {
00533     KexiDBAutoField::undoChanges();
00534     KexiComboBoxBase::undoChanges();
00535 }
00536 
00537 #include "kexidbcombobox.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys