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     return false;
00312 }
00313 
00314 void KexiDBComboBox::mousePressEvent( QMouseEvent *e )
00315 {
00316     if (handleMousePressEvent(e))
00317         return;
00318 
00319 //  QTimer::singleShot( 200, this, SLOT(internalClickTimeout()));
00320 //  d->shortClick = TRUE;
00321 //  }
00322     KexiDBAutoField::mousePressEvent( e );
00323 }
00324 
00325 void KexiDBComboBox::mouseDoubleClickEvent( QMouseEvent *e )
00326 {
00327     mousePressEvent( e );
00328 }
00329 
00330 bool KexiDBComboBox::eventFilter( QObject *o, QEvent *e )
00331 {
00332     if (o==this) {
00333         if (e->type()==QEvent::Resize) {
00334             d->paintedCombo->resize(size());
00335             if (m_subwidget)
00336                 m_subwidget->setGeometry( editorGeometry() );
00337         }
00338         else if (e->type()==QEvent::Enter) {
00339             if (!d->isEditable 
00340                 || /*over button if editable combo*/buttonGeometry().contains( static_cast<QMouseEvent*>(e)->pos() )) 
00341             {
00342                 d->mouseOver = true;
00343                 update();
00344             }
00345         }
00346         else if (e->type()==QEvent::MouseMove) {
00347             if (d->isEditable) {
00348                 const bool overButton = buttonGeometry().contains( static_cast<QMouseEvent*>(e)->pos() );
00349                 if (overButton != d->mouseOver) {
00350                     d->mouseOver = overButton;
00351                     update();
00352                 }
00353             }
00354         }
00355         else if (e->type()==QEvent::Leave) {
00356             d->mouseOver = false;
00357             update();
00358         }
00359         else if (e->type()==QEvent::KeyPress) {
00360             // handle F2/F4
00361             if (handleKeyPressEvent(static_cast<QKeyEvent*>(e)))
00362                 return true;
00363         }
00364     }
00365     else if (!d->isEditable && d->subWidgetsWithDisabledEvents && d->subWidgetsWithDisabledEvents->find(o)) {
00366         if (e->type()==QEvent::MouseButtonPress) {
00367             // clicking the subwidget should mean the same as clicking the combo box (i.e. show the popup)
00368             if (handleMousePressEvent(static_cast<QMouseEvent*>(e)))
00369                 return true;
00370         }
00371         else if (e->type()==QEvent::KeyPress) {
00372             if (handleKeyPressEvent(static_cast<QKeyEvent*>(e)))
00373                 return true;
00374         }
00375         return e->type()!=QEvent::Paint;
00376     }
00377     return KexiDBAutoField::eventFilter( o, e );
00378 }
00379 
00380 bool KexiDBComboBox::subwidgetStretchRequired(KexiDBAutoField* autoField) const
00381 {
00382     Q_UNUSED(autoField);
00383     return true;
00384 }
00385 
00386 void KexiDBComboBox::setPaletteBackgroundColor( const QColor & color )
00387 {
00388     KexiDBAutoField::setPaletteBackgroundColor(color);
00389     QPalette pal(palette());
00390     QColorGroup cg(pal.active());
00391     pal.setColor(QColorGroup::Base, red);
00392     pal.setColor(QColorGroup::Background, red);
00393     pal.setActive(cg);
00394     QWidget::setPalette(pal);
00395     update();
00396 }
00397 
00398 bool KexiDBComboBox::valueChanged()
00399 {
00400     kdDebug() << "KexiDataItemInterface::valueChanged(): " << m_origValue.toString() << " ? " << value().toString() << endl;
00401     return m_origValue != value();
00402 }
00403 
00404 void
00405 KexiDBComboBox::setColumnInfo(KexiDB::QueryColumnInfo* cinfo)
00406 {
00407     KexiFormDataItemInterface::setColumnInfo(cinfo);
00408 }
00409 
00410 void KexiDBComboBox::setVisibleColumnInfo(KexiDB::QueryColumnInfo* cinfo)
00411 {
00412     d->visibleColumnInfo = cinfo;
00413     // we're assuming we already have columnInfo()
00414     setColumnInfoInternal(columnInfo(), d->visibleColumnInfo);
00415 }
00416 
00417 KexiDB::QueryColumnInfo* KexiDBComboBox::visibleColumnInfo() const
00418 {
00419     return d->visibleColumnInfo;
00420 }
00421 
00422 void KexiDBComboBox::moveCursorToEndInInternalEditor()
00423 {
00424     if (d->isEditable && m_moveCursorToEndInInternalEditor_enabled)
00425         moveCursorToEnd();
00426 }
00427 
00428 void KexiDBComboBox::selectAllInInternalEditor()
00429 {
00430     if (d->isEditable && m_selectAllInInternalEditor_enabled)
00431         selectAll();
00432 }
00433 
00434 void KexiDBComboBox::setValueInternal(const QVariant& add, bool removeOld)
00435 {
00438     KexiComboBoxBase::setValueInternal(add, removeOld);
00439 }
00440 
00441 void KexiDBComboBox::setVisibleValueInternal(const QVariant& value)
00442 {
00443     KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
00444     if(iface)
00445         iface->setValue(value, QVariant(), false );
00446 }
00447 
00448 QVariant KexiDBComboBox::visibleValue()
00449 {
00450     return KexiComboBoxBase::visibleValue();
00451 }
00452 
00453 void KexiDBComboBox::setValueInInternalEditor(const QVariant& value)
00454 {
00455     if (!m_setValueInInternalEditor_enabled)
00456         return;
00457     KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
00458     if(iface)
00459         iface->setValue(value, QVariant(), false);
00460 }
00461 
00462 QVariant KexiDBComboBox::valueFromInternalEditor()
00463 {
00464     return KexiDBAutoField::value();
00465 }
00466 
00467 QPoint KexiDBComboBox::mapFromParentToGlobal(const QPoint& pos) const
00468 {
00469 //  const KexiFormScrollView* view = KexiUtils::findParentConst<const KexiFormScrollView>(this, "KexiFormScrollView"); 
00470     if (!parentWidget())
00471         return QPoint(-1,-1);
00472     return parentWidget()->mapToGlobal(pos);
00473 //  return view->viewport()->mapToGlobal(pos);
00474 }
00475 
00476 int KexiDBComboBox::popupWidthHint() const
00477 {
00478     return width(); //popup() ? popup()->width() : 0;
00479 }
00480 
00481 void KexiDBComboBox::fontChange( const QFont & oldFont )
00482 {
00483     d->sizeHint = QSize(); //force rebuild the cache
00484     KexiDBAutoField::fontChange(oldFont);
00485 }
00486 
00487 void KexiDBComboBox::styleChange( QStyle& oldStyle )
00488 {
00489     KexiDBAutoField::styleChange( oldStyle );
00490     d->sizeHint = QSize(); //force rebuild the cache
00491     if (m_subwidget)
00492         m_subwidget->setGeometry( editorGeometry() );
00493 }
00494 
00495 QSize KexiDBComboBox::sizeHint() const
00496 {
00497     if ( isVisible() && d->sizeHint.isValid() )
00498         return d->sizeHint;
00499 
00500     const int maxWidth = 7 * fontMetrics().width(QChar('x')) + 18;
00501     const int maxHeight = QMAX( fontMetrics().lineSpacing(), 14 ) + 2;
00502     d->sizeHint = (style().sizeFromContents(QStyle::CT_ComboBox, d->paintedCombo,
00503         QSize(maxWidth, maxHeight)).expandedTo(QApplication::globalStrut()));
00504 
00505     return d->sizeHint;
00506 }
00507 
00508 void KexiDBComboBox::editRequested()
00509 {
00510 }
00511 
00512 void KexiDBComboBox::acceptRequested()
00513 {
00514     signalValueChanged();
00515 }
00516 
00517 void KexiDBComboBox::slotRowAccepted(KexiTableItem *item, int row)
00518 {
00519     d->dataEnteredByHand = false;
00520     KexiComboBoxBase::slotRowAccepted(item, row);
00521     d->dataEnteredByHand = true;
00522 }
00523 
00524 void KexiDBComboBox::beforeSignalValueChanged()
00525 {
00526     if (d->dataEnteredByHand)   {
00527         KexiFormDataItemInterface *iface = dynamic_cast<KexiFormDataItemInterface*>((QWidget*)m_subwidget);
00528         if (iface) {
00529             slotInternalEditorValueChanged( iface->value() );
00530         }
00531     }
00532 }
00533 
00534 void KexiDBComboBox::undoChanges()
00535 {
00536     KexiDBAutoField::undoChanges();
00537     KexiComboBoxBase::undoChanges();
00538 }
00539 
00540 #include "kexidbcombobox.moc"
KDE Home | KDE Accessibility Home | Description of Access Keys