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.
550 lines
15 KiB
550 lines
15 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 <tdeapplication.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(TQWidget::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();
|
|
style().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(style().name(), "windows")) //a hack
|
|
p.fillRect( editorGeometry, cg.brush( TQColorGroup::Highlight ) );
|
|
TQRect r( TQStyle::visualRect( style().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
|
|
style().drawPrimitive( TQStyle::PE_FocusRect, &p,
|
|
r, cg, flags | TQStyle::Style_FocusAtBorder, TQStyleOption(cg.highlight()));
|
|
}
|
|
//todo
|
|
}
|
|
}
|
|
|
|
TQRect KexiDBComboBox::editorGeometry() const
|
|
{
|
|
TQRect r( TQStyle::visualRect(
|
|
style().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
|
|
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" );
|
|
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(
|
|
style().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() != TQt::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() == TQt::NoButton && ((k==TQt::Key_F2 && !d->isEditable) || k==TQt::Key_F4))
|
|
|| (ke->state() == TQt::AltButton && k==TQt::Key_Down);
|
|
const bool escPressed = ke->state() == TQt::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() == TQt::NoButton && k==TQt::Key_Escape;
|
|
if (escPressed && popupVisible) {
|
|
popup()->hide();
|
|
return true;
|
|
}
|
|
if (ke->state() == TQt::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, TQ_SLOT(internalClickTimeout()));
|
|
// d->shortClick = TRUE;
|
|
// }
|
|
KexiDBAutoField::mousePressEvent( e );
|
|
}
|
|
|
|
void KexiDBComboBox::mouseDoubleClickEvent( TQMouseEvent *e )
|
|
{
|
|
mousePressEvent( e );
|
|
}
|
|
|
|
bool KexiDBComboBox::eventFilter( TQObject *o, TQEvent *e )
|
|
{
|
|
if (o==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( static_cast<TQMouseEvent*>(e)->pos() ))
|
|
{
|
|
d->mouseOver = true;
|
|
update();
|
|
}
|
|
}
|
|
else if (e->type()==TQEvent::MouseMove) {
|
|
if (d->isEditable) {
|
|
const bool overButton = buttonGeometry().contains( static_cast<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(static_cast<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(static_cast<TQMouseEvent*>(e)))
|
|
return true;
|
|
}
|
|
else if (e->type()==TQEvent::KeyPress) {
|
|
if (handleKeyPressEvent(static_cast<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 = (style().sizeFromContents(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"
|