You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
koffice/kexi/plugins/forms/widgets/kexidbcombobox.cpp

551 lines
16 KiB

/* 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 <tqmetaobject.h>
#include <tqpainter.h>
#include <tqstyle.h>
#include <tqdrawutil.h>
#include <tqptrdict.h>
#include <tqcursor.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 TQStyle (because styles use <static_cast>)
TQSize sizeHint; //!< A cache for KexiDBComboBox::sizeHint(),
//!< rebuilt by KexiDBComboBox::fontChange() and KexiDBComboBox::styleChange()
KexiDB::QueryColumnInfo* visibleColumnInfo;
TQPtrDict<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(TQWidget *parent, const char *name, bool designMode)
: KexiDBAutoField(parent, name, designMode, NoLabel)
, KexiComboBoxBase()
, d(new Private())
{
setMouseTracking(true);
setFocusPolicy(TQ_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();
tqstyle().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(tqstyle().name(), "windows")) //a hack
p.fillRect( editorGeometry, cg.brush( TQColorGroup::Highlight ) );
TQRect r( TQStyle::visualRect( tqstyle().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
tqstyle().tqdrawPrimitive( TQStyle::PE_FocusRect, &p,
r, cg, flags | TQStyle::Style_FocusAtBorder, TQStyleOption(cg.highlight()));
}
//todo
}
}
TQRect KexiDBComboBox::editorGeometry() const
{
TQRect r( TQStyle::visualRect(
tqstyle().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
//! @todo TQt4: set transparent background, for now we're setting button color
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<char>();
d->subWidgetsWithDisabledEvents->insert(m_subwidget, (char*)1);
m_subwidget->installEventFilter(this);
TQObjectList *l = m_subwidget->queryList( TQWIDGET_OBJECT_NAME_STRING );
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<KexiSubwidgetInterface*>((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(
tqstyle().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() != 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 );
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() == Qt::NoButton && ((k==TQt::Key_F2 && !d->isEditable) || k==TQt::Key_F4))
|| (ke->state() == TQt::AltButton && k==TQt::Key_Down);
const bool escPressed = ke->state() == Qt::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() == Qt::NoButton && k==TQt::Key_Escape;
if (escPressed && popupVisible) {
popup()->hide();
return true;
}
if (ke->state() == Qt::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, TQT_SLOT(internalClickTimeout()));
// d->shortClick = TRUE;
// }
KexiDBAutoField::mousePressEvent( e );
}
void KexiDBComboBox::mouseDoubleClickEvent( TQMouseEvent *e )
{
mousePressEvent( e );
}
bool KexiDBComboBox::eventFilter( TQObject *o, TQEvent *e )
{
if (TQT_BASE_OBJECT(o)==TQT_BASE_OBJECT(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( TQT_TQMOUSEEVENT(e)->pos() ))
{
d->mouseOver = true;
update();
}
}
else if (e->type()==TQEvent::MouseMove) {
if (d->isEditable) {
const bool overButton = buttonGeometry().contains( TQT_TQMOUSEEVENT(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(TQT_TQKEYEVENT(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(TQT_TQMOUSEEVENT(e)))
return true;
}
else if (e->type()==TQEvent::KeyPress) {
if (handleKeyPressEvent(TQT_TQKEYEVENT(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<KexiFormDataItemInterface*>((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<KexiFormDataItemInterface*>((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<const KexiFormScrollView>(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 = (tqstyle().tqsizeFromContents(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<KexiFormDataItemInterface*>((TQWidget*)m_subwidget);
if (iface) {
slotInternalEditorValueChanged( iface->value() );
}
}
}
void KexiDBComboBox::undoChanges()
{
KexiDBAutoField::undoChanges();
KexiComboBoxBase::undoChanges();
}
#include "kexidbcombobox.moc"