/* This file is part of the KDE project Copyright (C) 2004 Lucijan Busch Copyright (C) 2004 Cedric Pasteur Copyright (C) 2005-2007 Jaroslaw Staniek This library 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 library 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 library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "kexidbform.h" #include "kexiformpart.h" #include "kexiformscrollview.h" #include #include #include #include #include #include //! @internal class KexiDBForm::Private { public: Private() : dataAwareObject(0) , orderedFocusWidgetsIterator(orderedFocusWidgets) , autoTabStops(false) , popupFocused(false) { } ~Private() { } //! \return index of data-aware widget \a widget int indexOfDataAwareWidget(TQWidget *widget) const { if (!dynamic_cast(widget)) return -1; return indexOfDataItem( dynamic_cast(widget) ); } //! \return index of data item \a item, or -1 if not found int indexOfDataItem( KexiDataItemInterface* item ) const { TQMapConstIterator indicesForDataAwareWidgetsIt( indicesForDataAwareWidgets.find(item)); if (indicesForDataAwareWidgetsIt == indicesForDataAwareWidgets.constEnd()) return -1; kexipluginsdbg << "KexiDBForm: column # for item: " << indicesForDataAwareWidgetsIt.data() << endl; return indicesForDataAwareWidgetsIt.data(); } //! Sets orderedFocusWidgetsIterator member to a position pointing to \a widget void setOrderedFocusWidgetsIteratorTo( TQWidget *widget ) { if (orderedFocusWidgetsIterator.current() == widget) return; orderedFocusWidgetsIterator.toFirst(); while (orderedFocusWidgetsIterator.current() && orderedFocusWidgetsIterator.current()!=widget) ++orderedFocusWidgetsIterator; } KexiDataAwareObjectInterface* dataAwareObject; //! ordered list of focusable widgets (can be both data-widgets or buttons, etc.) TQPtrList orderedFocusWidgets; //! ordered list of data-aware widgets TQPtrList orderedDataAwareWidgets; TQMap indicesForDataAwareWidgets; //!< a subset of orderedFocusWidgets mapped to indices TQPtrListIterator orderedFocusWidgetsIterator; TQPixmap buffer; //!< stores grabbed entire form's area for redraw TQRect prev_rect; //!< previously selected rectangle // TQGuardedPtr widgetFocusedBeforePopup; bool autoTabStops : 1; bool popupFocused : 1; //!< used in KexiDBForm::eventFilter() }; //======================== KexiDBForm::KexiDBForm(TQWidget *tqparent, KexiDataAwareObjectInterface* dataAwareObject, const char *name/*, KexiDB::Connection *conn*/) : KexiDBFormBase(tqparent, name) , KexiFormDataItemInterface() , d(new Private()) { installEventFilter(this); //test setDisplayMode( KexiGradientWidget::SimpleGradient ); editedItem = 0; d->dataAwareObject = dataAwareObject; m_hasFocusableWidget = false; kexipluginsdbg << "KexiDBForm::KexiDBForm(): " << endl; setCursor(TQCursor(TQt::ArrowCursor)); //to avoid keeping Size cursor when moving from form's boundaries setAcceptDrops( true ); } KexiDBForm::~KexiDBForm() { kexipluginsdbg << "KexiDBForm::~KexiDBForm(): close" << endl; delete d; } KexiDataAwareObjectInterface* KexiDBForm::dataAwareObject() const { return d->dataAwareObject; } //tqrepaint all tqchildren widgets static void repaintAll(TQWidget *w) { TQObjectList *list = w->queryList(TQWIDGET_OBJECT_NAME_STRING); TQObjectListIt it(*list); for (TQObject *obj; (obj=it.current()); ++it ) { TQT_TQWIDGET(obj)->tqrepaint(); } delete list; } void KexiDBForm::drawRect(const TQRect& r, int type) { TQValueList l; l.append(r); drawRects(l, type); } void KexiDBForm::drawRects(const TQValueList &list, int type) { TQPainter p; p.tqbegin(TQT_TQPAINTDEVICE(this), true); bool unclipped = testWFlags( WPaintUnclipped ); setWFlags( WPaintUnclipped ); if (d->prev_rect.isValid()) { //redraw prev. selection's rectangle p.drawPixmap( TQPoint(d->prev_rect.x()-2, d->prev_rect.y()-2), d->buffer, TQRect(d->prev_rect.x()-2, d->prev_rect.y()-2, d->prev_rect.width()+4, d->prev_rect.height()+4)); } p.setBrush(TQBrush::NoBrush); if(type == 1) // selection rect p.setPen(TQPen(white, 1, TQt::DotLine)); else if(type == 2) // insert rect p.setPen(TQPen(white, 2)); p.setRasterOp(XorROP); d->prev_rect = TQRect(); TQValueList::ConstIterator endIt = list.constEnd(); for(TQValueList::ConstIterator it = list.constBegin(); it != endIt; ++it) { p.drawRect(*it); if (d->prev_rect.isValid()) d->prev_rect = d->prev_rect.unite(*it); else d->prev_rect = *it; } if (!unclipped) clearWFlags( WPaintUnclipped ); p.end(); } void KexiDBForm::initBuffer() { repaintAll(this); d->buffer.resize( width(), height() ); d->buffer = TQPixmap::grabWindow( winId() ); d->prev_rect = TQRect(); } void KexiDBForm::clearForm() { TQPainter p; p.tqbegin(TQT_TQPAINTDEVICE(this), true); bool unclipped = testWFlags( WPaintUnclipped ); setWFlags( WPaintUnclipped ); //redraw entire form surface p.drawPixmap( TQPoint(0,0), d->buffer, TQRect(0,0,d->buffer.width(), d->buffer.height()) ); if (!unclipped) clearWFlags( WPaintUnclipped ); p.end(); repaintAll(this); } void KexiDBForm::highlightWidgets(TQWidget *from, TQWidget *to)//, const TQPoint &point) { TQPoint fromPoint, toPoint; if(from && from->parentWidget() && (from != this)) fromPoint = from->parentWidget()->mapTo(this, from->pos()); if(to && to->parentWidget() && (to != this)) toPoint = to->parentWidget()->mapTo(this, to->pos()); TQPainter p; p.tqbegin(TQT_TQPAINTDEVICE(this), true); bool unclipped = testWFlags( WPaintUnclipped ); setWFlags( WPaintUnclipped ); if (d->prev_rect.isValid()) { //redraw prev. selection's rectangle p.drawPixmap( TQPoint(d->prev_rect.x(), d->prev_rect.y()), d->buffer, TQRect(d->prev_rect.x(), d->prev_rect.y(), d->prev_rect.width(), d->prev_rect.height())); } p.setPen( TQPen(TQt::red, 2) ); if(to) { TQPixmap pix1 = TQPixmap::grabWidget(from); TQPixmap pix2 = TQPixmap::grabWidget(to); if((from != this) && (to != this)) p.drawLine( from->parentWidget()->mapTo(this, from->tqgeometry().center()), to->parentWidget()->mapTo(this, to->tqgeometry().center()) ); p.drawPixmap(fromPoint.x(), fromPoint.y(), pix1); p.drawPixmap(toPoint.x(), toPoint.y(), pix2); if(to == this) p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4); else p.drawRoundRect(toPoint.x(), toPoint.y(), to->width(), to->height(), 5, 5); } if(from == this) p.drawRoundRect(2, 2, width()-4, height()-4, 4, 4); else p.drawRoundRect(fromPoint.x(), fromPoint.y(), from->width(), from->height(), 5, 5); if((to == this) || (from == this)) d->prev_rect = TQRect(0, 0, d->buffer.width(), d->buffer.height()); else if(to) { d->prev_rect.setX( (fromPoint.x() < toPoint.x()) ? (fromPoint.x() - 5) : (toPoint.x() - 5) ); d->prev_rect.setY( (fromPoint.y() < toPoint.y()) ? (fromPoint.y() - 5) : (toPoint.y() - 5) ); d->prev_rect.setRight( (fromPoint.x() < toPoint.x()) ? (toPoint.x() + to->width() + 10) : (fromPoint.x() + from->width() + 10) ); d->prev_rect.setBottom( (fromPoint.y() < toPoint.y()) ? (toPoint.y() + to->height() + 10) : (fromPoint.y() + from->height() + 10) ) ; } else d->prev_rect = TQRect(fromPoint.x()- 5, fromPoint.y() -5, from->width() + 10, from->height() + 10); if (!unclipped) clearWFlags( WPaintUnclipped ); p.end(); } TQSize KexiDBForm::tqsizeHint() const { //todo: find better size (user configured?) return TQSize(400,300); } void KexiDBForm::setInvalidState( const TQString& displayText ) { Q_UNUSED( displayText ); //! @todo draw "invalid data source" text on the surface? } bool KexiDBForm::autoTabStops() const { return d->autoTabStops; } void KexiDBForm::setAutoTabStops(bool set) { d->autoTabStops = set; } TQPtrList* KexiDBForm::orderedFocusWidgets() const { return &d->orderedFocusWidgets; } TQPtrList* KexiDBForm::orderedDataAwareWidgets() const { return &d->orderedDataAwareWidgets; } void KexiDBForm::updateTabStopsOrder(KFormDesigner::Form* form) { TQWidget *fromWidget = 0; //TQWidget *tqtopLevelWidget = form->widget()->tqtopLevelWidget(); //js form->updateTabStopsOrder(); //certain widgets can have now updated focusPolicy properties, fix this uint numberOfDataAwareWidgets = 0; // if (d->orderedFocusWidgets.isEmpty()) { //generate a new list for (KFormDesigner::ObjectTreeListIterator it(form->tabStopsIterator()); it.current(); ++it) { if (it.current()->widget()->focusPolicy() & TQ_TabFocus) { //this widget has tab focus: it.current()->widget()->installEventFilter(this); //also filter events for data-aware tqchildren of this widget (i.e. KexiDBAutoField's editors) TQObjectList *tqchildren = it.current()->widget()->queryList(TQWIDGET_OBJECT_NAME_STRING); for (TQObjectListIt tqchildrenIt(*tqchildren); tqchildrenIt.current(); ++tqchildrenIt) { // if (dynamic_cast(tqchildrenIt.current())) { kexipluginsdbg << "KexiDBForm::updateTabStopsOrder(): also adding '" << tqchildrenIt.current()->className() << " " << tqchildrenIt.current()->name() << "' child to filtered widgets" << endl; //it.current()->widget()->installEventFilter(TQT_TQWIDGET(tqchildrenIt.current())); tqchildrenIt.current()->installEventFilter(this); // } } delete tqchildren; if (fromWidget) { kexipluginsdbg << "KexiDBForm::updateTabStopsOrder() tab order: " << fromWidget->name() << " -> " << it.current()->widget()->name() << endl; // setTabOrder( fromWidget, it.current()->widget() ); } fromWidget = it.current()->widget(); d->orderedFocusWidgets.append( it.current()->widget() ); } KexiFormDataItemInterface* dataItem = dynamic_cast( it.current()->widget() ); if (dataItem && !dataItem->dataSource().isEmpty()) { kexipluginsdbg << "#" << numberOfDataAwareWidgets << ": " << dataItem->dataSource() << " (" << it.current()->widget()->name() << ")" << endl; // /*! @todo d->indicesForDataAwareWidgets SHOULDNT BE UPDATED HERE BECAUSE // THERE CAN BE ALSO NON-TABSTOP DATA WIDGETS! // */ d->indicesForDataAwareWidgets.replace( dataItem, numberOfDataAwareWidgets ); numberOfDataAwareWidgets++; d->orderedDataAwareWidgets.append( it.current()->widget() ); } }//for // } /* else { //restore ordering for (TQPtrListIterator it(d->orderedFocusWidgets); it.current(); ++it) { if (fromWidget) { kdDebug() << "KexiDBForm::updateTabStopsOrder() tab order: " << fromWidget->name() << " -> " << it.current()->name() << endl; setTabOrder( fromWidget, it.current() ); } fromWidget = it.current(); } // SET_FOCUS_USING_REASON(tqfocusWidget(), TQFocusEvent::Tab); }*/ } void KexiDBForm::updateTabStopsOrder() { for (TQPtrListIterator it( d->orderedFocusWidgets ); it.current();) { if (! (it.current()->focusPolicy() & TQ_TabFocus)) d->orderedFocusWidgets.remove( it.current() ); else ++it; } } void KexiDBForm::updateReadOnlyFlags() { for (TQPtrListIterator it(d->orderedDataAwareWidgets); it.current(); ++it) { KexiFormDataItemInterface* dataItem = dynamic_cast( it.current() ); if (dataItem && !dataItem->dataSource().isEmpty()) { if (dataAwareObject()->isReadOnly()) { dataItem->setReadOnly( true ); } } } } bool KexiDBForm::eventFilter( TQObject * watched, TQEvent * e ) { //kexipluginsdbg << e->type() << endl; if (e->type()==TQEvent::Resize && TQT_BASE_OBJECT(watched) == TQT_BASE_OBJECT(this)) kexipluginsdbg << "RESIZE" << endl; if (e->type()==TQEvent::KeyPress) { if (preview()) { TQKeyEvent *ke = TQT_TQKEYEVENT(e); const int key = ke->key(); bool tab = ke->state() == Qt::NoButton && key == TQt::Key_Tab; bool backtab = ((ke->state() == Qt::NoButton || ke->state() == TQt::ShiftButton) && key == TQt::Key_Backtab) || (ke->state() == TQt::ShiftButton && key == TQt::Key_Tab); TQObject *o = watched; //tqfocusWidget(); TQWidget* realWidget = dynamic_cast(o); //will beused below (for tab/backtab handling) if (!tab && !backtab) { //for buttons, left/up and right/down keys act like tab/backtab (see qbutton.cpp) if (realWidget->inherits(TQBUTTON_OBJECT_NAME_STRING)) { if (ke->state() == Qt::NoButton && (key == TQt::Key_Right || key == TQt::Key_Down)) tab = true; else if (ke->state() == Qt::NoButton && (key == TQt::Key_Left || key == TQt::Key_Up)) backtab = true; } } if (!tab && !backtab) { // allow the editor widget to grab the key press event while (true) { if (!o || o == dynamic_cast(d->dataAwareObject)) break; if (dynamic_cast(o)) { realWidget = dynamic_cast(o); //will be used below if (realWidget == this) //we have encountered 'this' form surface, give up return false; KexiFormDataItemInterface* dataItemIface = dynamic_cast(o); while (dataItemIface) { if (dataItemIface->keyPressed(ke)) return false; dataItemIface = dynamic_cast(dataItemIface->parentInterface()); //try in tqparent, e.g. in combobox } break; } o = o->tqparent(); } // try to handle global shortcuts at the KexiDataAwareObjectInterface // level (e.g. for "next record" action) int curRow = d->dataAwareObject->currentRow(); int curCol = d->dataAwareObject->currentColumn(); bool moveToFirstField; //if true, we'll move focus to the first field (in tab order) bool moveToLastField; //if true, we'll move focus to the first field (in tab order) if (! (ke->state() == Qt::NoButton && (key == TQt::Key_Home || key == TQt::Key_End || key == TQt::Key_Down || key == TQt::Key_Up)) /* ^^ home/end/down/up are already handled by widgets */ && d->dataAwareObject->handleKeyPress( ke, curRow, curCol, false/*!fullRowSelection*/, &moveToFirstField, &moveToLastField)) { if (ke->isAccepted()) return true; TQWidget* widgetToFocus; if (moveToFirstField) { widgetToFocus = d->orderedFocusWidgets.first(); //? curCol = d->indexOfDataAwareWidget( widgetToFocus ); } else if (moveToLastField) { widgetToFocus = d->orderedFocusWidgets.last(); //? curCol = d->indexOfDataAwareWidget( widgetToFocus ); } else widgetToFocus = d->orderedDataAwareWidgets.at( curCol ); //? d->dataAwareObject->setCursorPosition( curRow, curCol ); if (widgetToFocus) widgetToFocus->setFocus(); else kexipluginswarn << "KexiDBForm::eventFilter(): widgetToFocus not found!" << endl; ke->accept(); return true; } if (key == TQt::Key_Delete && ke->state()==TQt::ControlButton) { //! @todo remove hardcoded shortcuts: can be reconfigured... d->dataAwareObject->deleteCurrentRow(); return true; } } // handle Esc key if (ke->state() == Qt::NoButton && key == TQt::Key_Escape) { //cancel field editing/row editing if possible if (d->dataAwareObject->cancelEditor()) return true; else if (d->dataAwareObject->cancelRowEdit()) return true; return false; // canceling not needed - pass the event to the active widget } // jstaniek: Fix for TQt bug (handling e.g. Alt+2, Ctrl+2 keys on every platform) // It's important because we're using alt+2 short cut by default // Damn! I've reported this to Trolltech in November 2004 - still not fixed. if (ke->isAccepted() && (ke->state() & TQt::AltButton) && ke->text()>="0" && ke->text()<="9") return true; if (tab || backtab) { //the watched widget can be a subwidget of a real widget, e.g. a drop down button of image box: find it while (!KexiFormPart::library()->widgetInfoForClassName(realWidget->className())) realWidget = realWidget->parentWidget(); if (!realWidget) return true; //ignore //the watched widget can be a subwidget of a real widget, e.g. autofield: find it //TQWidget* realWidget = TQT_TQWIDGET(watched); while (dynamic_cast(realWidget) && dynamic_cast(realWidget)->parentInterface()) realWidget = dynamic_cast( dynamic_cast(realWidget)->parentInterface() ); d->setOrderedFocusWidgetsIteratorTo( realWidget ); kexipluginsdbg << realWidget->name() << endl; // find next/prev widget to focus TQWidget *widgetToUnfocus = realWidget; TQWidget *widgetToFocus = 0; bool wasAtFirstWidget = false; //used to protect against infinite loop while (true) { if (tab) { if (d->orderedFocusWidgets.first() && realWidget == d->orderedFocusWidgets.last()) { if (wasAtFirstWidget) break; d->orderedFocusWidgetsIterator.toFirst(); wasAtFirstWidget = true; } else if (realWidget == d->orderedFocusWidgetsIterator.current()) { ++d->orderedFocusWidgetsIterator; //next } else return true; //ignore } else {//backtab if (d->orderedFocusWidgets.last() && realWidget == d->orderedFocusWidgets.first()) { d->orderedFocusWidgetsIterator.toLast(); } else if (realWidget == d->orderedFocusWidgetsIterator.current()) { --d->orderedFocusWidgetsIterator; //prev } else return true; //ignore } widgetToFocus = d->orderedFocusWidgetsIterator.current(); TQObject *pageFor_widgetToFocus = 0; KFormDesigner::TabWidget *tabWidgetFor_widgetToFocus = KFormDesigner::findParent( widgetToFocus, "KFormDesigner::TabWidget", pageFor_widgetToFocus); if (tabWidgetFor_widgetToFocus && TQT_BASE_OBJECT(tabWidgetFor_widgetToFocus->currentPage())!=TQT_BASE_OBJECT(pageFor_widgetToFocus)) { realWidget = widgetToFocus; continue; //the new widget to focus is placed on invisible tab page: move to next widget } break; }//while //set focus, but don't use just setFocus() because certain widgets //behaves differently (e.g. TQLineEdit calls selectAll()) when //focus event's reason is TQFocusEvent::Tab if (widgetToFocus->focusProxy()) widgetToFocus = TQT_TQWIDGET(widgetToFocus->focusProxy()); if (widgetToFocus && d->dataAwareObject->acceptEditor()) { if (tab) { //try to accept this will validate the current input (if any) KexiUtils::unsetFocusWithReason(widgetToUnfocus, TQFocusEvent::Tab); KexiUtils::setFocusWithReason(widgetToFocus, TQFocusEvent::Tab); kexipluginsdbg << "focusing " << widgetToFocus->name() << endl; } else {//backtab KexiUtils::unsetFocusWithReason(widgetToUnfocus, TQFocusEvent::Backtab); //set focus, see above note KexiUtils::setFocusWithReason(d->orderedFocusWidgetsIterator.current(), TQFocusEvent::Backtab); kexipluginsdbg << "focusing " << d->orderedFocusWidgetsIterator.current()->name() << endl; } } return true; } } } else if (e->type()==TQEvent::FocusIn) { bool focusDataWidget = preview(); if (TQT_TQFOCUSEVENT(e)->reason()==TQFocusEvent::Popup) { kdDebug() << "->>> focus IN, popup" <popupFocused; d->popupFocused = false; // if (d->widgetFocusedBeforePopup) { // watched = d->widgetFocusedBeforePopup; // d->widgetFocusedBeforePopup = 0; // } } if (focusDataWidget) { kexipluginsdbg << "KexiDBForm: FocusIn: " << watched->className() << " " << watched->name() << endl; if (d->dataAwareObject) { TQWidget *dataItem = dynamic_cast(watched); while (dataItem) { while (dataItem && !dynamic_cast(dataItem)) dataItem = dataItem->parentWidget(); if (!dataItem) break; kexipluginsdbg << "KexiDBForm: FocusIn: FOUND " << dataItem->className() << " " << dataItem->name() << endl; const int index = d->indexOfDataAwareWidget(dataItem); if (index>=0) { kexipluginsdbg << "KexiDBForm: moving cursor to column #" << index << endl; editedItem = 0; if ((int)index!=d->dataAwareObject->currentColumn()) { d->dataAwareObject->setCursorPosition( d->dataAwareObject->currentRow(), index /*column*/ ); } break; } else dataItem = dataItem->parentWidget(); dataItem->update(); } } } } else if (e->type()==TQEvent::FocusOut) { if (TQT_TQFOCUSEVENT(e)->reason()==TQFocusEvent::Popup) { //d->widgetFocusedBeforePopup = (TQWidget*)watched; d->popupFocused = true; } else d->popupFocused = false; // d->widgetFocusedBeforePopup = 0; // kdDebug() << "e->type()==TQEvent::FocusOut " << watched->className() << " " <name() << endl; // UNSET_FOCUS_USING_REASON(watched, TQT_TQFOCUSEVENT(e)->reason()); } return KexiDBFormBase::eventFilter(watched, e); } bool KexiDBForm::valueIsNull() { return true; } bool KexiDBForm::valueIsEmpty() { return true; } bool KexiDBForm::isReadOnly() const { if (d->dataAwareObject) return d->dataAwareObject->isReadOnly(); //! @todo ? return false; } void KexiDBForm::setReadOnly( bool readOnly ) { if (d->dataAwareObject) d->dataAwareObject->setReadOnly( readOnly ); //??? } TQWidget* KexiDBForm::widget() { return this; } bool KexiDBForm::cursorAtStart() { return false; } bool KexiDBForm::cursorAtEnd() { return false; } void KexiDBForm::clear() { //! @todo clear all fields? } bool KexiDBForm::preview() const { return dynamic_cast(d->dataAwareObject) ? dynamic_cast(d->dataAwareObject)->preview() : false; } void KexiDBForm::dragMoveEvent( TQDragMoveEvent *e ) { KexiDBFormBase::dragMoveEvent( e ); emit handleDragMoveEvent(e); } void KexiDBForm::dropEvent( TQDropEvent *e ) { KexiDBFormBase::dropEvent( e ); emit handleDropEvent(e); } void KexiDBForm::setCursor( const TQCursor & cursor ) { //js: empty, to avoid fscking problems with random cursors! //! @todo? if (KFormDesigner::FormManager::self()->isInserting()) //exception KexiDBFormBase::setCursor(cursor); } //! @todo: TQt4? XORed resize rectangles instead of black widgets /* void KexiDBForm::paintEvent( TQPaintEvent *e ) { TQPainter p; p.begin(this, true); bool unclipped = testWFlags( WPaintUnclipped ); setWFlags( WPaintUnclipped ); p.setPen(white); p.setRasterOp(XorROP); p.drawLine(e->rect().topLeft(), e->rect().bottomRight()); if (!unclipped) clearWFlags( WPaintUnclipped ); p.end(); KexiDBFormBase::paintEvent(e); } */ #include "kexidbform.moc"