kexi

kexidbcombobox.cpp

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